diff options
1839 files changed, 88239 insertions, 29390 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 1ea5e59210..05ff204541 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,18 +17,18 @@ skip_commits: - '**/*.md' - '**/*.rdoc' - '**/.document' + - '**/*.[1-8]' + - '**/*.ronn' environment: ruby_version: "24-%Platform%" - zlib_version: "1.2.12" matrix: + # Test only the oldest supported version because AppVeyor is unstable, its concurrency + # 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 - ssl: OpenSSL - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GEMS_FOR_TEST: "" - - build: vs - vs: 140 + 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" @@ -48,8 +48,9 @@ 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 libffi libyaml readline zlib + - vcpkg --triplet %Platform%-windows install --x-use-aria2 libffi libyaml readline zlib - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat - SET vcvars - '"%vcvars%" %Platform:x64=amd64%' @@ -69,9 +70,6 @@ for: - mkdir \usr\local\bin - mkdir \usr\local\include - mkdir \usr\local\lib - - SET ZLIB_ZIP=.downloaded-cache\zlib%zlib_version:.=%.zip - - if not exist %ZLIB_ZIP% curl -fsSL -o %ZLIB_ZIP% --retry 10 https://zlib.net/zlib%zlib_version:.=%.zip - - 7z x -aos -o%APPVEYOR_BUILD_FOLDER%\ext\zlib %ZLIB_ZIP% - for %%I in (%OPENSSL_DIR%\*.dll) do mklink /h \usr\local\bin\%%~nxI %%I - for %%I in (c:\Tools\vcpkg\installed\%Platform%-windows\bin\*.dll) do ( if not %%~nI == readline mklink \usr\local\bin\%%~nxI %%I @@ -79,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% - >- @@ -95,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 @@ -106,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 @@ -124,7 +123,7 @@ notifications: {{^isPullRequest}} { "ci": "AppVeyor CI", - "env": "Visual Studio 2013 / 2015", + "env": "Visual Studio 2013", "url": "{{buildUrl}}", "commit": "{{commitId}}", "branch": "{{branch}}" diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 854a3df982..0000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,134 +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}', '.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 - print_env_script: - - echo "GNUMAKEFLAGS=$GNUMAKEFLAGS" - # 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}')" - 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.1 - - 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 - print_env_script: - - echo "GNUMAKEFLAGS=$GNUMAKEFLAGS" - # 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: RUST_BACKTRACE=1 ./miniruby --yjit-call-threshold=1 -e0 - test_dump_insns_script: RUST_BACKTRACE=1 ./miniruby --yjit-call-threshold=1 --yjit-dump-insns -e0 - output_stats_script: RUST_BACKTRACE=1 ./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" TESTOPTS="$RUBY_TESTOPTS" - make_test_spec_script: source $HOME/.cargo/env && make test-spec RUN_OPTS="--yjit-call-threshold=1" @@ -18,15 +18,18 @@ gc.rb io.rb kernel.rb marshal.rb +mjit.rb numeric.rb nilclass.rb pack.rb ractor.rb string.rb +symbol.rb 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/dependabot.yml b/.github/dependabot.yml index b18fd29357..bc63aca35b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,4 +3,4 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'weekly' + interval: 'monthly' diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml deleted file mode 100644 index 7e163de697..0000000000 --- a/.github/workflows/auto_request_review.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Auto Request Review -on: - pull_request_target: - types: [opened, ready_for_review, reopened] -jobs: - auto-request-review: - name: Auto Request Review - runs-on: ubuntu-latest - steps: - - name: Request review based on files changes and/or groups the author belongs to - uses: necojackarc/auto-request-review@e08cdffa277d50854744de3f76230260e61c67f4 # v0.7.0, checking sha - with: - # scope: public_repo - token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 1c314da911..ebaafe3bf0 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -4,22 +4,36 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' 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: 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: @@ -34,12 +48,12 @@ jobs: - ruby-3.1 steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - 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@v1 + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.ruby }} bundler: none @@ -51,7 +65,7 @@ jobs: - run: make incs - run: make all - run: make test - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -59,7 +73,7 @@ jobs: "env": "${{ github.workflow }} / BASERUBY @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index f6f8b9a45b..070c0fa1dd 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -2,18 +2,31 @@ 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' schedule: - cron: '45 6 * * *' + workflow_dispatch: + +permissions: # added using https://github.com/step-security/secure-workflows + contents: read jobs: update: + permissions: + contents: write # for Git to git push if: ${{ github.event_name != 'schedule' || github.repository == 'ruby/ruby' }} name: update ${{ github.workflow }} runs-on: ubuntu-latest @@ -28,9 +41,9 @@ jobs: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV echo "TODAY=$(date +%F)" >> $GITHUB_ENV - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache-${{ github.sha }} @@ -68,14 +81,14 @@ jobs: [g, v] unless last[g] == v end changed, added = changed.partition {|g, _| last[g]} - news.sub!(/^\*( +)The following #{type} gems? are updated\.\n\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " + news.sub!(/^\*( +)The following #{type} gems? are updated\.\n+\K(?: \1\*( +).*\n)*/) do + mark = "#{$1} *#{$2}" changed.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") end or next - news.sub!(/^\*( +)The following default gems are now bundled gems\.\n\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " + 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} @@ -137,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 fab1989335..79b2916feb 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,11 +26,14 @@ 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: 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') }} @@ -34,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@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -52,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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -60,7 +71,7 @@ jobs: "env": "${{ matrix.os }} / Dependencies need to update", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml deleted file mode 100644 index 32a07f7fd6..0000000000 --- a/.github/workflows/check_misc.yml +++ /dev/null @@ -1,99 +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') }} - -jobs: - checks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - 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]' - - 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@v3 - 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' }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f9fa0a7449..8dba76fbe2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,31 +1,42 @@ name: "Code scanning - action" on: - push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' + # push: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # pull_request: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' schedule: - - cron: '0 12 * * 4' + - cron: '0 12 * * *' + workflow_dispatch: concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: # added using https://github.com/step-security/secure-workflows + contents: read + jobs: CodeQL-Build: # CodeQL runs on ubuntu-latest and windows-latest + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/autobuild to send a status report runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + # CodeQL fails to run pull requests from dependabot due to missing write access to upload results. + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') && github.event.head_commit.pusher.name != 'dependabot[bot]' }} env: enable_install_doc: no @@ -38,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@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -49,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@v2 + 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@v2 + uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 70d0e3ae6c..caf12cc0f4 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -4,13 +4,20 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' - - '**.md' + - '**/man' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.rdoc' - '**/.document' @@ -56,6 +63,9 @@ env: --color=always --tty=no +permissions: + contents: read + jobs: compile: strategy: @@ -70,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 } } @@ -98,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 @@ -174,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' } } @@ -227,10 +225,10 @@ jobs: - name: setenv run: | echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -264,7 +262,7 @@ jobs: - run: make test-annocheck if: ${{ matrix.entry.check && endsWith(matrix.entry.name, 'annocheck') }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -272,7 +270,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.entry.name }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 72f28a7b61..d8dc58b119 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,14 +26,18 @@ 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: make: strategy: 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}} @@ -37,21 +50,21 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - 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 + 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:/opt/homebrew/opt/bison/bin:$PATH"" >> $GITHUB_ENV - run: ./autogen.sh working-directory: src - name: Run configure @@ -81,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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -89,7 +102,7 @@ jobs: "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 020295baa1..0df917d3d8 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,6 +26,9 @@ 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 + # Notes: # Actions console encoding causes issues, see test-all & test-spec steps # @@ -28,19 +40,16 @@ jobs: MSYSTEM: ${{ matrix.msystem }} MSYS2_ARCH: x86_64 CHOST: "x86_64-w64-mingw32" - CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe -fstack-protector-strong" + CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CXXFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CPPFLAGS: "-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048" - LDFLAGS: "-pipe -fstack-protector-strong" + LDFLAGS: "-pipe" UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." GITPULLOPTIONS: --no-tags origin ${{github.ref}} strategy: matrix: include: - - msystem: "MINGW64" - base_ruby: 2.6 - test_task: "check" - test-all-opts: "--name=!/TestObjSpace#test_reachable_objects_during_iteration/" + # To mitigate flakiness of MinGW CI, we test only one runtime that newer MSYS2 uses. - msystem: "UCRT64" base_ruby: head test_task: "check" @@ -56,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@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: Set up Ruby & MSYS2 - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.base_ruby }} - name: set env @@ -150,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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -158,7 +167,7 @@ jobs: "env": "${{ github.workflow }} ${{ matrix.msystem }} / ${{ matrix.test_task }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mjit-bindgen.yml b/.github/workflows/mjit-bindgen.yml index 04c9ac4a9f..26f8a1b2aa 100644 --- a/.github/workflows/mjit-bindgen.yml +++ b/.github/workflows/mjit-bindgen.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,6 +26,9 @@ 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: make: strategy: @@ -24,9 +36,7 @@ jobs: include: - task: mjit-bindgen fail-fast: false - env: - 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 @@ -35,28 +45,28 @@ jobs: run: | echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: Install libraries - env: - arch: ${{ matrix.arch }} run: | set -x - arch=${arch:+:${arch/i[3-6]86/i386}} - ${arch:+sudo dpkg --add-architecture ${arch#:}} sudo apt-get update -q || : sudo apt-get install --no-install-recommends -q -y \ - ${arch:+cross}build-essential${arch/:/-} \ - libssl-dev${arch} libyaml-dev${arch} libreadline6-dev${arch} \ - zlib1g-dev${arch} libncurses5-dev${arch} libffi-dev${arch} \ - libclang1-10${arch} \ - bison autoconf ruby - sudo apt-get install -q -y pkg-config${arch} || : + build-essential \ + libssl-dev libyaml-dev libreadline6-dev \ + zlib1g-dev libncurses5-dev libffi-dev \ + libclang1-14 \ + bison autoconf + sudo apt-get install -q -y pkg-config || : + - name: Set up Ruby + 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@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -68,18 +78,14 @@ jobs: - run: ./autogen.sh working-directory: src - name: Run configure - env: - arch: ${{ matrix.arch }} - run: >- - $SETARCH ../src/configure -C --disable-install-doc --prefix=$(pwd)/install --enable-yjit=dev_nodebug - ${arch:+--target=$arch-$OSTYPE --host=$arch-$OSTYPE} - - run: $SETARCH make incs - - run: $SETARCH make - - run: $SETARCH make install - - run: $SETARCH make ${{ matrix.task }} + run: ../src/configure -C --disable-install-doc --prefix=$(pwd)/install --enable-yjit=dev_nodebug + - run: make incs + - run: make + - run: make install + - run: make ${{ matrix.task }} - run: git diff --exit-code working-directory: src - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -87,7 +93,7 @@ jobs: "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index b5065288c7..6f7181489a 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -3,32 +3,47 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' + - '**.[1-8]' + - '**.ronn' + merge_group: + paths-ignore: + - 'doc/**' + - '**.md' + - '**.rdoc' + - '**/.document' + - '**.[1-8]' + - '**.ronn' 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: make: strategy: matrix: - test_task: [ "check" ] # to make job names consistent - jit_opts: [ "--mjit", "--mjit-wait" ] + test_task: [check] # to make job names consistent + mjit_opts: [--mjit-wait] fail-fast: false runs-on: ubuntu-latest if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} env: TESTOPTS: '-q --tty=no' - RUN_OPTS: '--disable-gems ${{ matrix.jit_opts }} --mjit-debug=-ggdb3' + RUN_OPTS: '--disable-gems ${{ matrix.mjit_opts }} --mjit-debug=-ggdb3' GITPULLOPTIONS: --no-tags origin ${{github.ref}} steps: - run: mkdir build @@ -42,10 +57,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -64,31 +79,30 @@ 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 - run: | - ulimit -c unlimited - make -s test-all RUN_OPTS="$RUN_OPTS" - timeout-minutes: 60 + # - name: Run test-all + # run: | + # ulimit -c unlimited + # make -s test-all RUN_OPTS="$RUN_OPTS" + # 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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { "ci": "GitHub Actions", - "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.jit_opts }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.mjit_opts }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot 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 new file mode 100644 index 0000000000..c12a95362d --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecards supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '22 4 * * 2' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@ea651e62978af7915d09fe2e282747c798bf2dab # v2.4.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.27 + with: + sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 7bffe25bb2..4521195a2b 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -2,47 +2,61 @@ name: Rubyspec Version Guards Check on: push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' + paths: + - 'spec/**' + - '!spec/*.md' pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' + paths: + - 'spec/**' + - '!spec/*.md' + merge_group: 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: 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@v3 - - uses: ruby/setup-ruby@v1 + - 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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -50,8 +64,8 @@ jobs: "env": "${{ github.workflow }} / rubyspec @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "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' }} + if: ${{ failure() }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index a1818182a3..4fbca1170e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,6 +26,9 @@ 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: make: strategy: @@ -34,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 @@ -42,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 @@ -68,10 +79,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -116,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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -124,7 +135,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 553347d727..27920b5821 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,6 +26,9 @@ concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: # added using https://github.com/step-security/secure-workflows + contents: read + jobs: make: strategy: @@ -38,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 @@ -47,7 +59,7 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Install libraries @@ -79,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 \ @@ -103,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 ab9e35d5a3..c2bd4881c2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,90 +26,76 @@ 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: make: 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@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@v3 - 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@v3 + - 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@v3 - 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@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - 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 @@ -125,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 @@ -134,7 +129,7 @@ jobs: env: RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --job-status=normal timeout-minutes: 60 - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -142,7 +137,7 @@ jobs: "env": "VS${{ matrix.vs }} / ${{ matrix.test_task || 'check' }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index ae108d72a5..0b7b9046e9 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -3,12 +3,21 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' @@ -17,13 +26,16 @@ 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: 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@v3 + - 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 @@ -40,19 +52,23 @@ jobs: fail-fast: false matrix: include: - - test_task: "check-yjit-bindings" - 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" - configure: "--enable-yjit RUSTC='rustc +1.58.1'" # release build - rust_version: "1.58.1" + # YJIT should be automatically built in release mode on x86-64 Linux with rustc present + #configure: "--enable-yjit RUSTC='rustc +1.58.0'" + configure: "RUSTC='rustc +1.58.0'" + rust_version: "1.58.0" - test_task: "check" configure: "--enable-yjit=dev" - test_task: "check" configure: "--enable-yjit=dev" - yjit_opts: "--yjit-call-threshold=1" + yjit_opts: "--yjit-call-threshold=1 --yjit-verify-ctx" - test_task: "test-all TESTS=--repeat-count=2" configure: "--enable-yjit=dev" @@ -69,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 @@ -86,10 +102,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v3 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -117,6 +133,9 @@ jobs: if: ${{ matrix.test_task == 'check' }} - name: Enable YJIT through ENV run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + # Check that the binary was built with YJIT + - name: Check YJIT enabled + run: ./miniruby --yjit -v | grep "+YJIT" - name: make ${{ matrix.test_task }} run: make -s -j ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" YJIT_BENCH_OPTS="$YJIT_BENCH_OPTS" timeout-minutes: 60 @@ -124,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}" @@ -131,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: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -139,7 +159,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.gitignore b/.gitignore index 4bbf24f094..99d32a1825 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.inc *.log *.o +*.o.tmp *.obj *.old *.orig @@ -25,6 +26,7 @@ *.sav *.sl *.so +*.so.* *.swp *.yarb *~ @@ -145,6 +147,8 @@ lcov*.info /bin/*.exe /bin/*.dll +/bin/goruby +/bin/ruby # /benchmark/ /benchmark/bm_require.data @@ -234,12 +238,13 @@ 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 # YJIT /yjit-bench +/yjit_exit_locations.dump # /wasm/ /wasm/tests/*.wasm 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. @@ -23,13 +23,13 @@ Note that each entry is kept to a minimum, see links for details. * A proc that accepts a single positional argument and keywords will no longer autosplat. [[Bug #18633]] - ```ruby - proc{|a, **k| a}.call([1, 2]) - # Ruby 3.1 and before - # => 1 - # Ruby 3.2 and after - # => [1, 2] - ``` + ```ruby + proc{|a, **k| a}.call([1, 2]) + # Ruby 3.1 and before + # => 1 + # Ruby 3.2 and after + # => [1, 2] + ``` * Constant assignment evaluation order for constants set on explicit objects has been made consistent with single attribute assignment @@ -39,24 +39,24 @@ Note that each entry is kept to a minimum, see links for details. foo::BAR = baz ``` - `foo` is now called before `baz`. Similarly, for multiple assignments - to constants, left-to-right evaluation order is used. With this - code: + `foo` is now called before `baz`. Similarly, for multiple assignments + to constants, left-to-right evaluation order is used. With this + code: ```ruby - foo1::BAR1, foo2::BAR2 = baz1, baz2 + foo1::BAR1, foo2::BAR2 = baz1, baz2 ``` - The following evaluation order is now used: + The following evaluation order is now used: - 1. `foo1` - 2. `foo2` - 3. `baz1` - 4. `baz2` + 1. `foo1` + 2. `foo2` + 3. `baz1` + 4. `baz2` - [[Bug #15928]] + [[Bug #15928]] -* Find pattern is no longer experimental. +* "Find pattern" is no longer experimental. [[Feature #18585]] * Methods taking a rest parameter (like `*args`) and wishing to delegate keyword @@ -90,55 +90,151 @@ 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 `Coverge.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. +* Fiber + + * Introduce Fiber.[] and Fiber.[]= for inheritable fiber storage. + Introduce Fiber#storage and Fiber#storage= (experimental) for + getting and resetting the current storage. Introduce + `Fiber.new(storage:)` for setting the storage when creating a + 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 + 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 + storage provides. + + ```ruby + def log(message) + puts "#{Fiber[:request_id]}: #{message}" + end + + def handle_requests + while request = read_request + Fiber.schedule do + Fiber[:request_id] = SecureRandom.uuid + + request.messages.each do |message| + Fiber.schedule do + log("Handling #{message}") # Log includes inherited request_id. + end + end + end + end + end + ``` + + You should generally consider Fiber storage for any state which + you want to be shared implicitly between all fibers and threads + created in a given context, e.g. a connection pool, a request + id, a logger level, environment variables, configuration, etc. + +* Fiber::Scheduler + + * Introduce `Fiber::Scheduler#io_select` for non-blocking IO.select. + [[Feature #19060]] + +* IO + + * Introduce IO#timeout= and IO#timeout which can cause + IO::TimeoutError to be raised if a blocking operation exceeds the + specified timeout. [[Feature #18630]] + + ```ruby + STDIN.timeout = 1 + STDIN.read # => Blocking operation timed out! (IO::TimeoutError) + ``` + + * Introduce `IO.new(..., path:)` and promote `File#path` to `IO#path`. + [[Feature #19036]] + +* Class + + * Class#attached_object, which returns the object for which + the receiver is the singleton class. Raises TypeError if the + receiver is not a singleton class. + [[Feature #12084]] + + ```ruby + class Foo; end + + Foo.singleton_class.attached_object #=> Foo + Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370> + Foo.attached_object #=> TypeError: `Foo' is not a singleton class + nil.singleton_class.attached_object #=> TypeError: `NilClass' is not a singleton class + ``` + * Data + * New core class to represent simple immutable value object. The class is - similar to `Struct` and partially shares an implementation, but has more + 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]] * The dummy `Encoding::UTF_16` and `Encoding::UTF_32` encodings no longer 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. + 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 + * Enumerator.product has been added. Enumerator::Product is the implementation. [[Feature #18685]] +* Exception + + * Exception#detailed_message has been added. + The default error printer calls this method on the Exception object + instead of #message. [[Feature #18564]] + * Hash + * Hash#shift now always returns nil if the hash is empty, instead of returning the default value or calling the default proc. [[Bug #16908]] * Integer + * Integer#ceildiv has been added. [[Feature #18809]] * Kernel + * Kernel#binding raises RuntimeError if called from a non-Ruby frame (such as a method defined in C). [[Bug #18487]] * MatchData + * MatchData#byteoffset has been added. [[Feature #13110]] + * MatchData#deconstruct has been added. [[Feature #18821]] + * MatchData#deconstruct_keys has been added. [[Feature #18821]] * Module + * Module.used_refinements has been added. [[Feature #14332]] * Module#refinements has been added. [[Feature #12737]] * Module#const_added has been added. [[Feature #17881]] * Module#undefined_instance_methods has been added. [[Feature #12655]] * Proc + * Proc#dup returns an instance of subclass. [[Bug #17545]] * Proc#parameters now accepts lambda keyword. [[Feature #15357]] @@ -146,89 +242,335 @@ Note: We're only listing outstanding class updates. * Added `RLIMIT_NPTS` constant to FreeBSD platform * 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. [[Feature #18788]] + * Regexp.timeout= has been added. Also, Regexp.new new supports timeout keyword. + See [[Feature #17837]] + * Refinement + * Refinement#refined_class has been added. [[Feature #12737]] +* RubyVM::AbstractSyntaxTree + + * Add `error_tolerant` option for `parse`, `parse_file` and `of`. [[Feature #19013]] + With this option + + 1. SyntaxError is suppressed + 2. AST is returned for invalid input + 3. `end` is complemented when a parser reaches to the end of input but `end` is insufficient + 4. `end` is treated as keyword based on indent + + ```ruby + # Without error_tolerant option + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY) + def m + a = 10 + if + end + RUBY + # => <internal:ast>:33:in `parse': syntax error, unexpected `end' (SyntaxError) + + # With error_tolerant option + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true) + def m + a = 10 + if + end + RUBY + p root # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-4:3> + + # `end` is treated as keyword based on indent + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true) + module Z + class Foo + foo. + end + + def bar + end + end + RUBY + p root.children[-1].children[-1].children[-1].children[-2..-1] + # => [#<RubyVM::AbstractSyntaxTree::Node:CLASS@2:2-4:5>, #<RubyVM::AbstractSyntaxTree::Node:DEFN@6:2-7:5>] + ``` + + * Add `keep_tokens` option for `parse`, `parse_file` and `of`. Add `#tokens` and `#all_tokens` + for RubyVM::AbstractSyntaxTree::Node [[Feature #19070]] + + ```ruby + root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + root.tokens.map{_1[2]}.join # => "x = 1 + 2" + ``` + * Set - * 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` + * 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. * String + * String#byteindex and String#byterindex have been added. [[Feature #13110]] - * Update Unicode to Version 14.0.0 and Emoji Version 14.0. [[Feature #18037]] + * Update Unicode to Version 15.0.0 and Emoji Version 15.0. [[Feature #18639]] (also applies to Regexp) * String#bytesplice has been added. [[Feature #18598]] + * String#dedup has been added as an alias to String#-@. [[Feature #18595]] * Struct + * A Struct class can also be initialized with keyword arguments - without `keyword_init: true` on `Struct.new` [[Feature #16806]] + 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::Queue + + * 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]] + +* 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]] * TracePoint + * 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 + + * `UnboundMethod#==` returns `true` if the actual method is same. For example, + `String.instance_method(:object_id) == Array.instance_method(:object_id)` + returns `true`. [[Feature #18798]] + + * `UnboundMethod#inspect` does not show the receiver of `instance_method`. + For example `String.instance_method(:object_id).inspect` returns + `"#<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 + + * 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]] + * 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 - * bigdecimal 3.1.2 - * bundler 2.4.0.dev - * cgi 0.3.3 - * date 3.2.3 - * error_highlight 0.4.0 - * etc 1.4.0 - * io-console 0.5.11 - * io-nonblock 0.1.1 - * io-wait 0.3.0.pre - * ipaddr 1.2.4 - * json 2.6.2 - * logger 1.5.1 - * net-http 0.2.2 - * net-protocol 0.1.3 + + * RubyGems 3.4.1 + * abbrev 0.1.1 + * benchmark 0.2.1 + * bigdecimal 3.1.3 + * bundler 2.4.1 + * cgi 0.3.6 + * csv 3.2.6 + * date 3.3.3 + * delegate 0.3.0 + * 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 + * fcntl 1.0.2 + * fiddle 1.1.1 + * fileutils 1.7.0 + * forwardable 1.3.3 + * getoptlong 0.2.0 + * io-console 0.6.0 + * io-nonblock 0.2.0 + * io-wait 0.3.0 + * ipaddr 1.2.5 + * irb 1.6.2 + * json 2.6.3 + * logger 1.5.3 + * mutex_m 0.1.2 + * net-http 0.4.0 + * net-protocol 0.2.1 + * nkf 0.1.2 + * open-uri 0.3.0 + * open3 0.1.2 + * openssl 3.1.0 + * optparse 0.3.1 * ostruct 0.5.5 - * psych 5.0.0.dev - * reline 0.3.1 - * securerandom 0.2.0 + * pathname 0.2.1 + * pp 0.4.0 + * pstore 0.1.2 + * psych 5.0.1 + * racc 1.6.2 + * rdoc 6.5.0 + * readline-ext 0.1.5 + * reline 0.3.2 + * resolv 0.2.2 + * resolv-replace 0.1.1 + * securerandom 0.2.2 * set 1.0.3 - * stringio 3.0.3 - * syntax_suggest 0.0.1 - * timeout 0.3.0 + * stringio 3.0.4 + * strscan 3.0.5 + * 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 + * The following bundled gems are updated. + * minitest 5.16.3 - * net-imap 0.3.1 + * power_assert 2.0.3 + * test-unit 3.5.7 + * net-ftp 0.2.0 + * net-imap 0.3.4 * net-pop 0.1.2 - * net-smtp 0.3.2 - * rbs 2.6.0 + * net-smtp 0.3.3 + * rbs 2.8.2 * typeprof 0.21.3 - * debug 1.6.2 -* The following default gems are now bundled gems. + * 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. + +## Supported platforms + +* WebAssembly/WASI is added. See [wasm/README.md] and [ruby.wasm] for more details. [[Feature #18462]] ## Compatibility issues -Note: Excluding feature bug fixes. +* `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 @@ -250,15 +592,77 @@ The following deprecated methods are removed. [[Feature #16131]] * `Kernel#trust`, `Kernel#untrust`, `Kernel#untrusted?` [[Feature #16131]] +* `Method#public?`, `Method#private?`, `Method#protected?`, + `UnboundMethod#public?`, `UnboundMethod#private?`, `UnboundMethod#protected?` + [[Bug #18729]] [[Bug #18751]] [[Bug #18435]] + +### Source code incompatibility of extension libraries + +* Extension libraries provide PRNG, subclasses of Random, need updates. + See [PRNG update] below for more information. [[Bug #19100]] + +### Error printer + +* Ruby no longer escapes control characters and backslashes in an + error message. [[Feature #18367]] + +### 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 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 -* `Psych` no longer bundles libyaml sources. - Users need to install the libyaml library themselves via the package - system. [[Feature #18571]] +* Psych no longer bundles libyaml sources. + And also Fiddle no longer bundles libffi sources. + Users need to install the libyaml/libffi library themselves via the package + manager like apt, yum, brew, etc. + + Psych and fiddle supported the static build with specific version of libyaml + and libffi sources. You can build psych with libyaml-0.2.5 like this. + + ```bash + $ ./configure --with-libyaml-source-dir=/path/to/libyaml-0.2.5 + ``` + + And you can build fiddle with libffi-3.4.4 like this. + + ```bash + $ ./configure --with-libffi-source-dir=/path/to/libffi-3.4.4 + ``` + + [[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 + +The following APIs are updated. + +* PRNG update + + `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 + +* `VALUE rb_hash_new_capa(long capa)` was added to created hashes with the desired capacity. +* `rb_internal_thread_add_event_hook` and `rb_internal_thread_add_event_hook` were added to instrument threads scheduling. + The following events are available: + * `RUBY_INTERNAL_THREAD_EVENT_STARTED` + * `RUBY_INTERNAL_THREAD_EVENT_READY` + * `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 The following deprecated APIs are removed. @@ -268,64 +672,149 @@ The following deprecated APIs are removed. ## Implementation improvements -* Fixed several race conditions in `Kernel#autoload`. [[Bug #18782]] +* Fixed several race conditions in Kernel#autoload. [[Bug #18782]] +* Cache invalidation for expressions referencing constants is now + more fine-grained. `RubyVM.stat(:global_constant_state)` was + removed because it was closely tied to the previous caching scheme + where setting any constant invalidates all caches in the system. + 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 -* Support arm64 / aarch64 on UNIX platforms. -* Building YJIT requires Rust 1.58.1+. [[Feature #18481]] +* 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]] + * In order to ensure that CRuby is built with YJIT, please install `rustc` >= 1.58.0 + before running `./configure` + * Please reach out to the YJIT team should you run into any issues. +* Physical memory for JIT code is lazily allocated. Unlike Ruby 3.1, + the RSS of a Ruby process is minimized because virtual memory pages + allocated by `--yjit-exec-mem-size` will not be mapped to physical + memory pages until actually utilized by JIT code. +* Introduce Code GC that frees all code pages when the memory consumption + by JIT code reaches `--yjit-exec-mem-size`. + * `RubyVM::YJIT.runtime_stats` returns Code GC metrics in addition to + existing `inline_code_size` and `outlined_code_size` keys: + `code_gc_count`, `live_page_count`, `freed_page_count`, and `freed_code_size`. +* Most of the statistics produced by `RubyVM::YJIT.runtime_stats` are now available in release builds. + * 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 64 (MiB). +* The default `--yjit-call-threshold` is changed to 30. ### MJIT -## Static analysis - -### RBS - -### TypeProf - -## Debugger - -## error_highlight - -## IRB Autocomplete and Document Display - -## Miscellaneous changes - -[Feature #12005]: https://bugs.ruby-lang.org/issues/12005 -[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 #16131]: https://bugs.ruby-lang.org/issues/16131 -[Bug #16466]: https://bugs.ruby-lang.org/issues/16466 -[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 #17881]: https://bugs.ruby-lang.org/issues/17881 -[Feature #18037]: https://bugs.ruby-lang.org/issues/18037 -[Feature #18159]: https://bugs.ruby-lang.org/issues/18159 -[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 -[Bug #18487]: https://bugs.ruby-lang.org/issues/18487 -[Feature #18571]: https://bugs.ruby-lang.org/issues/18571 -[Feature #18585]: https://bugs.ruby-lang.org/issues/18585 -[Feature #18598]: https://bugs.ruby-lang.org/issues/18598 -[Bug #18625]: https://bugs.ruby-lang.org/issues/18625 -[Bug #18633]: https://bugs.ruby-lang.org/issues/18633 -[Feature #18685]: https://bugs.ruby-lang.org/issues/18685 -[Bug #18782]: https://bugs.ruby-lang.org/issues/18782 -[Feature #18788]: https://bugs.ruby-lang.org/issues/18788 -[Feature #18809]: https://bugs.ruby-lang.org/issues/18809 -[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 -[Feature #18949]: https://bugs.ruby-lang.org/issues/18949 -[Feature #19008]: https://bugs.ruby-lang.org/issues/19008 -[Feature #19026]: https://bugs.ruby-lang.org/issues/19026 -[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 +* 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. +* MinGW is no longer supported. [[Feature #18824]] +* 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 +[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 fe4ad84423..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; @@ -869,8 +910,11 @@ typedef struct { int type; } DebugInfoValue; -/* TODO: Big Endian */ +#if defined(WORDS_BIGENDIAN) +#define MERGE_2INTS(a,b,sz) (((uint64_t)(a)<<sz)|(b)) +#else #define MERGE_2INTS(a,b,sz) (((uint64_t)(b)<<sz)|(a)) +#endif static uint16_t get_uint16(const uint8_t *p) @@ -973,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 @@ -1017,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; @@ -1028,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; @@ -1074,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); @@ -1138,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: @@ -1186,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)); @@ -1205,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)); @@ -1224,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; @@ -1373,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; @@ -1383,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; } @@ -1422,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) { @@ -1437,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; @@ -1548,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; } @@ -1580,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; @@ -1643,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 = {}; @@ -1669,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; @@ -1677,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; @@ -1694,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)); @@ -1715,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) @@ -1843,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++) { @@ -2100,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) { @@ -2299,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; } @@ -837,11 +844,11 @@ ary_new(VALUE klass, long capa) } else { ary = ary_alloc_heap(klass); + ARY_SET_CAPA(ary, capa); assert(!ARY_EMBED_P(ary)); ptr = ary_heap_alloc(ary, capa); ARY_SET_PTR(ary, ptr); - ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); } @@ -945,11 +952,11 @@ ec_ary_new(rb_execution_context_t *ec, VALUE klass, long capa) } else { ary = ec_ary_alloc_heap(ec, klass); + ARY_SET_CAPA(ary, capa); assert(!ARY_EMBED_P(ary)); ptr = ary_heap_alloc(ary, capa); ARY_SET_PTR(ary, ptr); - ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); } @@ -1056,6 +1063,7 @@ ary_make_shared(VALUE ary) /* Shared roots cannot be embedded because the reference count * (refcnt) is stored in as.heap.aux.capa. */ VALUE shared = ary_alloc_heap(0); + FL_SET_SHARED_ROOT(shared); if (ARY_EMBED_P(ary)) { /* Cannot use ary_heap_alloc because we don't want to allocate @@ -1074,7 +1082,6 @@ ary_make_shared(VALUE ary) ARY_SET_LEN(shared, capa); ary_mem_clear(shared, len, capa - len); - FL_SET_SHARED_ROOT(shared); ARY_SET_SHARED_ROOT_REFCNT(shared, 1); FL_SET_SHARED(ary); RB_DEBUG_COUNTER_INC(obj_ary_shared_create); @@ -1348,11 +1355,11 @@ ary_make_partial(VALUE ary, VALUE klass, long offset, long len) return result; } else { + VALUE shared = ary_make_shared(ary); + VALUE result = ary_alloc_heap(klass); assert(!ARY_EMBED_P(result)); - VALUE shared = ary_make_shared(ary); - ARY_SET_PTR(result, RARRAY_CONST_PTR_TRANSIENT(ary)); ARY_SET_LEN(result, RARRAY_LEN(ary)); rb_ary_set_shared(result, shared); @@ -1389,7 +1396,7 @@ ary_make_partial_step(VALUE ary, VALUE klass, long offset, long len, long step) } long ustep = (step < 0) ? -step : step; - len = (len + ustep - 1) / ustep; + len = roomof(len, ustep); long i; long j = offset + ((step > 0) ? 0 : (orig_len - 1)); @@ -2086,7 +2093,7 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * * If +self+ is empty, returns +nil+. * - * When non-negative \Innteger argument +n+ is given, + * When non-negative \Integer argument +n+ is given, * returns the last +n+ elements in a new \Array: * * a = [:foo, 'bar', 2] @@ -3456,7 +3463,6 @@ rb_ary_rotate_m(int argc, VALUE *argv, VALUE ary) struct ary_sort_data { VALUE ary; VALUE receiver; - struct cmp_opt_data cmp_opt; }; static VALUE @@ -3502,15 +3508,15 @@ sort_2(const void *ap, const void *bp, void *dummy) VALUE a = *(const VALUE *)ap, b = *(const VALUE *)bp; int n; - if (FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(data->cmp_opt, Integer)) { + if (FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(INTEGER)) { if ((long)a > (long)b) return 1; if ((long)a < (long)b) return -1; return 0; } - if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(data->cmp_opt, String)) { + if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(STRING)) { return rb_str_cmp(a, b); } - if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(data->cmp_opt, Float)) { + if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(FLOAT)) { return rb_float_cmp(a, b); } @@ -3574,8 +3580,6 @@ rb_ary_sort_bang(VALUE ary) RBASIC_CLEAR_CLASS(tmp); data.ary = tmp; data.receiver = ary; - data.cmp_opt.opt_methods = 0; - data.cmp_opt.opt_inited = 0; RARRAY_PTR_USE(tmp, ptr, { ruby_qsort(ptr, len, sizeof(VALUE), rb_block_given_p()?sort_1:sort_2, &data); @@ -3732,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 { @@ -4555,7 +4559,7 @@ take_items(VALUE obj, long n) if (!NIL_P(result)) return rb_ary_subseq(result, 0, n); result = rb_ary_new2(n); args[0] = result; args[1] = (VALUE)n; - if (rb_check_block_call(obj, idEach, 0, 0, take_i, (VALUE)args) == Qundef) + if (UNDEF_P(rb_check_block_call(obj, idEach, 0, 0, take_i, (VALUE)args))) rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)", rb_obj_class(obj)); return result; @@ -5048,7 +5052,7 @@ rb_ary_fill(int argc, VALUE *argv, VALUE ary) ARY_SET_LEN(ary, end); } - if (item == Qundef) { + if (UNDEF_P(item)) { VALUE v; long i; @@ -5505,7 +5509,7 @@ rb_ary_cmp(VALUE ary1, VALUE ary2) if (NIL_P(ary2)) return Qnil; if (ary1 == ary2) return INT2FIX(0); v = rb_exec_recursive_paired(recursive_cmp, ary1, ary2, ary2); - if (v != Qundef) return v; + if (!UNDEF_P(v)) return v; len = RARRAY_LEN(ary1) - RARRAY_LEN(ary2); if (len == 0) return INT2FIX(0); if (len > 0) return INT2FIX(1); @@ -6056,7 +6060,6 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) static VALUE rb_ary_max(int argc, VALUE *argv, VALUE ary) { - struct cmp_opt_data cmp_opt = { 0, 0 }; VALUE result = Qundef, v; VALUE num; long i; @@ -6068,7 +6071,7 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) if (rb_block_given_p()) { for (i = 0; i < RARRAY_LEN(ary); i++) { v = RARRAY_AREF(ary, i); - if (result == Qundef || rb_cmpint(rb_yield_values(2, v, result), v, result) > 0) { + if (UNDEF_P(result) || rb_cmpint(rb_yield_values(2, v, result), v, result) > 0) { result = v; } } @@ -6076,13 +6079,13 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) else if (n > 0) { result = RARRAY_AREF(ary, 0); if (n > 1) { - if (FIXNUM_P(result) && CMP_OPTIMIZABLE(cmp_opt, Integer)) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { return ary_max_opt_fixnum(ary, 1, result); } - else if (STRING_P(result) && CMP_OPTIMIZABLE(cmp_opt, String)) { + else if (STRING_P(result) && CMP_OPTIMIZABLE(STRING)) { return ary_max_opt_string(ary, 1, result); } - else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(cmp_opt, Float)) { + else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(FLOAT)) { return ary_max_opt_float(ary, 1, result); } else { @@ -6090,7 +6093,7 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) } } } - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } @@ -6225,7 +6228,6 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) static VALUE rb_ary_min(int argc, VALUE *argv, VALUE ary) { - struct cmp_opt_data cmp_opt = { 0, 0 }; VALUE result = Qundef, v; VALUE num; long i; @@ -6237,7 +6239,7 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) if (rb_block_given_p()) { for (i = 0; i < RARRAY_LEN(ary); i++) { v = RARRAY_AREF(ary, i); - if (result == Qundef || rb_cmpint(rb_yield_values(2, v, result), v, result) < 0) { + if (UNDEF_P(result) || rb_cmpint(rb_yield_values(2, v, result), v, result) < 0) { result = v; } } @@ -6245,13 +6247,13 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) else if (n > 0) { result = RARRAY_AREF(ary, 0); if (n > 1) { - if (FIXNUM_P(result) && CMP_OPTIMIZABLE(cmp_opt, Integer)) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { return ary_min_opt_fixnum(ary, 1, result); } - else if (STRING_P(result) && CMP_OPTIMIZABLE(cmp_opt, String)) { + else if (STRING_P(result) && CMP_OPTIMIZABLE(STRING)) { return ary_min_opt_string(ary, 1, result); } - else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(cmp_opt, Float)) { + else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(FLOAT)) { return ary_min_opt_float(ary, 1, result); } else { @@ -6259,7 +6261,7 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) } } } - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } @@ -8148,7 +8150,7 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) { if (n != 0) v = rb_fix_plus(LONG2FIX(n), v); - if (r != Qundef) { + if (!UNDEF_P(r)) { v = rb_rational_plus(r, v); } else if (!n && z) { @@ -8227,7 +8229,7 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) else if (RB_BIGNUM_TYPE_P(e)) v = rb_big_plus(e, v); else if (RB_TYPE_P(e, T_RATIONAL)) { - if (r == Qundef) + if (UNDEF_P(r)) r = e; else r = rb_rational_plus(r, e); @@ -8737,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. @@ -64,8 +64,8 @@ ast_new_internal(rb_ast_t *ast, const NODE *node) return obj; } -static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines); -static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines); +static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); +static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); static VALUE ast_parse_new(void) @@ -85,31 +85,33 @@ ast_parse_done(rb_ast_t *ast) } static VALUE -ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines) +ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_str(str, keep_script_lines); + return rb_ast_parse_str(str, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_str(VALUE str, VALUE keep_script_lines) +rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; StringValue(str); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); return ast_parse_done(ast); } static VALUE -ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines) +ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_file(path, keep_script_lines); + return rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_file(VALUE path, VALUE keep_script_lines) +rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE f; rb_ast_t *ast = 0; @@ -120,6 +122,8 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines) rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); rb_io_close(f); return ast_parse_done(ast); @@ -139,13 +143,15 @@ lex_array(VALUE array, int index) } static VALUE -rb_ast_parse_array(VALUE array, VALUE keep_script_lines) +rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; array = rb_check_array_type(array); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); return ast_parse_done(ast); } @@ -193,7 +199,24 @@ script_lines(VALUE path) } static VALUE -ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines) +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; + } + + return INT2NUM(node_id); +} + +static VALUE +ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE node, lines = Qnil; const rb_iseq_t *iseq; @@ -232,13 +255,13 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script } if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) { - node = rb_ast_parse_array(lines, keep_script_lines); + node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant, keep_tokens); } else if (e_option) { - node = rb_ast_parse_str(rb_e_script, keep_script_lines); + node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant, keep_tokens); } else { - node = rb_ast_parse_file(path, keep_script_lines); + node = rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } return node_find(node, node_id); @@ -645,6 +668,8 @@ node_children(rb_ast_t *ast, const NODE *node) NEW_CHILD(ast, node->nd_pkwargs), kwrest); } + case NODE_ERROR: + return rb_ary_new_from_node_args(ast, 0); case NODE_ARGS_AUX: case NODE_LAST: break; @@ -699,6 +724,15 @@ ast_node_last_column(rb_execution_context_t *ec, VALUE self) } static VALUE +ast_node_all_tokens(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + + return rb_ast_tokens(data->ast); +} + +static VALUE ast_node_inspect(rb_execution_context_t *ec, VALUE self) { VALUE str; @@ -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> - def self.parse string, keep_script_lines: false - Primitive.ast_s_parse string, keep_script_lines + # + # 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> - def self.parse_file pathname, keep_script_lines: false - Primitive.ast_s_parse_file pathname, keep_script_lines + # + # 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,8 +91,25 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.of(method(:hello)) # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> - def self.of body, keep_script_lines: false - Primitive.ast_s_of body, keep_script_lines + # + # 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 + + # call-seq: + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(backtrace_location) -> integer + # + # Returns the node id for the given backtrace location. + # + # begin + # raise + # rescue => e + # loc = e.backtrace_locations.first + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + # end # => 0 + def self.node_id_for_backtrace_location backtrace_location + Primitive.node_id_for_backtrace_location backtrace_location end # RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in @@ -122,6 +167,47 @@ module RubyVM::AbstractSyntaxTree end # call-seq: + # node.tokens -> array + # + # Returns tokens corresponding to the location of the node. + # Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.tokens.map{_1[2]}.join # => "x = 1 + 2" + # + # Token is an array of: + # + # - id + # - token type + # - source code text + # - location [ first_lineno, first_column, last_lineno, last_column ] + def tokens + return nil unless all_tokens + + all_tokens.each_with_object([]) do |token, a| + loc = token.last + if ([first_lineno, first_column] <=> [loc[0], loc[1]]) <= 0 && + ([last_lineno, last_column] <=> [loc[2], loc[3]]) >= 0 + a << token + end + end + end + + # call-seq: + # node.all_tokens -> array + # + # Returns all tokens for the input script regardless the receiver node. + # Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.children[-1].all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + def all_tokens + Primitive.ast_node_all_tokens + end + + # call-seq: # node.children -> array # # Returns AST nodes under this one. Each kind of node diff --git a/benchmark/array_sort_int.yml b/benchmark/array_sort_int.yml new file mode 100644 index 0000000000..7b9027ebf7 --- /dev/null +++ b/benchmark/array_sort_int.yml @@ -0,0 +1,15 @@ +prelude: | + ary2 = 2.times.to_a.shuffle + ary10 = 10.times.to_a.shuffle + ary100 = 100.times.to_a.shuffle + ary1000 = 1000.times.to_a.shuffle + ary10000 = 10000.times.to_a.shuffle + +benchmark: + ary2.sort: ary2.sort + ary10.sort: ary10.sort + ary100.sort: ary100.sort + ary1000.sort: ary1000.sort + ary10000.sort: ary10000.sort + +loop_count: 10000 diff --git a/benchmark/cgi_escape_html.yml b/benchmark/cgi_escape_html.yml index af6abd08ac..655be9d7d8 100644 --- a/benchmark/cgi_escape_html.yml +++ b/benchmark/cgi_escape_html.yml @@ -1,32 +1,23 @@ -prelude: require 'cgi/escape' +prelude: | + # frozen_string_literal: true + require 'cgi/escape' benchmark: - - name: escape_html_blank - prelude: str = "" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("") loop_count: 20000000 - - name: escape_html_short_none - prelude: str = "abcde" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcde") loop_count: 20000000 - - name: escape_html_short_one - prelude: str = "abcd<" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcd<") loop_count: 20000000 - - name: escape_html_short_all - prelude: str = "'&\"<>" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("'&\"<>") loop_count: 5000000 - - name: escape_html_long_none - prelude: str = "abcde" * 300 - script: CGI.escapeHTML(str) + - prelude: long_no_escape = "abcde" * 300 + script: CGI.escapeHTML(long_no_escape) loop_count: 1000000 - - name: escape_html_long_all - prelude: str = "'&\"<>" * 10 - script: CGI.escapeHTML(str) + - prelude: long_all_escape = "'&\"<>" * 10 + script: CGI.escapeHTML(long_all_escape) loop_count: 1000000 - - name: escape_html_real - prelude: | # http://example.com/ - str = <<~HTML + - prelude: | # http://example.com/ + example_html = <<~HTML <body> <div> <h1>Example Domain</h1> @@ -36,5 +27,5 @@ benchmark: </div> </body> HTML - script: CGI.escapeHTML(str) + script: CGI.escapeHTML(example_html) loop_count: 1000000 diff --git a/benchmark/enum_minmax.yml b/benchmark/enum_minmax.yml new file mode 100644 index 0000000000..9d01731abb --- /dev/null +++ b/benchmark/enum_minmax.yml @@ -0,0 +1,25 @@ +prelude: | + set2 = 2.times.to_a.shuffle.to_set + set10 = 10.times.to_a.shuffle.to_set + set100 = 100.times.to_a.shuffle.to_set + set1000 = 1000.times.to_a.shuffle.to_set + set10000 = 10000.times.to_a.shuffle.to_set + +benchmark: + set2.min: set2.min + set10.min: set10.min + set100.min: set100.min + set1000.min: set1000.min + set10000.min: set10000.min + set2.max: set2.max + set10.max: set10.max + set100.max: set100.max + set1000.max: set1000.max + set10000.max: set10000.max + set2.minmax: set2.minmax + set10.minmax: set10.minmax + set100.minmax: set100.minmax + set1000.minmax: set1000.minmax + set10000.minmax: set10000.minmax + +loop_count: 10000 diff --git a/benchmark/enum_sort.yml b/benchmark/enum_sort.yml new file mode 100644 index 0000000000..6f26e748c6 --- /dev/null +++ b/benchmark/enum_sort.yml @@ -0,0 +1,15 @@ +prelude: | + set2 = 2.times.to_a.shuffle.to_set + set10 = 10.times.to_a.shuffle.to_set + set100 = 100.times.to_a.shuffle.to_set + set1000 = 1000.times.to_a.shuffle.to_set + set10000 = 10000.times.to_a.shuffle.to_set + +benchmark: + set2.sort_by: set2.sort_by { 0 } + set10.sort_by: set10.sort_by { 0 } + set100.sort_by: set100.sort_by { 0 } + set1000.sort_by: set1000.sort_by { 0 } + set10000.sort_by: set10000.sort_by { 0 } + +loop_count: 10000 diff --git a/benchmark/erb_escape_html.yml b/benchmark/erb_escape_html.yml new file mode 100644 index 0000000000..ca28d756e7 --- /dev/null +++ b/benchmark/erb_escape_html.yml @@ -0,0 +1,31 @@ +prelude: | + # frozen_string_literal: true + require 'erb' +benchmark: + - script: ERB::Util.html_escape("") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcde") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcd<") + loop_count: 20000000 + - script: ERB::Util.html_escape("'&\"<>") + loop_count: 5000000 + - prelude: long_no_escape = "abcde" * 300 + script: ERB::Util.html_escape(long_no_escape) + loop_count: 1000000 + - prelude: long_all_escape = "'&\"<>" * 10 + script: ERB::Util.html_escape(long_all_escape) + loop_count: 1000000 + - prelude: | # http://example.com/ + example_html = <<~HTML + <body> + <div> + <h1>Example Domain</h1> + <p>This domain is established to be used for illustrative examples in documents. You may use this + domain in examples without prior coordination or asking for permission.</p> + <p><a href="http://www.iana.org/domains/example">More information...</a></p> + </div> + </body> + HTML + script: ERB::Util.html_escape(example_html) + loop_count: 1000000 diff --git a/benchmark/lib/benchmark_driver/runner/mjit.rb b/benchmark/lib/benchmark_driver/runner/mjit.rb index 1d4693e8be..3a58a620de 100644 --- a/benchmark/lib/benchmark_driver/runner/mjit.rb +++ b/benchmark/lib/benchmark_driver/runner/mjit.rb @@ -16,7 +16,7 @@ class BenchmarkDriver::Runner::Mjit < BenchmarkDriver::Runner::Ips job.prelude = "#{job.prelude}\n#{<<~EOS}" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? __bmdv_ruby_i = 0 - while __bmdv_ruby_i < 10000 # jit_min_calls + while __bmdv_ruby_i < 10000 # MJIT call threshold #{job.script} __bmdv_ruby_i += 1 end diff --git a/benchmark/numeric_methods.yml b/benchmark/numeric_methods.yml index 433c2268a3..1384902935 100644 --- a/benchmark/numeric_methods.yml +++ b/benchmark/numeric_methods.yml @@ -10,4 +10,20 @@ benchmark: int.finite? infinite?: | int.infinite? + integer_real: | + int.real + float_real: | + flo.real + integr_imag: | + int.imag + float_imag: | + flo.imag + integer_conj: | + int.conj + float_conj: | + flo.conj + integer_numerator: | + int.numerator + integer_denominator: | + int.denominator loop_count: 20000000 diff --git a/benchmark/range_min.yml b/benchmark/range_min.yml new file mode 100644 index 0000000000..9e60dd7308 --- /dev/null +++ b/benchmark/range_min.yml @@ -0,0 +1,2 @@ +benchmark: + - (1..10).min 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) @@ -977,7 +977,7 @@ integer_unpack_num_bdigits_small(size_t numwords, size_t wordsize, size_t nails, { /* nlp_bits stands for number of leading padding bits */ size_t num_bits = (wordsize * CHAR_BIT - nails) * numwords; - size_t num_bdigits = (num_bits + BITSPERDIG - 1) / BITSPERDIG; + size_t num_bdigits = roomof(num_bits, BITSPERDIG); *nlp_bits_ret = (int)(num_bdigits * BITSPERDIG - num_bits); return num_bdigits; } @@ -987,7 +987,7 @@ integer_unpack_num_bdigits_generic(size_t numwords, size_t wordsize, size_t nail { /* BITSPERDIG = SIZEOF_BDIGIT * CHAR_BIT */ /* num_bits = (wordsize * CHAR_BIT - nails) * numwords */ - /* num_bdigits = (num_bits + BITSPERDIG - 1) / BITSPERDIG */ + /* num_bdigits = roomof(num_bits, BITSPERDIG) */ /* num_bits = CHAR_BIT * (wordsize * numwords) - nails * numwords = CHAR_BIT * num_bytes1 - nails * numwords */ size_t num_bytes1 = wordsize * numwords; @@ -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/runner.rb b/bootstraptest/runner.rb index 1d219be71e..f9b3e919b8 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -108,10 +108,16 @@ BT = Class.new(bt) do def wn=(wn) unless wn == 1 - if /(?:\A|\s)--jobserver-(?:auth|fds)=\K(\d+),(\d+)/ =~ ENV.delete("MAKEFLAGS") + if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS") begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + if fifo = $3 + fifo.gsub!(/\\(?=.)/, '') + r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) + w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) + else + r = IO.for_fd($1.to_i(10), "rb", autoclose: false) + w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + end rescue => e r.close if r else diff --git a/bootstraptest/test_attr.rb b/bootstraptest/test_attr.rb index 721a847145..3cb9d3eb39 100644 --- a/bootstraptest/test_attr.rb +++ b/bootstraptest/test_attr.rb @@ -34,3 +34,19 @@ assert_equal %{ok}, %{ print "ok" end }, '[ruby-core:15120]' + +assert_equal %{ok}, %{ + class Big + attr_reader :foo + def initialize + @foo = "ok" + end + end + + obj = Big.new + 100.times do |i| + obj.instance_variable_set(:"@ivar_\#{i}", i) + end + + Big.new.foo +} diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index b29db7ab0e..67e66b03ee 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -283,8 +283,9 @@ assert_equal 30.times.map { 'ok' }.to_s, %q{ 30.times.map{|i| test i } -} unless ENV['RUN_OPTS'] =~ /--jit-min-calls=5/ || # This always fails with --jit-wait --jit-min-calls=5 - (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878 +} unless ENV['RUN_OPTS'] =~ /--mjit-call-threshold=5/ || # This always fails with --mjit-wait --mjit-call-threshold=5 + (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') || # https://bugs.ruby-lang.org/issues/17878 + true # too flaky everywhere http://ci.rvm.jp/results/trunk@ruby-sp1/4321096 # Exception for empty select assert_match /specify at least one ractor/, %q{ @@ -501,7 +502,7 @@ assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ rs.delete r n }.sort -} +} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131 # Ractor.select also support multiple take, receive and yield assert_equal '[true, true, true]', %q{ @@ -1472,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 @@ -1481,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}") } } @@ -1579,4 +1580,49 @@ assert_equal "ok", %q{ end } +assert_equal "ok", %q{ + module M + def foo + @foo + end + end + + class A + include M + + def initialize + 100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") } + @foo = 2 + end + end + + class B + include M + + def initialize + @foo = 1 + end + end + + Ractor.new do + b = B.new + 100_000.times do + raise unless b.foo == 1 + end + end + + a = A.new + 100_000.times do + raise unless a.foo == 2 + end + + "ok" +} + +assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ + Warning[:experimental] = $VERBOSE = true + STDERR.reopen(STDOUT) + eval("Ractor.new{}.take", nil, "test_ractor.rb", 1) +} + end # if !ENV['GITHUB_WORKFLOW'] diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 2dff7d591a..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) @@ -45,6 +75,29 @@ assert_normal_exit %q{ Object.send(:remove_const, :Foo) } +assert_normal_exit %q{ + # Test to ensure send on overriden c functions + # doesn't corrupt the stack + class Bar + def bar(x) + x + end + end + + class Foo + def bar + Bar.new + end + end + + foo = Foo.new + # before this change, this line would error + # because "s" would still be on the stack + # String.to_s is the overridden method here + p foo.bar.bar("s".__send__(:to_s)) +} + + assert_equal '[nil, nil, nil, nil, nil, nil]', %q{ [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass| klass.class_eval("def foo = @foo") @@ -198,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 @@ -1118,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 @@ -2150,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{ @@ -2232,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{ @@ -2247,6 +2334,26 @@ 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{ + # enable the event once to make sure invalidation + # happens the second time we enable it + TracePoint.new(:c_call) {}.enable{} + + def compiled + itself + end + + # assume first call compiles + compiled + + events = [] + tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } + tp.enable { compiled } + + events } # test enabling tracing for a suspended fiber @@ -3309,3 +3416,115 @@ assert_equal 'true', %q{ end foo(Foo.new) } + +# bmethod +assert_equal '[1, 2, 3]', %q{ + one = 1 + define_method(:foo) do + one + end + + 3.times.map { |i| foo + i } +} + +# return inside bmethod +assert_equal 'ok', %q{ + define_method(:foo) do + 1.tap { return :ok } + end + + foo +} + +# bmethod optional and keywords +assert_equal '[[1, nil, 2]]', %q{ + define_method(:opt_and_kwargs) do |a = {}, b: nil, c: nil| + [a, b, c] + end + + 5.times.map { opt_and_kwargs(1, c: 2) }.uniq +} + +# bmethod with forwarded block +assert_equal '2', %q{ + define_method(:foo) do |&block| + block.call + end + + def bar(&block) + foo(&block) + end + + bar { 1 } + bar { 2 } +} + +# bmethod with forwarded block and arguments +assert_equal '5', %q{ + define_method(:foo) do |n, &block| + n + block.call + end + + def bar(n, &block) + foo(n, &block) + end + + bar(0) { 1 } + bar(3) { 2 } +} + +# bmethod with forwarded unwanted block +assert_equal '1', %q{ + one = 1 + define_method(:foo) do + one + end + + def bar(&block) + foo(&block) + end + + bar { } + bar { } +} + +# test for return stub lifetime issue +assert_equal '1', %q{ + def foo(n) + if n == 2 + return 1.times { Object.define_method(:foo) {} } + end + + foo(n + 1) + end + + foo(1) +} + +# case-when with redefined === +assert_equal 'ok', %q{ + class Symbol + def ===(a) + true + end + end + + def cw(arg) + case arg + when :b + :ok + when 4 + :ng + end + end + + cw(4) +} + +assert_normal_exit %{ + class Bug20997 + def foo(&) = self.class.name(&) + + new.foo + end +} @@ -13,7 +13,7 @@ struct rb_builtin_function { const char * const name; // for jit - void (*compiler)(FILE *, long, unsigned, bool); + void (*compiler)(VALUE, long, unsigned, bool); }; #define RB_BUILTIN_FUNCTION(_i, _name, _fname, _arity, _compiler) {\ diff --git a/ccan/check_type/check_type.h b/ccan/check_type/check_type.h index e795ad71d0..659e1a5a83 100644 --- a/ccan/check_type/check_type.h +++ b/ccan/check_type/check_type.h @@ -44,7 +44,7 @@ * ((encl_type *) \ * ((char *)(mbr_ptr) - offsetof(enclosing_type, mbr)))) */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_check_type(expr, type) \ ((typeof(expr) *)0 != (type *)0) diff --git a/ccan/container_of/container_of.h b/ccan/container_of/container_of.h index b30c347d57..872bb6ea6e 100644 --- a/ccan/container_of/container_of.h +++ b/ccan/container_of/container_of.h @@ -112,7 +112,7 @@ static inline char *container_of_or_null_(void *member_ptr, size_t offset) * return i; * } */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_container_of_var(member_ptr, container_var, member) \ ccan_container_of(member_ptr, typeof(*container_var), member) #else @@ -131,7 +131,7 @@ static inline char *container_of_or_null_(void *member_ptr, size_t offset) * structure memory layout. * */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_container_off_var(var, member) \ ccan_container_off(typeof(*var), member) #else diff --git a/ccan/list/list.h b/ccan/list/list.h index 91787bfdb3..30b2af04e9 100644 --- a/ccan/list/list.h +++ b/ccan/list/list.h @@ -770,7 +770,7 @@ static inline struct ccan_list_node *ccan_list_node_from_off_(void *ptr, size_t (ccan_container_off_var(var, member) + \ ccan_check_type(var->member, struct ccan_list_node)) -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_list_typeof(var) typeof(var) #else #define ccan_list_typeof(var) void * @@ -64,7 +64,7 @@ push_subclass_entry_to_list(VALUE super, VALUE klass) void rb_class_subclass_add(VALUE super, VALUE klass) { - if (super && super != Qundef) { + if (super && !UNDEF_P(super)) { rb_subclass_entry_t *entry = push_subclass_entry_to_list(super, klass); RCLASS_SUBCLASS_ENTRY(klass) = entry; } @@ -197,7 +197,7 @@ class_alloc(VALUE flags, VALUE klass) { size_t alloc_size = sizeof(struct RClass); -#if USE_RVARGC +#if RCLASS_EXT_EMBEDDED alloc_size += sizeof(rb_classext_t); #endif @@ -206,17 +206,13 @@ class_alloc(VALUE flags, VALUE klass) if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; RVARGC_NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size); -#if USE_RVARGC +#if RCLASS_EXT_EMBEDDED memset(RCLASS_EXT(obj), 0, sizeof(rb_classext_t)); -# if SIZEOF_SERIAL_T != SIZEOF_VALUE - RCLASS(obj)->class_serial_ptr = ZALLOC(rb_serial_t); -# endif #else obj->ptr = ZALLOC(rb_classext_t); #endif /* ZALLOC - RCLASS_IV_TBL(obj) = 0; RCLASS_CONST_TBL(obj) = 0; RCLASS_M_TBL(obj) = 0; RCLASS_IV_INDEX_TBL(obj) = 0; @@ -226,7 +222,6 @@ class_alloc(VALUE flags, VALUE klass) RCLASS_MODULE_SUBCLASSES(obj) = NULL; */ RCLASS_SET_ORIGIN((VALUE)obj, (VALUE)obj); - RCLASS_SERIAL(obj) = rb_next_class_serial(); RB_OBJ_WRITE(obj, &RCLASS_REFINED_CLASS(obj), Qnil); RCLASS_ALLOCATOR(obj) = 0; @@ -282,7 +277,7 @@ rb_class_update_superclasses(VALUE klass) VALUE super = RCLASS_SUPER(klass); if (!RB_TYPE_P(klass, T_CLASS)) return; - if (super == Qundef) return; + if (UNDEF_P(super)) return; // If the superclass array is already built if (RCLASS_SUPERCLASSES(klass)) @@ -331,7 +326,13 @@ rb_class_new(VALUE super) { Check_Type(super, T_CLASS); rb_check_inheritable(super); - return rb_class_boot(super); + VALUE klass = rb_class_boot(super); + + if (super != rb_cObject && super != rb_cBasicObject) { + RCLASS_EXT(klass)->max_iv_count = RCLASS_EXT(super)->max_iv_count; + } + + return klass; } VALUE @@ -403,26 +404,57 @@ 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) { - if (RCLASS_IV_TBL(clone)) { - st_free_table(RCLASS_IV_TBL(clone)); - RCLASS_IV_TBL(clone) = 0; - } if (RCLASS_CONST_TBL(clone)) { 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 (RCLASS_IV_TBL(orig)) { + if (!RB_TYPE_P(clone, T_ICLASS)) { st_data_t id; rb_iv_tbl_copy(clone, orig); CONST_ID(id, "__tmp_classpath__"); - st_delete(RCLASS_IV_TBL(clone), &id, 0); + rb_attr_delete(clone, id); CONST_ID(id, "__classpath__"); - st_delete(RCLASS_IV_TBL(clone), &id, 0); + rb_attr_delete(clone, id); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; @@ -524,7 +556,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) prev_clone_p = clone_p; RCLASS_M_TBL(clone_p) = RCLASS_M_TBL(p); RCLASS_CONST_TBL(clone_p) = RCLASS_CONST_TBL(p); - RCLASS_IV_TBL(clone_p) = RCLASS_IV_TBL(p); RCLASS_ALLOCATOR(clone_p) = RCLASS_ALLOCATOR(p); if (RB_TYPE_P(clone, T_CLASS)) { RCLASS_SET_INCLUDER(clone_p, clone); @@ -611,16 +642,14 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) RCLASS_SET_SUPER(clone, RCLASS_SUPER(klass)); RCLASS_ALLOCATOR(clone) = RCLASS_ALLOCATOR(klass); - if (RCLASS_IV_TBL(klass)) { - rb_iv_tbl_copy(clone, klass); - } + rb_iv_tbl_copy(clone, klass); if (RCLASS_CONST_TBL(klass)) { struct clone_const_arg arg; arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0); arg.klass = clone; rb_id_table_foreach(RCLASS_CONST_TBL(klass), clone_const_i, &arg); } - if (attach != Qundef) { + if (!UNDEF_P(attach)) { rb_singleton_class_attached(clone, attach); } RCLASS_M_TBL_INIT(clone); @@ -929,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; @@ -946,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; } @@ -959,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); @@ -1066,13 +1100,10 @@ rb_include_class_new(VALUE module, VALUE super) module = METACLASS_OF(module); } RUBY_ASSERT(!RB_TYPE_P(module, T_ICLASS)); - if (!RCLASS_IV_TBL(module)) { - RCLASS_IV_TBL(module) = st_init_numtable(); - } if (!RCLASS_CONST_TBL(module)) { RCLASS_CONST_TBL(module) = rb_id_table_create(0); } - RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); + RCLASS_CVC_TBL(klass) = RCLASS_CVC_TBL(module); RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module); @@ -1114,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. */ @@ -1182,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; @@ -1246,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)}; @@ -1257,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 = @@ -1581,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 @@ -1589,6 +1637,33 @@ rb_class_subclasses(VALUE klass) return class_descendants(klass, true); } +/* + * call-seq: + * attached_object -> object + * + * Returns the object for which the receiver is the singleton class. + * + * Raises an TypeError if the class is not a singleton class. + * + * class Foo; end + * + * Foo.singleton_class.attached_object #=> Foo + * Foo.attached_object #=> TypeError: `Foo' is not a singleton class + * Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370> + * TrueClass.attached_object #=> TypeError: `TrueClass' is not a singleton class + * NilClass.attached_object #=> TypeError: `NilClass' is not a singleton class + */ + +VALUE +rb_class_attached_object(VALUE klass) +{ + if (!FL_TEST(klass, FL_SINGLETON)) { + rb_raise(rb_eTypeError, "`%"PRIsVALUE"' is not a singleton class", klass); + } + + return rb_attr_get(klass, id_attached); +} + static void ins_methods_push(st_data_t name, st_data_t ary) { @@ -1639,10 +1714,7 @@ ins_methods_pub_i(st_data_t name, st_data_t type, st_data_t ary) static int ins_methods_undef_i(st_data_t name, st_data_t type, st_data_t ary) { - if ((rb_method_visibility_t)type == METHOD_VISI_UNDEF) { - ins_methods_push(name, ary); - } - return ST_CONTINUE; + return ins_methods_type_i(name, type, ary, METHOD_VISI_UNDEF); } struct method_entry_arg { @@ -2119,9 +2191,7 @@ singleton_class_of(VALUE obj) klass = METACLASS_OF(obj); if (!(FL_TEST(klass, FL_SINGLETON) && rb_attr_get(klass, id_attached) == obj)) { - rb_serial_t serial = RCLASS_SERIAL(klass); klass = rb_make_metaclass(obj, klass); - RCLASS_SERIAL(klass) = serial; } RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); @@ -2135,7 +2205,7 @@ rb_freeze_singleton_class(VALUE x) /* should not propagate to meta-meta-class, and so on */ if (!(RBASIC(x)->flags & FL_SINGLETON)) { VALUE klass = RBASIC_CLASS(x); - if (klass && (klass = RCLASS_ORIGIN(klass)) != 0 && + if (klass && // no class when hidden from ObjectSpace FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) { OBJ_FREEZE_RAW(klass); } @@ -18,7 +18,7 @@ mflags = $(MFLAGS) gnumake_recursive = enable_shared = $(ENABLE_SHARED:no=) -UNICODE_VERSION = 14.0.0 +UNICODE_VERSION = 15.0.0 UNICODE_EMOJI_VERSION_0 = $(UNICODE_VERSION)/// UNICODE_EMOJI_VERSION_1 = $(UNICODE_EMOJI_VERSION_0:.0///=) UNICODE_EMOJI_VERSION = $(UNICODE_EMOJI_VERSION_1:///=) @@ -43,14 +43,14 @@ 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 = GEM_VENDOR = BENCHMARK_DRIVER_GIT_URL = https://github.com/benchmark-driver/benchmark-driver -BENCHMARK_DRIVER_GIT_REF = v0.15.18 +BENCHMARK_DRIVER_GIT_REF = v0.16.0 SIMPLECOV_GIT_URL = https://github.com/colszowka/simplecov.git SIMPLECOV_GIT_REF = v0.17.0 SIMPLECOV_HTML_GIT_URL = https://github.com/colszowka/simplecov-html.git @@ -115,7 +115,7 @@ COMMONOBJS = array.$(OBJEXT) \ math.$(OBJEXT) \ memory_view.$(OBJEXT) \ mjit.$(OBJEXT) \ - mjit_compiler.$(OBJEXT) \ + mjit_c.$(OBJEXT) \ node.$(OBJEXT) \ numeric.$(OBJEXT) \ object.$(OBJEXT) \ @@ -136,6 +136,7 @@ COMMONOBJS = array.$(OBJEXT) \ regsyntax.$(OBJEXT) \ ruby.$(OBJEXT) \ scheduler.$(OBJEXT) \ + shape.$(OBJEXT) \ signal.$(OBJEXT) \ sprintf.$(OBJEXT) \ st.$(OBJEXT) \ @@ -223,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/' \ @@ -233,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) $@ @@ -248,19 +250,7 @@ mjit_config.h: Makefile .PHONY: mjit-bindgen mjit-bindgen: - $(Q)$(XRUBY) -C $(srcdir) -Ilib \ - -e 'ENV["GEM_HOME"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_APP_CONFIG"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_PATH__SYSTEM"] = "true"' \ - -e 'ENV["BUNDLE_WITHOUT"] = "lint doc"' \ - -e 'load "spec/bundler/support/bundle.rb"' -- install --gemfile=tool/mjit/Gemfile - $(Q)$(XRUBY) -C $(srcdir) -Ilib \ - -e 'ENV["GEM_HOME"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_APP_CONFIG"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_GEMFILE"] = "tool/mjit/Gemfile"' \ - -e 'ENV["BUNDLE_PATH__SYSTEM"] = "true"' \ - -e 'ENV["BUNDLE_WITHOUT"] = "lint doc"' \ - -e 'load "spec/bundler/support/bundle.rb"' -- exec tool/mjit/bindgen.rb $(CURDIR) + $(Q) $(BASERUBY) -rrubygems -C $(srcdir)/tool/mjit bindgen.rb $(CURDIR) # These rules using MJIT_HEADER_SUFFIX must be in common.mk, not # Makefile.in, in order to override the macro in defs/universal.mk. @@ -302,6 +292,8 @@ showflags: " LC_ALL = $(LC_ALL)" \ " LC_CTYPE = $(LC_CTYPE)" \ " MFLAGS = $(MFLAGS)" \ + " RUSTC = $(RUSTC)" \ + " YJIT_RUSTC_ARGS = $(YJIT_RUSTC_ARGS)" \ $(MESSAGE_END) -@$(CC_VERSION) @@ -642,12 +634,13 @@ clear-installed-list: PHONY clean: clean-ext clean-enc clean-golf clean-docs clean-extout clean-local clean-platform clean-spec clean-local:: clean-runnable - $(Q)$(RM) $(OBJS) $(MINIOBJS) $(MAINOBJ) $(LIBRUBY_A) $(LIBRUBY_SO) $(LIBRUBY) $(LIBRUBY_ALIASES) + $(Q)$(RM) $(OBJS) $(MINIOBJS) $(INITOBJS) $(MAINOBJ) $(LIBRUBY_A) $(LIBRUBY_SO) $(LIBRUBY) $(LIBRUBY_ALIASES) $(Q)$(RM) $(PROGRAM) $(WPROGRAM) miniruby$(EXEEXT) dmyext.$(OBJEXT) dmyenc.$(OBJEXT) $(ARCHFILE) .*.time $(Q)$(RM) y.tab.c y.output encdb.h transdb.h config.log rbconfig.rb $(ruby_pc) $(COROUTINE_H:/Context.h=/.time) $(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) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine 2> $(NULL) || $(NULLCMD) + -$(Q)$(RMALL) yjit/target + -$(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) @@ -773,7 +766,7 @@ clean-spec: PHONY -$(Q) $(RMDIRS) $(RUBYSPEC_CAPIEXT) 2> $(NULL) || $(NULLCMD) -$(Q) $(RMALL) rubyspec_temp -check: main test test-tool test-all 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)" && \ @@ -797,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 @@ -851,7 +845,7 @@ yes-test-tool: prog PHONY no-test-tool: PHONY test-sample: test-basic # backward compatibility for mswin-build -test-short: btest-ruby test-knownbug test-basic +test-short: btest-ruby $(DOT_WAIT) test-knownbug $(DOT_WAIT) test-basic test: test-short # $ make test-all TESTOPTS="--help" displays more detail @@ -881,7 +875,7 @@ extconf: $(PREP) rbconfig.rb: $(RBCONFIG) $(HAVE_BASERUBY:no=)$(RBCONFIG)$(HAVE_BASERUBY:no=): $(PREP) -$(RBCONFIG): $(tooldir)/mkconfig.rb config.status $(srcdir)/version.h +$(RBCONFIG): $(tooldir)/mkconfig.rb config.status $(srcdir)/version.h $(srcdir)/common.mk $(Q)$(BOOTSTRAPRUBY) -n \ -e 'BEGIN{version=ARGV.shift;mis=ARGV.dup}' \ -e 'END{abort "UNICODE version mismatch: #{mis}" unless mis.empty?}' \ @@ -899,7 +893,7 @@ $(RBCONFIG): $(tooldir)/mkconfig.rb config.status $(srcdir)/version.h test-rubyspec: test-spec yes-test-rubyspec: yes-test-spec -test-spec-precheck: programs +test-spec-precheck: programs yes-fake test-spec: $(TEST_RUNNABLE)-test-spec yes-test-spec: test-spec-precheck @@ -909,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) @@ -953,8 +949,6 @@ PHONY: {$(srcdir)}.y.c: $(ECHO) generating $@ $(Q)$(BASERUBY) $(tooldir)/id2token.rb $(SRC_FILE) > parse.tmp.y - $(Q)$(BASERUBY) $(tooldir)/pure_parser.rb parse.tmp.y $(YACC) - $(Q)$(RM) parse.tmp.y.bak $(Q)$(YACC) -d $(YFLAGS) -o y.tab.c parse.tmp.y $(Q)$(RM) parse.tmp.y $(Q)sed -f $(tooldir)/ytab.sed -e "/^#/s|parse\.tmp\.[iy]|$(SRC_FILE)|" -e "/^#/s!y\.tab\.c!$@!" y.tab.c > $@.new @@ -1084,7 +1078,7 @@ $(srcs_vpath)insns_info.inc: $(tooldir)/ruby_vm/views/insns_info.inc.erb $(inc_c $(srcs_vpath)vmtc.inc: $(tooldir)/ruby_vm/views/vmtc.inc.erb $(inc_common_headers) $(srcs_vpath)vm.inc: $(tooldir)/ruby_vm/views/vm.inc.erb $(inc_common_headers) \ $(tooldir)/ruby_vm/views/_insn_entry.erb $(tooldir)/ruby_vm/views/_trace_instruction.erb -$(srcs_vpath)mjit_compile_attr.inc: $(tooldir)/ruby_vm/views/mjit_compile_attr.inc.erb +$(srcs_vpath)mjit_sp_inc.inc: $(tooldir)/ruby_vm/views/mjit_sp_inc.inc.erb BUILTIN_RB_SRCS = \ $(srcdir)/ast.rb \ @@ -1095,13 +1089,13 @@ BUILTIN_RB_SRCS = \ $(srcdir)/marshal.rb \ $(srcdir)/mjit.rb \ $(srcdir)/mjit_c.rb \ - $(srcdir)/mjit_compiler.rb \ $(srcdir)/pack.rb \ $(srcdir)/trace_point.rb \ $(srcdir)/warning.rb \ $(srcdir)/array.rb \ $(srcdir)/kernel.rb \ $(srcdir)/ractor.rb \ + $(srcdir)/symbol.rb \ $(srcdir)/timev.rb \ $(srcdir)/thread_sync.rb \ $(srcdir)/nilclass.rb \ @@ -1225,22 +1219,19 @@ builtin_binary.inc: $(PREP) $(BUILTIN_RB_SRCS) $(srcdir)/template/builtin_binary $(BUILTIN_RB_INCS): $(top_srcdir)/tool/mk_builtin_loader.rb -$(srcdir)/revision.h: $(REVISION_H) - -revision.$(HAVE_BASERUBY:no=tmp):: - $(Q) $(NULLCMD) > $@ -revision.$(HAVE_BASERUBY:yes=tmp):: $(srcdir)/version.h $(tooldir)/file2lastrev.rb $(REVISION_FORCE) - $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" > $@ +$(srcdir)/revision.h$(no_baseruby:no=~disabled~): $(REVISION_H) -$(REVISION_H): revision.tmp - $(Q)$(IFCHANGE) "--timestamp=$@" "$(srcdir)/revision.h" revision.tmp +$(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) 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 $@ $(Q) $(CHDIR) $(@D) && \ - sed -e "s/{\$$([^(){}]*)[^{}]*}//g" -e /AUTOGENERATED/q depend | \ + $(CAT_DEPEND) depend | \ $(exec) $(MAKE) -f - $(mflags) \ - Q=$(Q) ECHO=$(ECHO) RM="$(RM1)" BISON=$(YACC) top_srcdir=../.. srcdir=. VPATH=../.. \ + Q=$(Q) ECHO=$(ECHO) RM="$(RM1)" BISON="$(YACC)" top_srcdir=../.. srcdir=. VPATH=../.. \ RUBY="$(BASERUBY)" PATH_SEPARATOR="$(PATH_SEPARATOR)" LANG=C $(srcdir)/ext/json/parser/parser.c: $(srcdir)/ext/json/parser/parser.rl $(srcdir)/ext/json/parser/prereq.mk @@ -1257,7 +1248,7 @@ $(srcdir)/ext/rbconfig/sizeof/sizes.c: $(srcdir)/ext/rbconfig/sizeof/depend \ $(tooldir)/generic_erb.rb $(srcdir)/template/sizes.c.tmpl $(srcdir)/configure.ac $(ECHO) generating $@ $(Q) $(CHDIR) $(@D) && \ - sed /AUTOGENERATED/q depend | \ + $(CAT_DEPEND) depend | \ $(exec) $(MAKE) -f - $(mflags) \ Q=$(Q) ECHO=$(ECHO) top_srcdir=../../.. srcdir=. VPATH=../../.. RUBY="$(BASERUBY)" $(@F) @@ -1265,19 +1256,19 @@ $(srcdir)/ext/rbconfig/sizeof/limits.c: $(srcdir)/ext/rbconfig/sizeof/depend \ $(tooldir)/generic_erb.rb $(srcdir)/template/limits.c.tmpl $(ECHO) generating $@ $(Q) $(CHDIR) $(@D) && \ - sed /AUTOGENERATED/q depend | \ + $(CAT_DEPEND) depend | \ $(exec) $(MAKE) -f - $(mflags) \ Q=$(Q) ECHO=$(ECHO) top_srcdir=../../.. srcdir=. VPATH=../../.. RUBY="$(BASERUBY)" $(@F) $(srcdir)/ext/socket/constdefs.c: $(srcdir)/ext/socket/depend $(Q) $(CHDIR) $(@D) && \ - sed /AUTOGENERATED/q depend | \ + $(CAT_DEPEND) depend | \ $(exec) $(MAKE) -f - $(mflags) \ Q=$(Q) ECHO=$(ECHO) top_srcdir=../.. srcdir=. VPATH=../.. RUBY="$(BASERUBY)" $(srcdir)/ext/etc/constdefs.h: $(srcdir)/ext/etc/depend $(Q) $(CHDIR) $(@D) && \ - sed /AUTOGENERATED/q depend | \ + $(CAT_DEPEND) depend | \ $(exec) $(MAKE) -f - $(mflags) \ Q=$(Q) ECHO=$(ECHO) top_srcdir=../.. srcdir=. VPATH=../.. RUBY="$(BASERUBY)" @@ -1367,6 +1358,10 @@ after-update:: $(REVISION_H) after-update:: extract-extlibs after-update:: extract-gems +update-src:: + $(Q) $(RM) $(REVISION_H) revision.h "$(srcdir)/$(REVISION_H)" "$(srcdir)/revision.h" + $(Q) exit > "$(srcdir)/revision.h" + update-remote:: update-src update-download update-download:: $(ALWAYS_UPDATE_UNICODE:yes=update-unicode) update-download:: update-gems @@ -1381,6 +1376,7 @@ update-config_files: PHONY refresh-gems: update-bundled_gems prepare-gems prepare-gems: $(HAVE_BASERUBY:yes=update-gems) $(HAVE_BASERUBY:yes=extract-gems) +extract-gems: $(HAVE_BASERUBY:yes=update-gems) update-gems$(gnumake:yes=-sequential): PHONY $(ECHO) Downloading bundled gem files... @@ -1412,6 +1408,9 @@ extract-gems$(gnumake:yes=-sequential): PHONY -e 'end' \ gems/bundled_gems +outdate-bundled-gems: PHONY + $(Q) $(BASERUBY) $(tooldir)/$@.rb --make="$(MAKE)" --mflags="$(MFLAGS)" "$(srcdir)" + update-bundled_gems: PHONY $(Q) $(RUNRUBY) -rrubygems \ $(tooldir)/update-bundled_gems.rb \ @@ -1438,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 @@ -1457,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) @@ -1466,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 @@ -1584,31 +1587,31 @@ UNICODE_EMOJI_DOWNLOAD = \ -d $(UNICODE_SRC_EMOJI_DATA_DIR) \ -p emoji/$(UNICODE_EMOJI_VERSION) -$(UNICODE_FILES) $(UNICODE_PROPERTY_FILES): update-unicode-files -update-unicode-files: +update-unicode-files: $(UNICODE_FILES) $(UNICODE_PROPERTY_FILES) +$(UNICODE_FILES) $(UNICODE_PROPERTY_FILES): $(ECHO) Downloading Unicode $(UNICODE_VERSION) data and property files... $(Q) $(MAKEDIRS) "$(UNICODE_SRC_DATA_DIR)" $(Q) $(UNICODE_DOWNLOAD) $(UNICODE_FILES) $(UNICODE_PROPERTY_FILES) -$(UNICODE_AUXILIARY_FILES): update-unicode-auxiliary-files -update-unicode-auxiliary-files: +update-unicode-auxiliary-files: $(UNICODE_AUXILIARY_FILES) +$(UNICODE_AUXILIARY_FILES): $(ECHO) Downloading Unicode $(UNICODE_VERSION) auxiliary files... $(Q) $(MAKEDIRS) "$(UNICODE_SRC_DATA_DIR)/auxiliary" $(Q) $(UNICODE_AUXILIARY_DOWNLOAD) $(UNICODE_AUXILIARY_FILES) -$(UNICODE_UCD_EMOJI_FILES): update-unicode-ucd-emoji-files -update-unicode-ucd-emoji-files: +update-unicode-ucd-emoji-files: $(UNICODE_UCD_EMOJI_FILES) +$(UNICODE_UCD_EMOJI_FILES): $(ECHO) Downloading Unicode UCD emoji $(UNICODE_EMOJI_VERSION) files... $(Q) $(MAKEDIRS) "$(UNICODE_SRC_DATA_DIR)/emoji" $(Q) $(UNICODE_UCD_EMOJI_DOWNLOAD) $(UNICODE_UCD_EMOJI_FILES) -$(UNICODE_EMOJI_FILES): update-unicode-emoji-files -update-unicode-emoji-files: +update-unicode-emoji-files: $(UNICODE_EMOJI_FILES) +$(UNICODE_EMOJI_FILES): $(ECHO) Downloading Unicode emoji $(UNICODE_EMOJI_VERSION) files... $(Q) $(MAKEDIRS) "$(UNICODE_SRC_EMOJI_DATA_DIR)" $(Q) $(UNICODE_EMOJI_DOWNLOAD) $(UNICODE_EMOJI_FILES) -$(srcdir)/lib/unicode_normalize/$(ALWAYS_UPDATE_UNICODE:yes=tables.rb): \ +$(srcdir)/lib/unicode_normalize/tables.rb: \ $(UNICODE_SRC_DATA_DIR)/$(HAVE_BASERUBY:yes=.unicode-tables.time) $(UNICODE_SRC_DATA_DIR)/$(ALWAYS_UPDATE_UNICODE:yes=.unicode-tables.time): \ @@ -1617,13 +1620,25 @@ $(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 \ + $(UNICODE_SRC_DATA_DIR)/CompositionExclusions.txt \ + $(empty) +UNICODE_TABLES_DEPENDENTS_1 = none$(ALWAYS_UPDATE_UNICODE) +UNICODE_TABLES_DEPENDENTS = $(UNICODE_TABLES_DEPENDENTS_1:noneyes=force) UNICODE_TABLES_TIMESTAMP = yes -$(UNICODE_SRC_DATA_DIR)/.unicode-tables.time: $(tooldir)/generic_erb.rb \ +$(UNICODE_SRC_DATA_DIR)/.unicode-tables.$(UNICODE_TABLES_DEPENDENTS:none=time): + $(Q) $(MAKEDIRS) $(@D) + $(Q) exit > $(@) || $(NULLCMD) +$(UNICODE_SRC_DATA_DIR)/.unicode-tables.$(UNICODE_TABLES_DEPENDENTS:force=time): \ + $(tooldir)/generic_erb.rb \ $(srcdir)/template/unicode_norm_gen.tmpl \ - $(ALWAYS_UPDATE_UNICODE:yes=update-unicode) - $(Q) $(MAKE) $(@D) + $(UNICODE_TABLES_DATA_FILES) \ + $(order_only) \ + $(UNICODE_SRC_DATA_DIR) $(Q) $(BASERUBY) $(tooldir)/generic_erb.rb \ -c $(UNICODE_TABLES_TIMESTAMP:yes=-t$@) \ -o $(srcdir)/lib/unicode_normalize/tables.rb \ @@ -1817,6 +1832,7 @@ addr2line.$(OBJEXT): {$(VPATH)}internal/xmalloc.h addr2line.$(OBJEXT): {$(VPATH)}missing.h array.$(OBJEXT): $(hdrdir)/ruby/ruby.h array.$(OBJEXT): $(top_srcdir)/internal/array.h +array.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h array.$(OBJEXT): $(top_srcdir)/internal/bignum.h array.$(OBJEXT): $(top_srcdir)/internal/bits.h array.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -1832,6 +1848,7 @@ array.$(OBJEXT): $(top_srcdir)/internal/proc.h array.$(OBJEXT): $(top_srcdir)/internal/rational.h array.$(OBJEXT): $(top_srcdir)/internal/serial.h array.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +array.$(OBJEXT): $(top_srcdir)/internal/variable.h array.$(OBJEXT): $(top_srcdir)/internal/vm.h array.$(OBJEXT): $(top_srcdir)/internal/warnings.h array.$(OBJEXT): {$(VPATH)}array.c @@ -1848,6 +1865,7 @@ array.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h array.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h array.$(OBJEXT): {$(VPATH)}builtin.h array.$(OBJEXT): {$(VPATH)}config.h +array.$(OBJEXT): {$(VPATH)}constant.h array.$(OBJEXT): {$(VPATH)}debug_counter.h array.$(OBJEXT): {$(VPATH)}defines.h array.$(OBJEXT): {$(VPATH)}encoding.h @@ -2010,6 +2028,7 @@ array.$(OBJEXT): {$(VPATH)}oniguruma.h array.$(OBJEXT): {$(VPATH)}probes.dmyh array.$(OBJEXT): {$(VPATH)}probes.h array.$(OBJEXT): {$(VPATH)}ruby_assert.h +array.$(OBJEXT): {$(VPATH)}shape.h array.$(OBJEXT): {$(VPATH)}st.h array.$(OBJEXT): {$(VPATH)}subst.h array.$(OBJEXT): {$(VPATH)}transient_heap.h @@ -2021,6 +2040,7 @@ ast.$(OBJEXT): $(CCAN_DIR)/str/str.h ast.$(OBJEXT): $(hdrdir)/ruby.h ast.$(OBJEXT): $(hdrdir)/ruby/ruby.h ast.$(OBJEXT): $(top_srcdir)/internal/array.h +ast.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ast.$(OBJEXT): $(top_srcdir)/internal/compilers.h ast.$(OBJEXT): $(top_srcdir)/internal/gc.h ast.$(OBJEXT): $(top_srcdir)/internal/imemo.h @@ -2028,6 +2048,7 @@ ast.$(OBJEXT): $(top_srcdir)/internal/parse.h ast.$(OBJEXT): $(top_srcdir)/internal/serial.h ast.$(OBJEXT): $(top_srcdir)/internal/static_assert.h ast.$(OBJEXT): $(top_srcdir)/internal/symbol.h +ast.$(OBJEXT): $(top_srcdir)/internal/variable.h ast.$(OBJEXT): $(top_srcdir)/internal/vm.h ast.$(OBJEXT): $(top_srcdir)/internal/warnings.h ast.$(OBJEXT): {$(VPATH)}assert.h @@ -2045,9 +2066,11 @@ ast.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h ast.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h ast.$(OBJEXT): {$(VPATH)}builtin.h ast.$(OBJEXT): {$(VPATH)}config.h +ast.$(OBJEXT): {$(VPATH)}constant.h ast.$(OBJEXT): {$(VPATH)}defines.h ast.$(OBJEXT): {$(VPATH)}encoding.h ast.$(OBJEXT): {$(VPATH)}id.h +ast.$(OBJEXT): {$(VPATH)}id_table.h ast.$(OBJEXT): {$(VPATH)}intern.h ast.$(OBJEXT): {$(VPATH)}internal.h ast.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -2207,6 +2230,7 @@ ast.$(OBJEXT): {$(VPATH)}onigmo.h ast.$(OBJEXT): {$(VPATH)}oniguruma.h ast.$(OBJEXT): {$(VPATH)}ruby_assert.h ast.$(OBJEXT): {$(VPATH)}ruby_atomic.h +ast.$(OBJEXT): {$(VPATH)}shape.h ast.$(OBJEXT): {$(VPATH)}st.h ast.$(OBJEXT): {$(VPATH)}subst.h ast.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -2390,6 +2414,7 @@ bignum.$(OBJEXT): {$(VPATH)}internal/warning_push.h bignum.$(OBJEXT): {$(VPATH)}internal/xmalloc.h bignum.$(OBJEXT): {$(VPATH)}missing.h bignum.$(OBJEXT): {$(VPATH)}ruby_assert.h +bignum.$(OBJEXT): {$(VPATH)}shape.h bignum.$(OBJEXT): {$(VPATH)}st.h bignum.$(OBJEXT): {$(VPATH)}subst.h bignum.$(OBJEXT): {$(VPATH)}thread.h @@ -2400,11 +2425,13 @@ builtin.$(OBJEXT): $(CCAN_DIR)/list/list.h builtin.$(OBJEXT): $(CCAN_DIR)/str/str.h builtin.$(OBJEXT): $(hdrdir)/ruby/ruby.h builtin.$(OBJEXT): $(top_srcdir)/internal/array.h +builtin.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h builtin.$(OBJEXT): $(top_srcdir)/internal/compilers.h builtin.$(OBJEXT): $(top_srcdir)/internal/gc.h builtin.$(OBJEXT): $(top_srcdir)/internal/imemo.h builtin.$(OBJEXT): $(top_srcdir)/internal/serial.h builtin.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +builtin.$(OBJEXT): $(top_srcdir)/internal/variable.h builtin.$(OBJEXT): $(top_srcdir)/internal/vm.h builtin.$(OBJEXT): $(top_srcdir)/internal/warnings.h builtin.$(OBJEXT): {$(VPATH)}assert.h @@ -2422,8 +2449,10 @@ builtin.$(OBJEXT): {$(VPATH)}builtin.c builtin.$(OBJEXT): {$(VPATH)}builtin.h builtin.$(OBJEXT): {$(VPATH)}builtin_binary.inc builtin.$(OBJEXT): {$(VPATH)}config.h +builtin.$(OBJEXT): {$(VPATH)}constant.h builtin.$(OBJEXT): {$(VPATH)}defines.h builtin.$(OBJEXT): {$(VPATH)}id.h +builtin.$(OBJEXT): {$(VPATH)}id_table.h builtin.$(OBJEXT): {$(VPATH)}intern.h builtin.$(OBJEXT): {$(VPATH)}internal.h builtin.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -2572,6 +2601,7 @@ builtin.$(OBJEXT): {$(VPATH)}missing.h builtin.$(OBJEXT): {$(VPATH)}node.h builtin.$(OBJEXT): {$(VPATH)}ruby_assert.h builtin.$(OBJEXT): {$(VPATH)}ruby_atomic.h +builtin.$(OBJEXT): {$(VPATH)}shape.h builtin.$(OBJEXT): {$(VPATH)}st.h builtin.$(OBJEXT): {$(VPATH)}subst.h builtin.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -2584,6 +2614,7 @@ class.$(OBJEXT): $(CCAN_DIR)/list/list.h class.$(OBJEXT): $(CCAN_DIR)/str/str.h class.$(OBJEXT): $(hdrdir)/ruby/ruby.h class.$(OBJEXT): $(top_srcdir)/internal/array.h +class.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h class.$(OBJEXT): $(top_srcdir)/internal/class.h class.$(OBJEXT): $(top_srcdir)/internal/compilers.h class.$(OBJEXT): $(top_srcdir)/internal/eval.h @@ -2774,6 +2805,7 @@ class.$(OBJEXT): {$(VPATH)}onigmo.h class.$(OBJEXT): {$(VPATH)}oniguruma.h class.$(OBJEXT): {$(VPATH)}ruby_assert.h class.$(OBJEXT): {$(VPATH)}ruby_atomic.h +class.$(OBJEXT): {$(VPATH)}shape.h class.$(OBJEXT): {$(VPATH)}st.h class.$(OBJEXT): {$(VPATH)}subst.h class.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -2781,6 +2813,7 @@ class.$(OBJEXT): {$(VPATH)}thread_native.h class.$(OBJEXT): {$(VPATH)}vm_core.h class.$(OBJEXT): {$(VPATH)}vm_opts.h compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h +compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h compar.$(OBJEXT): $(top_srcdir)/internal/compar.h compar.$(OBJEXT): $(top_srcdir)/internal/compilers.h compar.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -2965,6 +2998,7 @@ compile.$(OBJEXT): $(CCAN_DIR)/list/list.h compile.$(OBJEXT): $(CCAN_DIR)/str/str.h compile.$(OBJEXT): $(hdrdir)/ruby/ruby.h compile.$(OBJEXT): $(top_srcdir)/internal/array.h +compile.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h compile.$(OBJEXT): $(top_srcdir)/internal/bignum.h compile.$(OBJEXT): $(top_srcdir)/internal/bits.h compile.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -3177,6 +3211,7 @@ compile.$(OBJEXT): {$(VPATH)}re.h compile.$(OBJEXT): {$(VPATH)}regex.h compile.$(OBJEXT): {$(VPATH)}ruby_assert.h compile.$(OBJEXT): {$(VPATH)}ruby_atomic.h +compile.$(OBJEXT): {$(VPATH)}shape.h compile.$(OBJEXT): {$(VPATH)}st.h compile.$(OBJEXT): {$(VPATH)}subst.h compile.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -3186,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 @@ -3195,15 +3235,18 @@ 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 complex.$(OBJEXT): $(top_srcdir)/internal/rational.h complex.$(OBJEXT): $(top_srcdir)/internal/serial.h complex.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +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 @@ -3215,6 +3258,7 @@ complex.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h complex.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h complex.$(OBJEXT): {$(VPATH)}complex.c complex.$(OBJEXT): {$(VPATH)}config.h +complex.$(OBJEXT): {$(VPATH)}constant.h complex.$(OBJEXT): {$(VPATH)}defines.h complex.$(OBJEXT): {$(VPATH)}id.h complex.$(OBJEXT): {$(VPATH)}id_table.h @@ -3360,10 +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 @@ -3371,14 +3423,18 @@ cont.$(OBJEXT): $(CCAN_DIR)/str/str.h cont.$(OBJEXT): $(hdrdir)/ruby.h cont.$(OBJEXT): $(hdrdir)/ruby/ruby.h 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 cont.$(OBJEXT): {$(VPATH)}$(COROUTINE_H) @@ -3394,9 +3450,11 @@ cont.$(OBJEXT): {$(VPATH)}backward/2/long_long.h cont.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h cont.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h cont.$(OBJEXT): {$(VPATH)}config.h +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 @@ -3474,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 @@ -3544,14 +3611,18 @@ cont.$(OBJEXT): {$(VPATH)}internal/value_type.h cont.$(OBJEXT): {$(VPATH)}internal/variable.h cont.$(OBJEXT): {$(VPATH)}internal/warning_push.h cont.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +cont.$(OBJEXT): {$(VPATH)}iseq.h 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 cont.$(OBJEXT): {$(VPATH)}ruby_atomic.h +cont.$(OBJEXT): {$(VPATH)}shape.h cont.$(OBJEXT): {$(VPATH)}st.h cont.$(OBJEXT): {$(VPATH)}subst.h cont.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -3559,12 +3630,15 @@ cont.$(OBJEXT): {$(VPATH)}thread_native.h cont.$(OBJEXT): {$(VPATH)}vm_core.h cont.$(OBJEXT): {$(VPATH)}vm_debug.h cont.$(OBJEXT): {$(VPATH)}vm_opts.h +cont.$(OBJEXT): {$(VPATH)}vm_sync.h +cont.$(OBJEXT): {$(VPATH)}yjit.h debug.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h debug.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h debug.$(OBJEXT): $(CCAN_DIR)/list/list.h debug.$(OBJEXT): $(CCAN_DIR)/str/str.h debug.$(OBJEXT): $(hdrdir)/ruby/ruby.h debug.$(OBJEXT): $(top_srcdir)/internal/array.h +debug.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h debug.$(OBJEXT): $(top_srcdir)/internal/class.h debug.$(OBJEXT): $(top_srcdir)/internal/compilers.h debug.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -3572,6 +3646,7 @@ debug.$(OBJEXT): $(top_srcdir)/internal/imemo.h debug.$(OBJEXT): $(top_srcdir)/internal/serial.h debug.$(OBJEXT): $(top_srcdir)/internal/signal.h debug.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +debug.$(OBJEXT): $(top_srcdir)/internal/variable.h debug.$(OBJEXT): $(top_srcdir)/internal/vm.h debug.$(OBJEXT): $(top_srcdir)/internal/warnings.h debug.$(OBJEXT): {$(VPATH)}assert.h @@ -3586,6 +3661,7 @@ debug.$(OBJEXT): {$(VPATH)}backward/2/long_long.h debug.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h debug.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h debug.$(OBJEXT): {$(VPATH)}config.h +debug.$(OBJEXT): {$(VPATH)}constant.h debug.$(OBJEXT): {$(VPATH)}debug.c debug.$(OBJEXT): {$(VPATH)}debug_counter.h debug.$(OBJEXT): {$(VPATH)}defines.h @@ -3756,6 +3832,7 @@ debug.$(OBJEXT): {$(VPATH)}ractor.h debug.$(OBJEXT): {$(VPATH)}ractor_core.h debug.$(OBJEXT): {$(VPATH)}ruby_assert.h debug.$(OBJEXT): {$(VPATH)}ruby_atomic.h +debug.$(OBJEXT): {$(VPATH)}shape.h debug.$(OBJEXT): {$(VPATH)}st.h debug.$(OBJEXT): {$(VPATH)}subst.h debug.$(OBJEXT): {$(VPATH)}symbol.h @@ -3940,6 +4017,7 @@ dir.$(OBJEXT): $(top_srcdir)/internal/object.h dir.$(OBJEXT): $(top_srcdir)/internal/serial.h dir.$(OBJEXT): $(top_srcdir)/internal/static_assert.h dir.$(OBJEXT): $(top_srcdir)/internal/string.h +dir.$(OBJEXT): $(top_srcdir)/internal/variable.h dir.$(OBJEXT): $(top_srcdir)/internal/vm.h dir.$(OBJEXT): $(top_srcdir)/internal/warnings.h dir.$(OBJEXT): {$(VPATH)}assert.h @@ -3954,6 +4032,7 @@ dir.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h dir.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h dir.$(OBJEXT): {$(VPATH)}builtin.h dir.$(OBJEXT): {$(VPATH)}config.h +dir.$(OBJEXT): {$(VPATH)}constant.h dir.$(OBJEXT): {$(VPATH)}defines.h dir.$(OBJEXT): {$(VPATH)}dir.c dir.$(OBJEXT): {$(VPATH)}dir.rbinc @@ -4116,6 +4195,7 @@ dir.$(OBJEXT): {$(VPATH)}io.h dir.$(OBJEXT): {$(VPATH)}missing.h dir.$(OBJEXT): {$(VPATH)}onigmo.h dir.$(OBJEXT): {$(VPATH)}oniguruma.h +dir.$(OBJEXT): {$(VPATH)}shape.h dir.$(OBJEXT): {$(VPATH)}st.h dir.$(OBJEXT): {$(VPATH)}subst.h dir.$(OBJEXT): {$(VPATH)}thread.h @@ -5441,6 +5521,7 @@ encoding.$(OBJEXT): $(top_srcdir)/internal/object.h encoding.$(OBJEXT): $(top_srcdir)/internal/serial.h encoding.$(OBJEXT): $(top_srcdir)/internal/static_assert.h encoding.$(OBJEXT): $(top_srcdir)/internal/string.h +encoding.$(OBJEXT): $(top_srcdir)/internal/variable.h encoding.$(OBJEXT): $(top_srcdir)/internal/vm.h encoding.$(OBJEXT): $(top_srcdir)/internal/warnings.h encoding.$(OBJEXT): {$(VPATH)}assert.h @@ -5454,6 +5535,7 @@ encoding.$(OBJEXT): {$(VPATH)}backward/2/long_long.h encoding.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h encoding.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h encoding.$(OBJEXT): {$(VPATH)}config.h +encoding.$(OBJEXT): {$(VPATH)}constant.h encoding.$(OBJEXT): {$(VPATH)}debug_counter.h encoding.$(OBJEXT): {$(VPATH)}defines.h encoding.$(OBJEXT): {$(VPATH)}encindex.h @@ -5616,6 +5698,7 @@ encoding.$(OBJEXT): {$(VPATH)}onigmo.h encoding.$(OBJEXT): {$(VPATH)}oniguruma.h encoding.$(OBJEXT): {$(VPATH)}regenc.h encoding.$(OBJEXT): {$(VPATH)}ruby_assert.h +encoding.$(OBJEXT): {$(VPATH)}shape.h encoding.$(OBJEXT): {$(VPATH)}st.h encoding.$(OBJEXT): {$(VPATH)}subst.h encoding.$(OBJEXT): {$(VPATH)}util.h @@ -5623,6 +5706,7 @@ encoding.$(OBJEXT): {$(VPATH)}vm_debug.h encoding.$(OBJEXT): {$(VPATH)}vm_sync.h enum.$(OBJEXT): $(hdrdir)/ruby/ruby.h enum.$(OBJEXT): $(top_srcdir)/internal/array.h +enum.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h enum.$(OBJEXT): $(top_srcdir)/internal/bignum.h enum.$(OBJEXT): $(top_srcdir)/internal/bits.h enum.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -5640,6 +5724,7 @@ enum.$(OBJEXT): $(top_srcdir)/internal/rational.h enum.$(OBJEXT): $(top_srcdir)/internal/re.h enum.$(OBJEXT): $(top_srcdir)/internal/serial.h enum.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +enum.$(OBJEXT): $(top_srcdir)/internal/variable.h enum.$(OBJEXT): $(top_srcdir)/internal/vm.h enum.$(OBJEXT): $(top_srcdir)/internal/warnings.h enum.$(OBJEXT): {$(VPATH)}assert.h @@ -5653,6 +5738,7 @@ enum.$(OBJEXT): {$(VPATH)}backward/2/long_long.h enum.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h enum.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h enum.$(OBJEXT): {$(VPATH)}config.h +enum.$(OBJEXT): {$(VPATH)}constant.h enum.$(OBJEXT): {$(VPATH)}defines.h enum.$(OBJEXT): {$(VPATH)}encoding.h enum.$(OBJEXT): {$(VPATH)}enum.c @@ -5813,14 +5899,21 @@ enum.$(OBJEXT): {$(VPATH)}missing.h enum.$(OBJEXT): {$(VPATH)}onigmo.h enum.$(OBJEXT): {$(VPATH)}oniguruma.h enum.$(OBJEXT): {$(VPATH)}ruby_assert.h +enum.$(OBJEXT): {$(VPATH)}shape.h 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 @@ -5838,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 @@ -5852,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 @@ -6003,18 +6098,27 @@ 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 error.$(OBJEXT): $(CCAN_DIR)/str/str.h error.$(OBJEXT): $(hdrdir)/ruby/ruby.h error.$(OBJEXT): $(top_srcdir)/internal/array.h +error.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h error.$(OBJEXT): $(top_srcdir)/internal/class.h error.$(OBJEXT): $(top_srcdir)/internal/compilers.h error.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -6212,6 +6316,7 @@ error.$(OBJEXT): {$(VPATH)}onigmo.h error.$(OBJEXT): {$(VPATH)}oniguruma.h error.$(OBJEXT): {$(VPATH)}ruby_assert.h error.$(OBJEXT): {$(VPATH)}ruby_atomic.h +error.$(OBJEXT): {$(VPATH)}shape.h error.$(OBJEXT): {$(VPATH)}st.h error.$(OBJEXT): {$(VPATH)}subst.h error.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -6226,8 +6331,10 @@ eval.$(OBJEXT): $(CCAN_DIR)/str/str.h eval.$(OBJEXT): $(hdrdir)/ruby.h eval.$(OBJEXT): $(hdrdir)/ruby/ruby.h eval.$(OBJEXT): $(top_srcdir)/internal/array.h +eval.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h eval.$(OBJEXT): $(top_srcdir)/internal/class.h eval.$(OBJEXT): $(top_srcdir)/internal/compilers.h +eval.$(OBJEXT): $(top_srcdir)/internal/cont.h eval.$(OBJEXT): $(top_srcdir)/internal/error.h eval.$(OBJEXT): $(top_srcdir)/internal/eval.h eval.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -6433,6 +6540,7 @@ eval.$(OBJEXT): {$(VPATH)}ractor.h eval.$(OBJEXT): {$(VPATH)}ractor_core.h eval.$(OBJEXT): {$(VPATH)}ruby_assert.h eval.$(OBJEXT): {$(VPATH)}ruby_atomic.h +eval.$(OBJEXT): {$(VPATH)}shape.h eval.$(OBJEXT): {$(VPATH)}st.h eval.$(OBJEXT): {$(VPATH)}subst.h eval.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -6473,6 +6581,7 @@ file.$(OBJEXT): $(top_srcdir)/internal/serial.h file.$(OBJEXT): $(top_srcdir)/internal/static_assert.h file.$(OBJEXT): $(top_srcdir)/internal/string.h file.$(OBJEXT): $(top_srcdir)/internal/thread.h +file.$(OBJEXT): $(top_srcdir)/internal/variable.h file.$(OBJEXT): $(top_srcdir)/internal/vm.h file.$(OBJEXT): $(top_srcdir)/internal/warnings.h file.$(OBJEXT): {$(VPATH)}assert.h @@ -6486,6 +6595,7 @@ file.$(OBJEXT): {$(VPATH)}backward/2/long_long.h file.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h file.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h file.$(OBJEXT): {$(VPATH)}config.h +file.$(OBJEXT): {$(VPATH)}constant.h file.$(OBJEXT): {$(VPATH)}defines.h file.$(OBJEXT): {$(VPATH)}dln.h file.$(OBJEXT): {$(VPATH)}encindex.h @@ -6648,6 +6758,7 @@ file.$(OBJEXT): {$(VPATH)}io.h file.$(OBJEXT): {$(VPATH)}missing.h file.$(OBJEXT): {$(VPATH)}onigmo.h file.$(OBJEXT): {$(VPATH)}oniguruma.h +file.$(OBJEXT): {$(VPATH)}shape.h file.$(OBJEXT): {$(VPATH)}st.h file.$(OBJEXT): {$(VPATH)}subst.h file.$(OBJEXT): {$(VPATH)}thread.h @@ -6659,6 +6770,7 @@ gc.$(OBJEXT): $(CCAN_DIR)/str/str.h gc.$(OBJEXT): $(hdrdir)/ruby.h gc.$(OBJEXT): $(hdrdir)/ruby/ruby.h gc.$(OBJEXT): $(top_srcdir)/internal/array.h +gc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h gc.$(OBJEXT): $(top_srcdir)/internal/bignum.h gc.$(OBJEXT): $(top_srcdir)/internal/bits.h gc.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -6707,7 +6819,6 @@ gc.$(OBJEXT): {$(VPATH)}encoding.h gc.$(OBJEXT): {$(VPATH)}eval_intern.h gc.$(OBJEXT): {$(VPATH)}gc.c gc.$(OBJEXT): {$(VPATH)}gc.h -gc.$(OBJEXT): {$(VPATH)}gc.rb gc.$(OBJEXT): {$(VPATH)}gc.rbinc gc.$(OBJEXT): {$(VPATH)}id.h gc.$(OBJEXT): {$(VPATH)}id_table.h @@ -6864,6 +6975,7 @@ gc.$(OBJEXT): {$(VPATH)}internal/variable.h gc.$(OBJEXT): {$(VPATH)}internal/warning_push.h gc.$(OBJEXT): {$(VPATH)}internal/xmalloc.h gc.$(OBJEXT): {$(VPATH)}io.h +gc.$(OBJEXT): {$(VPATH)}iseq.h gc.$(OBJEXT): {$(VPATH)}method.h gc.$(OBJEXT): {$(VPATH)}missing.h gc.$(OBJEXT): {$(VPATH)}mjit.h @@ -6880,6 +6992,7 @@ gc.$(OBJEXT): {$(VPATH)}regex.h gc.$(OBJEXT): {$(VPATH)}regint.h gc.$(OBJEXT): {$(VPATH)}ruby_assert.h gc.$(OBJEXT): {$(VPATH)}ruby_atomic.h +gc.$(OBJEXT): {$(VPATH)}shape.h gc.$(OBJEXT): {$(VPATH)}st.h gc.$(OBJEXT): {$(VPATH)}subst.h gc.$(OBJEXT): {$(VPATH)}symbol.h @@ -6900,11 +7013,13 @@ goruby.$(OBJEXT): $(CCAN_DIR)/str/str.h goruby.$(OBJEXT): $(hdrdir)/ruby.h goruby.$(OBJEXT): $(hdrdir)/ruby/ruby.h goruby.$(OBJEXT): $(top_srcdir)/internal/array.h +goruby.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h goruby.$(OBJEXT): $(top_srcdir)/internal/compilers.h goruby.$(OBJEXT): $(top_srcdir)/internal/gc.h goruby.$(OBJEXT): $(top_srcdir)/internal/imemo.h goruby.$(OBJEXT): $(top_srcdir)/internal/serial.h goruby.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +goruby.$(OBJEXT): $(top_srcdir)/internal/variable.h goruby.$(OBJEXT): $(top_srcdir)/internal/vm.h goruby.$(OBJEXT): $(top_srcdir)/internal/warnings.h goruby.$(OBJEXT): {$(VPATH)}assert.h @@ -6920,11 +7035,12 @@ goruby.$(OBJEXT): {$(VPATH)}backward/2/long_long.h goruby.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h goruby.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h goruby.$(OBJEXT): {$(VPATH)}config.h +goruby.$(OBJEXT): {$(VPATH)}constant.h goruby.$(OBJEXT): {$(VPATH)}defines.h goruby.$(OBJEXT): {$(VPATH)}golf_prelude.c -goruby.$(OBJEXT): {$(VPATH)}golf_prelude.rb goruby.$(OBJEXT): {$(VPATH)}goruby.c goruby.$(OBJEXT): {$(VPATH)}id.h +goruby.$(OBJEXT): {$(VPATH)}id_table.h goruby.$(OBJEXT): {$(VPATH)}intern.h goruby.$(OBJEXT): {$(VPATH)}internal.h goruby.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -6964,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 @@ -7074,6 +7191,7 @@ goruby.$(OBJEXT): {$(VPATH)}missing.h goruby.$(OBJEXT): {$(VPATH)}node.h goruby.$(OBJEXT): {$(VPATH)}ruby_assert.h goruby.$(OBJEXT): {$(VPATH)}ruby_atomic.h +goruby.$(OBJEXT): {$(VPATH)}shape.h goruby.$(OBJEXT): {$(VPATH)}st.h goruby.$(OBJEXT): {$(VPATH)}subst.h goruby.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -7081,8 +7199,13 @@ goruby.$(OBJEXT): {$(VPATH)}thread_native.h goruby.$(OBJEXT): {$(VPATH)}vm_core.h goruby.$(OBJEXT): {$(VPATH)}vm_debug.h goruby.$(OBJEXT): {$(VPATH)}vm_opts.h +hash.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +hash.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +hash.$(OBJEXT): $(CCAN_DIR)/list/list.h +hash.$(OBJEXT): $(CCAN_DIR)/str/str.h hash.$(OBJEXT): $(hdrdir)/ruby/ruby.h hash.$(OBJEXT): $(top_srcdir)/internal/array.h +hash.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h hash.$(OBJEXT): $(top_srcdir)/internal/bignum.h hash.$(OBJEXT): $(top_srcdir)/internal/bits.h hash.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -7091,6 +7214,7 @@ hash.$(OBJEXT): $(top_srcdir)/internal/cont.h hash.$(OBJEXT): $(top_srcdir)/internal/error.h hash.$(OBJEXT): $(top_srcdir)/internal/gc.h hash.$(OBJEXT): $(top_srcdir)/internal/hash.h +hash.$(OBJEXT): $(top_srcdir)/internal/imemo.h hash.$(OBJEXT): $(top_srcdir)/internal/object.h hash.$(OBJEXT): $(top_srcdir)/internal/proc.h hash.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -7099,9 +7223,11 @@ hash.$(OBJEXT): $(top_srcdir)/internal/string.h hash.$(OBJEXT): $(top_srcdir)/internal/symbol.h hash.$(OBJEXT): $(top_srcdir)/internal/thread.h hash.$(OBJEXT): $(top_srcdir)/internal/time.h +hash.$(OBJEXT): $(top_srcdir)/internal/variable.h hash.$(OBJEXT): $(top_srcdir)/internal/vm.h hash.$(OBJEXT): $(top_srcdir)/internal/warnings.h hash.$(OBJEXT): {$(VPATH)}assert.h +hash.$(OBJEXT): {$(VPATH)}atomic.h hash.$(OBJEXT): {$(VPATH)}backward/2/assume.h hash.$(OBJEXT): {$(VPATH)}backward/2/attributes.h hash.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -7112,6 +7238,7 @@ hash.$(OBJEXT): {$(VPATH)}backward/2/long_long.h hash.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h hash.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h hash.$(OBJEXT): {$(VPATH)}config.h +hash.$(OBJEXT): {$(VPATH)}constant.h hash.$(OBJEXT): {$(VPATH)}debug_counter.h hash.$(OBJEXT): {$(VPATH)}defines.h hash.$(OBJEXT): {$(VPATH)}encoding.h @@ -7269,20 +7396,28 @@ hash.$(OBJEXT): {$(VPATH)}internal/value_type.h hash.$(OBJEXT): {$(VPATH)}internal/variable.h hash.$(OBJEXT): {$(VPATH)}internal/warning_push.h hash.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +hash.$(OBJEXT): {$(VPATH)}iseq.h +hash.$(OBJEXT): {$(VPATH)}method.h hash.$(OBJEXT): {$(VPATH)}missing.h +hash.$(OBJEXT): {$(VPATH)}node.h hash.$(OBJEXT): {$(VPATH)}onigmo.h hash.$(OBJEXT): {$(VPATH)}oniguruma.h hash.$(OBJEXT): {$(VPATH)}probes.dmyh hash.$(OBJEXT): {$(VPATH)}probes.h hash.$(OBJEXT): {$(VPATH)}ractor.h hash.$(OBJEXT): {$(VPATH)}ruby_assert.h +hash.$(OBJEXT): {$(VPATH)}ruby_atomic.h +hash.$(OBJEXT): {$(VPATH)}shape.h hash.$(OBJEXT): {$(VPATH)}st.h hash.$(OBJEXT): {$(VPATH)}subst.h hash.$(OBJEXT): {$(VPATH)}symbol.h +hash.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h hash.$(OBJEXT): {$(VPATH)}thread_native.h hash.$(OBJEXT): {$(VPATH)}transient_heap.h hash.$(OBJEXT): {$(VPATH)}util.h +hash.$(OBJEXT): {$(VPATH)}vm_core.h hash.$(OBJEXT): {$(VPATH)}vm_debug.h +hash.$(OBJEXT): {$(VPATH)}vm_opts.h hash.$(OBJEXT): {$(VPATH)}vm_sync.h inits.$(OBJEXT): $(hdrdir)/ruby.h inits.$(OBJEXT): $(hdrdir)/ruby/ruby.h @@ -7454,6 +7589,7 @@ io.$(OBJEXT): $(CCAN_DIR)/list/list.h io.$(OBJEXT): $(CCAN_DIR)/str/str.h io.$(OBJEXT): $(hdrdir)/ruby/ruby.h io.$(OBJEXT): $(top_srcdir)/internal/array.h +io.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h io.$(OBJEXT): $(top_srcdir)/internal/bignum.h io.$(OBJEXT): $(top_srcdir)/internal/bits.h io.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -7660,6 +7796,7 @@ io.$(OBJEXT): {$(VPATH)}oniguruma.h io.$(OBJEXT): {$(VPATH)}ractor.h io.$(OBJEXT): {$(VPATH)}ruby_assert.h io.$(OBJEXT): {$(VPATH)}ruby_atomic.h +io.$(OBJEXT): {$(VPATH)}shape.h io.$(OBJEXT): {$(VPATH)}st.h io.$(OBJEXT): {$(VPATH)}subst.h io.$(OBJEXT): {$(VPATH)}thread.h @@ -7670,12 +7807,17 @@ io.$(OBJEXT): {$(VPATH)}vm_core.h io.$(OBJEXT): {$(VPATH)}vm_opts.h io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/error.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/fixnum.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/numeric.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/serial.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/static_assert.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/string.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/thread.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/vm.h io_buffer.$(OBJEXT): {$(VPATH)}assert.h io_buffer.$(OBJEXT): {$(VPATH)}backward/2/assume.h io_buffer.$(OBJEXT): {$(VPATH)}backward/2/attributes.h @@ -7856,6 +7998,7 @@ iseq.$(OBJEXT): $(CCAN_DIR)/str/str.h iseq.$(OBJEXT): $(hdrdir)/ruby.h iseq.$(OBJEXT): $(hdrdir)/ruby/ruby.h iseq.$(OBJEXT): $(top_srcdir)/internal/array.h +iseq.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h iseq.$(OBJEXT): $(top_srcdir)/internal/bits.h iseq.$(OBJEXT): $(top_srcdir)/internal/class.h iseq.$(OBJEXT): $(top_srcdir)/internal/compile.h @@ -8062,6 +8205,7 @@ iseq.$(OBJEXT): {$(VPATH)}oniguruma.h iseq.$(OBJEXT): {$(VPATH)}ractor.h iseq.$(OBJEXT): {$(VPATH)}ruby_assert.h iseq.$(OBJEXT): {$(VPATH)}ruby_atomic.h +iseq.$(OBJEXT): {$(VPATH)}shape.h iseq.$(OBJEXT): {$(VPATH)}st.h iseq.$(OBJEXT): {$(VPATH)}subst.h iseq.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -8077,6 +8221,7 @@ load.$(OBJEXT): $(CCAN_DIR)/list/list.h load.$(OBJEXT): $(CCAN_DIR)/str/str.h load.$(OBJEXT): $(hdrdir)/ruby/ruby.h load.$(OBJEXT): $(top_srcdir)/internal/array.h +load.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h load.$(OBJEXT): $(top_srcdir)/internal/compilers.h load.$(OBJEXT): $(top_srcdir)/internal/dir.h load.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -8274,6 +8419,7 @@ load.$(OBJEXT): {$(VPATH)}probes.dmyh load.$(OBJEXT): {$(VPATH)}probes.h load.$(OBJEXT): {$(VPATH)}ruby_assert.h load.$(OBJEXT): {$(VPATH)}ruby_atomic.h +load.$(OBJEXT): {$(VPATH)}shape.h load.$(OBJEXT): {$(VPATH)}st.h load.$(OBJEXT): {$(VPATH)}subst.h load.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -8283,7 +8429,6 @@ load.$(OBJEXT): {$(VPATH)}vm_core.h load.$(OBJEXT): {$(VPATH)}vm_opts.h loadpath.$(OBJEXT): $(hdrdir)/ruby/ruby.h loadpath.$(OBJEXT): $(hdrdir)/ruby/version.h -loadpath.$(OBJEXT): $(top_srcdir)/revision.h loadpath.$(OBJEXT): $(top_srcdir)/version.h loadpath.$(OBJEXT): {$(VPATH)}assert.h loadpath.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -8773,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 @@ -8784,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 @@ -8792,9 +8943,11 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/string.h marshal.$(OBJEXT): $(top_srcdir)/internal/struct.h marshal.$(OBJEXT): $(top_srcdir)/internal/symbol.h marshal.$(OBJEXT): $(top_srcdir)/internal/util.h +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 @@ -8806,9 +8959,11 @@ marshal.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h marshal.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h marshal.$(OBJEXT): {$(VPATH)}builtin.h marshal.$(OBJEXT): {$(VPATH)}config.h +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 @@ -8849,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 @@ -8964,12 +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 @@ -8980,6 +9145,7 @@ math.$(OBJEXT): $(top_srcdir)/internal/math.h math.$(OBJEXT): $(top_srcdir)/internal/object.h math.$(OBJEXT): $(top_srcdir)/internal/serial.h math.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +math.$(OBJEXT): $(top_srcdir)/internal/variable.h math.$(OBJEXT): $(top_srcdir)/internal/vm.h math.$(OBJEXT): $(top_srcdir)/internal/warnings.h math.$(OBJEXT): {$(VPATH)}assert.h @@ -8993,6 +9159,7 @@ math.$(OBJEXT): {$(VPATH)}backward/2/long_long.h math.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h math.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h math.$(OBJEXT): {$(VPATH)}config.h +math.$(OBJEXT): {$(VPATH)}constant.h math.$(OBJEXT): {$(VPATH)}defines.h math.$(OBJEXT): {$(VPATH)}id_table.h math.$(OBJEXT): {$(VPATH)}intern.h @@ -9139,15 +9306,20 @@ math.$(OBJEXT): {$(VPATH)}internal/warning_push.h math.$(OBJEXT): {$(VPATH)}internal/xmalloc.h math.$(OBJEXT): {$(VPATH)}math.c math.$(OBJEXT): {$(VPATH)}missing.h +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 @@ -9303,6 +9475,7 @@ memory_view.$(OBJEXT): {$(VPATH)}internal/xmalloc.h memory_view.$(OBJEXT): {$(VPATH)}memory_view.c memory_view.$(OBJEXT): {$(VPATH)}memory_view.h memory_view.$(OBJEXT): {$(VPATH)}missing.h +memory_view.$(OBJEXT): {$(VPATH)}shape.h memory_view.$(OBJEXT): {$(VPATH)}st.h memory_view.$(OBJEXT): {$(VPATH)}subst.h memory_view.$(OBJEXT): {$(VPATH)}util.h @@ -9315,11 +9488,13 @@ miniinit.$(OBJEXT): $(CCAN_DIR)/str/str.h miniinit.$(OBJEXT): $(hdrdir)/ruby/ruby.h miniinit.$(OBJEXT): $(srcdir)/mjit_c.rb miniinit.$(OBJEXT): $(top_srcdir)/internal/array.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h miniinit.$(OBJEXT): $(top_srcdir)/internal/compilers.h miniinit.$(OBJEXT): $(top_srcdir)/internal/gc.h miniinit.$(OBJEXT): $(top_srcdir)/internal/imemo.h miniinit.$(OBJEXT): $(top_srcdir)/internal/serial.h miniinit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/variable.h miniinit.$(OBJEXT): $(top_srcdir)/internal/vm.h miniinit.$(OBJEXT): $(top_srcdir)/internal/warnings.h miniinit.$(OBJEXT): {$(VPATH)}array.rb @@ -9337,12 +9512,14 @@ miniinit.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h miniinit.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h miniinit.$(OBJEXT): {$(VPATH)}builtin.h miniinit.$(OBJEXT): {$(VPATH)}config.h +miniinit.$(OBJEXT): {$(VPATH)}constant.h miniinit.$(OBJEXT): {$(VPATH)}defines.h miniinit.$(OBJEXT): {$(VPATH)}dir.rb miniinit.$(OBJEXT): {$(VPATH)}encoding.h miniinit.$(OBJEXT): {$(VPATH)}gc.rb miniinit.$(OBJEXT): {$(VPATH)}gem_prelude.rb miniinit.$(OBJEXT): {$(VPATH)}id.h +miniinit.$(OBJEXT): {$(VPATH)}id_table.h miniinit.$(OBJEXT): {$(VPATH)}intern.h miniinit.$(OBJEXT): {$(VPATH)}internal.h miniinit.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -9382,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 @@ -9505,7 +9683,6 @@ miniinit.$(OBJEXT): {$(VPATH)}miniprelude.c miniinit.$(OBJEXT): {$(VPATH)}missing.h miniinit.$(OBJEXT): {$(VPATH)}mjit.rb miniinit.$(OBJEXT): {$(VPATH)}mjit_c.rb -miniinit.$(OBJEXT): {$(VPATH)}mjit_compiler.rb miniinit.$(OBJEXT): {$(VPATH)}nilclass.rb miniinit.$(OBJEXT): {$(VPATH)}node.h miniinit.$(OBJEXT): {$(VPATH)}numeric.rb @@ -9516,8 +9693,10 @@ miniinit.$(OBJEXT): {$(VPATH)}prelude.rb miniinit.$(OBJEXT): {$(VPATH)}ractor.rb miniinit.$(OBJEXT): {$(VPATH)}ruby_assert.h miniinit.$(OBJEXT): {$(VPATH)}ruby_atomic.h +miniinit.$(OBJEXT): {$(VPATH)}shape.h miniinit.$(OBJEXT): {$(VPATH)}st.h miniinit.$(OBJEXT): {$(VPATH)}subst.h +miniinit.$(OBJEXT): {$(VPATH)}symbol.rb miniinit.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h miniinit.$(OBJEXT): {$(VPATH)}thread_native.h miniinit.$(OBJEXT): {$(VPATH)}thread_sync.rb @@ -9535,6 +9714,7 @@ mjit.$(OBJEXT): $(hdrdir)/ruby.h mjit.$(OBJEXT): $(hdrdir)/ruby/ruby.h mjit.$(OBJEXT): $(hdrdir)/ruby/version.h mjit.$(OBJEXT): $(top_srcdir)/internal/array.h +mjit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h mjit.$(OBJEXT): $(top_srcdir)/internal/class.h mjit.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h mjit.$(OBJEXT): $(top_srcdir)/internal/compile.h @@ -9547,6 +9727,7 @@ mjit.$(OBJEXT): $(top_srcdir)/internal/imemo.h mjit.$(OBJEXT): $(top_srcdir)/internal/process.h mjit.$(OBJEXT): $(top_srcdir)/internal/serial.h mjit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +mjit.$(OBJEXT): $(top_srcdir)/internal/variable.h mjit.$(OBJEXT): $(top_srcdir)/internal/vm.h mjit.$(OBJEXT): $(top_srcdir)/internal/warnings.h mjit.$(OBJEXT): {$(VPATH)}assert.h @@ -9730,10 +9911,9 @@ mjit.$(OBJEXT): {$(VPATH)}method.h mjit.$(OBJEXT): {$(VPATH)}missing.h mjit.$(OBJEXT): {$(VPATH)}mjit.c mjit.$(OBJEXT): {$(VPATH)}mjit.h -mjit.$(OBJEXT): {$(VPATH)}mjit.rb mjit.$(OBJEXT): {$(VPATH)}mjit.rbinc +mjit.$(OBJEXT): {$(VPATH)}mjit_c.h mjit.$(OBJEXT): {$(VPATH)}mjit_config.h -mjit.$(OBJEXT): {$(VPATH)}mjit_unit.h mjit.$(OBJEXT): {$(VPATH)}node.h mjit.$(OBJEXT): {$(VPATH)}onigmo.h mjit.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -9741,6 +9921,7 @@ mjit.$(OBJEXT): {$(VPATH)}ractor.h mjit.$(OBJEXT): {$(VPATH)}ractor_core.h mjit.$(OBJEXT): {$(VPATH)}ruby_assert.h mjit.$(OBJEXT): {$(VPATH)}ruby_atomic.h +mjit.$(OBJEXT): {$(VPATH)}shape.h mjit.$(OBJEXT): {$(VPATH)}st.h mjit.$(OBJEXT): {$(VPATH)}subst.h mjit.$(OBJEXT): {$(VPATH)}thread.h @@ -9753,220 +9934,220 @@ mjit.$(OBJEXT): {$(VPATH)}vm_debug.h mjit.$(OBJEXT): {$(VPATH)}vm_opts.h mjit.$(OBJEXT): {$(VPATH)}vm_sync.h mjit.$(OBJEXT): {$(VPATH)}yjit.h -mjit_compiler.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h -mjit_compiler.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h -mjit_compiler.$(OBJEXT): $(CCAN_DIR)/list/list.h -mjit_compiler.$(OBJEXT): $(CCAN_DIR)/str/str.h -mjit_compiler.$(OBJEXT): $(hdrdir)/ruby.h -mjit_compiler.$(OBJEXT): $(hdrdir)/ruby/ruby.h -mjit_compiler.$(OBJEXT): $(srcdir)/mjit_c.rb -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/array.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/class.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/compile.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/compilers.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/gc.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/hash.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/imemo.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/object.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/serial.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/static_assert.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/variable.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/vm.h -mjit_compiler.$(OBJEXT): $(top_srcdir)/internal/warnings.h -mjit_compiler.$(OBJEXT): {$(VPATH)}assert.h -mjit_compiler.$(OBJEXT): {$(VPATH)}atomic.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/assume.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/attributes.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/bool.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/limits.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/long_long.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h -mjit_compiler.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h -mjit_compiler.$(OBJEXT): {$(VPATH)}builtin.h -mjit_compiler.$(OBJEXT): {$(VPATH)}config.h -mjit_compiler.$(OBJEXT): {$(VPATH)}constant.h -mjit_compiler.$(OBJEXT): {$(VPATH)}debug_counter.h -mjit_compiler.$(OBJEXT): {$(VPATH)}defines.h -mjit_compiler.$(OBJEXT): {$(VPATH)}id.h -mjit_compiler.$(OBJEXT): {$(VPATH)}id_table.h -mjit_compiler.$(OBJEXT): {$(VPATH)}insns.def -mjit_compiler.$(OBJEXT): {$(VPATH)}insns.inc -mjit_compiler.$(OBJEXT): {$(VPATH)}insns_info.inc -mjit_compiler.$(OBJEXT): {$(VPATH)}intern.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/abi.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/anyargs.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/assume.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/cold.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/const.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/error.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/format.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/pure.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/warning.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/cast.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/compiler_since.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/config.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/constant_p.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rarray.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rclass.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rdata.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rfile.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rhash.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/robject.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rstring.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/ctype.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/dllexport.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/dosish.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/error.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/eval.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/event.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/fl_type.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/gc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/glob.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/globals.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/attribute.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/builtin.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/extension.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/feature.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/has/warning.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/array.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/class.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/compar.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/complex.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/cont.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/dir.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/enum.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/error.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/eval.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/file.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/gc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/hash.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/io.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/load.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/object.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/parse.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/proc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/process.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/random.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/range.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/rational.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/re.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/select.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/signal.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/string.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/struct.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/thread.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/time.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/variable.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/intern/vm.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/interpreter.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/iterator.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/memory.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/method.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/module.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/newobj.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/rgengc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/scan_args.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/special_consts.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/static_assert.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/stdalign.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/stdbool.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/symbol.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/value.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/value_type.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/variable.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/warning_push.h -mjit_compiler.$(OBJEXT): {$(VPATH)}internal/xmalloc.h -mjit_compiler.$(OBJEXT): {$(VPATH)}iseq.h -mjit_compiler.$(OBJEXT): {$(VPATH)}method.h -mjit_compiler.$(OBJEXT): {$(VPATH)}missing.h -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit.h -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_c.rb -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_c.rbinc -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_compile_attr.inc -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_compiler.c -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_compiler.h -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_compiler.rb -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_compiler.rbinc -mjit_compiler.$(OBJEXT): {$(VPATH)}mjit_unit.h -mjit_compiler.$(OBJEXT): {$(VPATH)}node.h -mjit_compiler.$(OBJEXT): {$(VPATH)}ruby_assert.h -mjit_compiler.$(OBJEXT): {$(VPATH)}ruby_atomic.h -mjit_compiler.$(OBJEXT): {$(VPATH)}st.h -mjit_compiler.$(OBJEXT): {$(VPATH)}subst.h -mjit_compiler.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h -mjit_compiler.$(OBJEXT): {$(VPATH)}thread_native.h -mjit_compiler.$(OBJEXT): {$(VPATH)}vm_callinfo.h -mjit_compiler.$(OBJEXT): {$(VPATH)}vm_core.h -mjit_compiler.$(OBJEXT): {$(VPATH)}vm_exec.h -mjit_compiler.$(OBJEXT): {$(VPATH)}vm_insnhelper.h -mjit_compiler.$(OBJEXT): {$(VPATH)}vm_opts.h -mjit_compiler.$(OBJEXT): {$(VPATH)}yjit.h +mjit_c.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +mjit_c.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +mjit_c.$(OBJEXT): $(CCAN_DIR)/list/list.h +mjit_c.$(OBJEXT): $(CCAN_DIR)/str/str.h +mjit_c.$(OBJEXT): $(hdrdir)/ruby.h +mjit_c.$(OBJEXT): $(hdrdir)/ruby/ruby.h +mjit_c.$(OBJEXT): $(srcdir)/mjit_c.rb +mjit_c.$(OBJEXT): $(top_srcdir)/internal/array.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/class.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/compile.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/compilers.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/gc.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/hash.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/imemo.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/object.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/serial.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/variable.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/vm.h +mjit_c.$(OBJEXT): $(top_srcdir)/internal/warnings.h +mjit_c.$(OBJEXT): {$(VPATH)}assert.h +mjit_c.$(OBJEXT): {$(VPATH)}atomic.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/assume.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/bool.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/limits.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +mjit_c.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +mjit_c.$(OBJEXT): {$(VPATH)}builtin.h +mjit_c.$(OBJEXT): {$(VPATH)}config.h +mjit_c.$(OBJEXT): {$(VPATH)}constant.h +mjit_c.$(OBJEXT): {$(VPATH)}debug_counter.h +mjit_c.$(OBJEXT): {$(VPATH)}defines.h +mjit_c.$(OBJEXT): {$(VPATH)}id.h +mjit_c.$(OBJEXT): {$(VPATH)}id_table.h +mjit_c.$(OBJEXT): {$(VPATH)}insns.def +mjit_c.$(OBJEXT): {$(VPATH)}insns.inc +mjit_c.$(OBJEXT): {$(VPATH)}insns_info.inc +mjit_c.$(OBJEXT): {$(VPATH)}intern.h +mjit_c.$(OBJEXT): {$(VPATH)}internal.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/abi.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/anyargs.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/assume.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/const.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/error.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/format.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/cast.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/config.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/constant_p.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/robject.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/ctype.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/dllexport.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/dosish.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/error.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/eval.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/event.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/fl_type.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/gc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/glob.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/globals.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/extension.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/feature.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/has/warning.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/array.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/class.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/error.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/file.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/gc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/io.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/load.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/object.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/process.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/random.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/range.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/re.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/select.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/string.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/time.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/interpreter.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/iterator.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/memory.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/method.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/module.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/newobj.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/rgengc.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/scan_args.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/special_consts.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/static_assert.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/stdalign.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/stdbool.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/symbol.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/value.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/value_type.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/variable.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/warning_push.h +mjit_c.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +mjit_c.$(OBJEXT): {$(VPATH)}iseq.h +mjit_c.$(OBJEXT): {$(VPATH)}method.h +mjit_c.$(OBJEXT): {$(VPATH)}missing.h +mjit_c.$(OBJEXT): {$(VPATH)}mjit.h +mjit_c.$(OBJEXT): {$(VPATH)}mjit_c.c +mjit_c.$(OBJEXT): {$(VPATH)}mjit_c.h +mjit_c.$(OBJEXT): {$(VPATH)}mjit_c.rb +mjit_c.$(OBJEXT): {$(VPATH)}mjit_c.rbinc +mjit_c.$(OBJEXT): {$(VPATH)}mjit_sp_inc.inc +mjit_c.$(OBJEXT): {$(VPATH)}node.h +mjit_c.$(OBJEXT): {$(VPATH)}ruby_assert.h +mjit_c.$(OBJEXT): {$(VPATH)}ruby_atomic.h +mjit_c.$(OBJEXT): {$(VPATH)}shape.h +mjit_c.$(OBJEXT): {$(VPATH)}st.h +mjit_c.$(OBJEXT): {$(VPATH)}subst.h +mjit_c.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +mjit_c.$(OBJEXT): {$(VPATH)}thread_native.h +mjit_c.$(OBJEXT): {$(VPATH)}vm_callinfo.h +mjit_c.$(OBJEXT): {$(VPATH)}vm_core.h +mjit_c.$(OBJEXT): {$(VPATH)}vm_exec.h +mjit_c.$(OBJEXT): {$(VPATH)}vm_insnhelper.h +mjit_c.$(OBJEXT): {$(VPATH)}vm_opts.h +mjit_c.$(OBJEXT): {$(VPATH)}yjit.h node.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h node.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h node.$(OBJEXT): $(CCAN_DIR)/list/list.h node.$(OBJEXT): $(CCAN_DIR)/str/str.h node.$(OBJEXT): $(hdrdir)/ruby/ruby.h node.$(OBJEXT): $(top_srcdir)/internal/array.h +node.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h node.$(OBJEXT): $(top_srcdir)/internal/compilers.h node.$(OBJEXT): $(top_srcdir)/internal/gc.h node.$(OBJEXT): $(top_srcdir)/internal/hash.h @@ -10140,6 +10321,7 @@ node.$(OBJEXT): {$(VPATH)}node.c node.$(OBJEXT): {$(VPATH)}node.h node.$(OBJEXT): {$(VPATH)}ruby_assert.h node.$(OBJEXT): {$(VPATH)}ruby_atomic.h +node.$(OBJEXT): {$(VPATH)}shape.h node.$(OBJEXT): {$(VPATH)}st.h node.$(OBJEXT): {$(VPATH)}subst.h node.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -10337,16 +10519,21 @@ numeric.$(OBJEXT): {$(VPATH)}internal/warning_push.h numeric.$(OBJEXT): {$(VPATH)}internal/xmalloc.h numeric.$(OBJEXT): {$(VPATH)}missing.h numeric.$(OBJEXT): {$(VPATH)}numeric.c -numeric.$(OBJEXT): {$(VPATH)}numeric.rb numeric.$(OBJEXT): {$(VPATH)}numeric.rbinc numeric.$(OBJEXT): {$(VPATH)}onigmo.h numeric.$(OBJEXT): {$(VPATH)}oniguruma.h numeric.$(OBJEXT): {$(VPATH)}ruby_assert.h +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 @@ -10355,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 @@ -10367,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 @@ -10534,22 +10723,32 @@ object.$(OBJEXT): {$(VPATH)}internal/value_type.h object.$(OBJEXT): {$(VPATH)}internal/variable.h object.$(OBJEXT): {$(VPATH)}internal/warning_push.h object.$(OBJEXT): {$(VPATH)}internal/xmalloc.h -object.$(OBJEXT): {$(VPATH)}kernel.rb 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 @@ -10727,6 +10926,7 @@ pack.$(OBJEXT): {$(VPATH)}onigmo.h pack.$(OBJEXT): {$(VPATH)}oniguruma.h pack.$(OBJEXT): {$(VPATH)}pack.c pack.$(OBJEXT): {$(VPATH)}pack.rbinc +pack.$(OBJEXT): {$(VPATH)}shape.h pack.$(OBJEXT): {$(VPATH)}st.h pack.$(OBJEXT): {$(VPATH)}subst.h pack.$(OBJEXT): {$(VPATH)}util.h @@ -10738,6 +10938,7 @@ parse.$(OBJEXT): $(top_srcdir)/internal/bits.h parse.$(OBJEXT): $(top_srcdir)/internal/compile.h parse.$(OBJEXT): $(top_srcdir)/internal/compilers.h parse.$(OBJEXT): $(top_srcdir)/internal/complex.h +parse.$(OBJEXT): $(top_srcdir)/internal/encoding.h parse.$(OBJEXT): $(top_srcdir)/internal/error.h parse.$(OBJEXT): $(top_srcdir)/internal/fixnum.h parse.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -10939,6 +11140,7 @@ parse.$(OBJEXT): {$(VPATH)}ractor.h parse.$(OBJEXT): {$(VPATH)}regenc.h parse.$(OBJEXT): {$(VPATH)}regex.h parse.$(OBJEXT): {$(VPATH)}ruby_assert.h +parse.$(OBJEXT): {$(VPATH)}shape.h parse.$(OBJEXT): {$(VPATH)}st.h parse.$(OBJEXT): {$(VPATH)}subst.h parse.$(OBJEXT): {$(VPATH)}symbol.h @@ -10949,11 +11151,13 @@ proc.$(OBJEXT): $(CCAN_DIR)/list/list.h proc.$(OBJEXT): $(CCAN_DIR)/str/str.h proc.$(OBJEXT): $(hdrdir)/ruby/ruby.h proc.$(OBJEXT): $(top_srcdir)/internal/array.h +proc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h proc.$(OBJEXT): $(top_srcdir)/internal/class.h 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 @@ -10961,6 +11165,7 @@ proc.$(OBJEXT): $(top_srcdir)/internal/serial.h proc.$(OBJEXT): $(top_srcdir)/internal/static_assert.h proc.$(OBJEXT): $(top_srcdir)/internal/string.h proc.$(OBJEXT): $(top_srcdir)/internal/symbol.h +proc.$(OBJEXT): $(top_srcdir)/internal/variable.h proc.$(OBJEXT): $(top_srcdir)/internal/vm.h proc.$(OBJEXT): $(top_srcdir)/internal/warnings.h proc.$(OBJEXT): {$(VPATH)}assert.h @@ -10975,6 +11180,7 @@ proc.$(OBJEXT): {$(VPATH)}backward/2/long_long.h proc.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h proc.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h proc.$(OBJEXT): {$(VPATH)}config.h +proc.$(OBJEXT): {$(VPATH)}constant.h proc.$(OBJEXT): {$(VPATH)}defines.h proc.$(OBJEXT): {$(VPATH)}encoding.h proc.$(OBJEXT): {$(VPATH)}eval_intern.h @@ -11141,6 +11347,7 @@ proc.$(OBJEXT): {$(VPATH)}oniguruma.h proc.$(OBJEXT): {$(VPATH)}proc.c proc.$(OBJEXT): {$(VPATH)}ruby_assert.h proc.$(OBJEXT): {$(VPATH)}ruby_atomic.h +proc.$(OBJEXT): {$(VPATH)}shape.h proc.$(OBJEXT): {$(VPATH)}st.h proc.$(OBJEXT): {$(VPATH)}subst.h proc.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -11155,6 +11362,7 @@ process.$(OBJEXT): $(CCAN_DIR)/str/str.h process.$(OBJEXT): $(hdrdir)/ruby.h process.$(OBJEXT): $(hdrdir)/ruby/ruby.h process.$(OBJEXT): $(top_srcdir)/internal/array.h +process.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h process.$(OBJEXT): $(top_srcdir)/internal/bignum.h process.$(OBJEXT): $(top_srcdir)/internal/bits.h process.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -11360,6 +11568,7 @@ process.$(OBJEXT): {$(VPATH)}process.c process.$(OBJEXT): {$(VPATH)}ractor.h process.$(OBJEXT): {$(VPATH)}ruby_assert.h process.$(OBJEXT): {$(VPATH)}ruby_atomic.h +process.$(OBJEXT): {$(VPATH)}shape.h process.$(OBJEXT): {$(VPATH)}st.h process.$(OBJEXT): {$(VPATH)}subst.h process.$(OBJEXT): {$(VPATH)}thread.h @@ -11372,8 +11581,10 @@ 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 ractor.$(OBJEXT): $(top_srcdir)/internal/bignum.h ractor.$(OBJEXT): $(top_srcdir)/internal/bits.h ractor.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -11390,6 +11601,7 @@ ractor.$(OBJEXT): $(top_srcdir)/internal/static_assert.h ractor.$(OBJEXT): $(top_srcdir)/internal/string.h ractor.$(OBJEXT): $(top_srcdir)/internal/struct.h ractor.$(OBJEXT): $(top_srcdir)/internal/thread.h +ractor.$(OBJEXT): $(top_srcdir)/internal/variable.h ractor.$(OBJEXT): $(top_srcdir)/internal/vm.h ractor.$(OBJEXT): $(top_srcdir)/internal/warnings.h ractor.$(OBJEXT): {$(VPATH)}assert.h @@ -11405,6 +11617,7 @@ ractor.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h ractor.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h ractor.$(OBJEXT): {$(VPATH)}builtin.h ractor.$(OBJEXT): {$(VPATH)}config.h +ractor.$(OBJEXT): {$(VPATH)}constant.h ractor.$(OBJEXT): {$(VPATH)}debug_counter.h ractor.$(OBJEXT): {$(VPATH)}defines.h ractor.$(OBJEXT): {$(VPATH)}encoding.h @@ -11564,16 +11777,17 @@ 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 ractor.$(OBJEXT): {$(VPATH)}ractor.c ractor.$(OBJEXT): {$(VPATH)}ractor.h -ractor.$(OBJEXT): {$(VPATH)}ractor.rb ractor.$(OBJEXT): {$(VPATH)}ractor.rbinc ractor.$(OBJEXT): {$(VPATH)}ractor_core.h ractor.$(OBJEXT): {$(VPATH)}ruby_assert.h ractor.$(OBJEXT): {$(VPATH)}ruby_atomic.h +ractor.$(OBJEXT): {$(VPATH)}shape.h ractor.$(OBJEXT): {$(VPATH)}st.h ractor.$(OBJEXT): {$(VPATH)}subst.h ractor.$(OBJEXT): {$(VPATH)}thread.h @@ -11592,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 @@ -11763,12 +11978,14 @@ random.$(OBJEXT): {$(VPATH)}ractor.h random.$(OBJEXT): {$(VPATH)}random.c random.$(OBJEXT): {$(VPATH)}random.h random.$(OBJEXT): {$(VPATH)}ruby_atomic.h +random.$(OBJEXT): {$(VPATH)}shape.h random.$(OBJEXT): {$(VPATH)}siphash.c random.$(OBJEXT): {$(VPATH)}siphash.h random.$(OBJEXT): {$(VPATH)}st.h random.$(OBJEXT): {$(VPATH)}subst.h range.$(OBJEXT): $(hdrdir)/ruby/ruby.h range.$(OBJEXT): $(top_srcdir)/internal/array.h +range.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h range.$(OBJEXT): $(top_srcdir)/internal/bignum.h range.$(OBJEXT): $(top_srcdir)/internal/bits.h range.$(OBJEXT): $(top_srcdir)/internal/compar.h @@ -11955,6 +12172,7 @@ range.$(OBJEXT): {$(VPATH)}missing.h range.$(OBJEXT): {$(VPATH)}onigmo.h range.$(OBJEXT): {$(VPATH)}oniguruma.h range.$(OBJEXT): {$(VPATH)}range.c +range.$(OBJEXT): {$(VPATH)}shape.h range.$(OBJEXT): {$(VPATH)}st.h range.$(OBJEXT): {$(VPATH)}subst.h rational.$(OBJEXT): $(hdrdir)/ruby/ruby.h @@ -11971,6 +12189,7 @@ rational.$(OBJEXT): $(top_srcdir)/internal/object.h rational.$(OBJEXT): $(top_srcdir)/internal/rational.h rational.$(OBJEXT): $(top_srcdir)/internal/serial.h rational.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +rational.$(OBJEXT): $(top_srcdir)/internal/variable.h rational.$(OBJEXT): $(top_srcdir)/internal/vm.h rational.$(OBJEXT): $(top_srcdir)/internal/warnings.h rational.$(OBJEXT): {$(VPATH)}assert.h @@ -11984,6 +12203,7 @@ rational.$(OBJEXT): {$(VPATH)}backward/2/long_long.h rational.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h rational.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h rational.$(OBJEXT): {$(VPATH)}config.h +rational.$(OBJEXT): {$(VPATH)}constant.h rational.$(OBJEXT): {$(VPATH)}defines.h rational.$(OBJEXT): {$(VPATH)}id.h rational.$(OBJEXT): {$(VPATH)}id_table.h @@ -12132,6 +12352,7 @@ rational.$(OBJEXT): {$(VPATH)}internal/xmalloc.h rational.$(OBJEXT): {$(VPATH)}missing.h rational.$(OBJEXT): {$(VPATH)}rational.c rational.$(OBJEXT): {$(VPATH)}ruby_assert.h +rational.$(OBJEXT): {$(VPATH)}shape.h rational.$(OBJEXT): {$(VPATH)}st.h rational.$(OBJEXT): {$(VPATH)}subst.h re.$(OBJEXT): $(hdrdir)/ruby.h @@ -12140,6 +12361,7 @@ re.$(OBJEXT): $(top_srcdir)/internal/array.h re.$(OBJEXT): $(top_srcdir)/internal/bits.h re.$(OBJEXT): $(top_srcdir)/internal/class.h re.$(OBJEXT): $(top_srcdir)/internal/compilers.h +re.$(OBJEXT): $(top_srcdir)/internal/encoding.h re.$(OBJEXT): $(top_srcdir)/internal/gc.h re.$(OBJEXT): $(top_srcdir)/internal/hash.h re.$(OBJEXT): $(top_srcdir)/internal/imemo.h @@ -12329,6 +12551,7 @@ re.$(OBJEXT): {$(VPATH)}re.h re.$(OBJEXT): {$(VPATH)}regenc.h re.$(OBJEXT): {$(VPATH)}regex.h re.$(OBJEXT): {$(VPATH)}regint.h +re.$(OBJEXT): {$(VPATH)}shape.h re.$(OBJEXT): {$(VPATH)}st.h re.$(OBJEXT): {$(VPATH)}subst.h re.$(OBJEXT): {$(VPATH)}util.h @@ -13324,9 +13547,11 @@ ruby.$(OBJEXT): $(hdrdir)/ruby.h ruby.$(OBJEXT): $(hdrdir)/ruby/ruby.h ruby.$(OBJEXT): $(hdrdir)/ruby/version.h ruby.$(OBJEXT): $(top_srcdir)/internal/array.h +ruby.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ruby.$(OBJEXT): $(top_srcdir)/internal/class.h ruby.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h ruby.$(OBJEXT): $(top_srcdir)/internal/compilers.h +ruby.$(OBJEXT): $(top_srcdir)/internal/cont.h ruby.$(OBJEXT): $(top_srcdir)/internal/error.h ruby.$(OBJEXT): $(top_srcdir)/internal/file.h ruby.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -13517,6 +13742,7 @@ ruby.$(OBJEXT): {$(VPATH)}internal/variable.h ruby.$(OBJEXT): {$(VPATH)}internal/warning_push.h ruby.$(OBJEXT): {$(VPATH)}internal/xmalloc.h ruby.$(OBJEXT): {$(VPATH)}io.h +ruby.$(OBJEXT): {$(VPATH)}iseq.h ruby.$(OBJEXT): {$(VPATH)}method.h ruby.$(OBJEXT): {$(VPATH)}missing.h ruby.$(OBJEXT): {$(VPATH)}mjit.h @@ -13526,6 +13752,7 @@ ruby.$(OBJEXT): {$(VPATH)}oniguruma.h ruby.$(OBJEXT): {$(VPATH)}ruby.c ruby.$(OBJEXT): {$(VPATH)}ruby_assert.h ruby.$(OBJEXT): {$(VPATH)}ruby_atomic.h +ruby.$(OBJEXT): {$(VPATH)}shape.h ruby.$(OBJEXT): {$(VPATH)}st.h ruby.$(OBJEXT): {$(VPATH)}subst.h ruby.$(OBJEXT): {$(VPATH)}thread.h @@ -13541,12 +13768,14 @@ scheduler.$(OBJEXT): $(CCAN_DIR)/list/list.h scheduler.$(OBJEXT): $(CCAN_DIR)/str/str.h scheduler.$(OBJEXT): $(hdrdir)/ruby/ruby.h scheduler.$(OBJEXT): $(top_srcdir)/internal/array.h +scheduler.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h scheduler.$(OBJEXT): $(top_srcdir)/internal/compilers.h scheduler.$(OBJEXT): $(top_srcdir)/internal/gc.h scheduler.$(OBJEXT): $(top_srcdir)/internal/imemo.h scheduler.$(OBJEXT): $(top_srcdir)/internal/serial.h scheduler.$(OBJEXT): $(top_srcdir)/internal/static_assert.h scheduler.$(OBJEXT): $(top_srcdir)/internal/thread.h +scheduler.$(OBJEXT): $(top_srcdir)/internal/variable.h scheduler.$(OBJEXT): $(top_srcdir)/internal/vm.h scheduler.$(OBJEXT): $(top_srcdir)/internal/warnings.h scheduler.$(OBJEXT): {$(VPATH)}assert.h @@ -13561,10 +13790,12 @@ scheduler.$(OBJEXT): {$(VPATH)}backward/2/long_long.h scheduler.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h scheduler.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h scheduler.$(OBJEXT): {$(VPATH)}config.h +scheduler.$(OBJEXT): {$(VPATH)}constant.h scheduler.$(OBJEXT): {$(VPATH)}defines.h scheduler.$(OBJEXT): {$(VPATH)}encoding.h scheduler.$(OBJEXT): {$(VPATH)}fiber/scheduler.h scheduler.$(OBJEXT): {$(VPATH)}id.h +scheduler.$(OBJEXT): {$(VPATH)}id_table.h scheduler.$(OBJEXT): {$(VPATH)}intern.h scheduler.$(OBJEXT): {$(VPATH)}internal.h scheduler.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -13726,6 +13957,7 @@ scheduler.$(OBJEXT): {$(VPATH)}oniguruma.h scheduler.$(OBJEXT): {$(VPATH)}ruby_assert.h scheduler.$(OBJEXT): {$(VPATH)}ruby_atomic.h scheduler.$(OBJEXT): {$(VPATH)}scheduler.c +scheduler.$(OBJEXT): {$(VPATH)}shape.h scheduler.$(OBJEXT): {$(VPATH)}st.h scheduler.$(OBJEXT): {$(VPATH)}subst.h scheduler.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -13891,12 +14123,219 @@ setproctitle.$(OBJEXT): {$(VPATH)}setproctitle.c setproctitle.$(OBJEXT): {$(VPATH)}st.h setproctitle.$(OBJEXT): {$(VPATH)}subst.h setproctitle.$(OBJEXT): {$(VPATH)}util.h +shape.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +shape.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +shape.$(OBJEXT): $(CCAN_DIR)/list/list.h +shape.$(OBJEXT): $(CCAN_DIR)/str/str.h +shape.$(OBJEXT): $(hdrdir)/ruby/ruby.h +shape.$(OBJEXT): $(top_srcdir)/internal/array.h +shape.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +shape.$(OBJEXT): $(top_srcdir)/internal/class.h +shape.$(OBJEXT): $(top_srcdir)/internal/compilers.h +shape.$(OBJEXT): $(top_srcdir)/internal/gc.h +shape.$(OBJEXT): $(top_srcdir)/internal/imemo.h +shape.$(OBJEXT): $(top_srcdir)/internal/serial.h +shape.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +shape.$(OBJEXT): $(top_srcdir)/internal/symbol.h +shape.$(OBJEXT): $(top_srcdir)/internal/variable.h +shape.$(OBJEXT): $(top_srcdir)/internal/vm.h +shape.$(OBJEXT): $(top_srcdir)/internal/warnings.h +shape.$(OBJEXT): {$(VPATH)}assert.h +shape.$(OBJEXT): {$(VPATH)}atomic.h +shape.$(OBJEXT): {$(VPATH)}backward/2/assume.h +shape.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +shape.$(OBJEXT): {$(VPATH)}backward/2/bool.h +shape.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +shape.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +shape.$(OBJEXT): {$(VPATH)}backward/2/limits.h +shape.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +shape.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +shape.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +shape.$(OBJEXT): {$(VPATH)}config.h +shape.$(OBJEXT): {$(VPATH)}constant.h +shape.$(OBJEXT): {$(VPATH)}debug_counter.h +shape.$(OBJEXT): {$(VPATH)}defines.h +shape.$(OBJEXT): {$(VPATH)}encoding.h +shape.$(OBJEXT): {$(VPATH)}gc.h +shape.$(OBJEXT): {$(VPATH)}id.h +shape.$(OBJEXT): {$(VPATH)}id_table.h +shape.$(OBJEXT): {$(VPATH)}intern.h +shape.$(OBJEXT): {$(VPATH)}internal.h +shape.$(OBJEXT): {$(VPATH)}internal/abi.h +shape.$(OBJEXT): {$(VPATH)}internal/anyargs.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +shape.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +shape.$(OBJEXT): {$(VPATH)}internal/assume.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/const.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/error.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/format.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +shape.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +shape.$(OBJEXT): {$(VPATH)}internal/cast.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +shape.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +shape.$(OBJEXT): {$(VPATH)}internal/config.h +shape.$(OBJEXT): {$(VPATH)}internal/constant_p.h +shape.$(OBJEXT): {$(VPATH)}internal/core.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +shape.$(OBJEXT): {$(VPATH)}internal/core/robject.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +shape.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +shape.$(OBJEXT): {$(VPATH)}internal/ctype.h +shape.$(OBJEXT): {$(VPATH)}internal/dllexport.h +shape.$(OBJEXT): {$(VPATH)}internal/dosish.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/re.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/string.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h +shape.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h +shape.$(OBJEXT): {$(VPATH)}internal/error.h +shape.$(OBJEXT): {$(VPATH)}internal/eval.h +shape.$(OBJEXT): {$(VPATH)}internal/event.h +shape.$(OBJEXT): {$(VPATH)}internal/fl_type.h +shape.$(OBJEXT): {$(VPATH)}internal/gc.h +shape.$(OBJEXT): {$(VPATH)}internal/glob.h +shape.$(OBJEXT): {$(VPATH)}internal/globals.h +shape.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +shape.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +shape.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +shape.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +shape.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +shape.$(OBJEXT): {$(VPATH)}internal/has/extension.h +shape.$(OBJEXT): {$(VPATH)}internal/has/feature.h +shape.$(OBJEXT): {$(VPATH)}internal/has/warning.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/array.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/class.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/error.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/file.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/gc.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/io.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/load.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/object.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/process.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/random.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/range.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/re.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/select.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/string.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/time.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +shape.$(OBJEXT): {$(VPATH)}internal/interpreter.h +shape.$(OBJEXT): {$(VPATH)}internal/iterator.h +shape.$(OBJEXT): {$(VPATH)}internal/memory.h +shape.$(OBJEXT): {$(VPATH)}internal/method.h +shape.$(OBJEXT): {$(VPATH)}internal/module.h +shape.$(OBJEXT): {$(VPATH)}internal/newobj.h +shape.$(OBJEXT): {$(VPATH)}internal/rgengc.h +shape.$(OBJEXT): {$(VPATH)}internal/scan_args.h +shape.$(OBJEXT): {$(VPATH)}internal/special_consts.h +shape.$(OBJEXT): {$(VPATH)}internal/static_assert.h +shape.$(OBJEXT): {$(VPATH)}internal/stdalign.h +shape.$(OBJEXT): {$(VPATH)}internal/stdbool.h +shape.$(OBJEXT): {$(VPATH)}internal/symbol.h +shape.$(OBJEXT): {$(VPATH)}internal/value.h +shape.$(OBJEXT): {$(VPATH)}internal/value_type.h +shape.$(OBJEXT): {$(VPATH)}internal/variable.h +shape.$(OBJEXT): {$(VPATH)}internal/warning_push.h +shape.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +shape.$(OBJEXT): {$(VPATH)}method.h +shape.$(OBJEXT): {$(VPATH)}missing.h +shape.$(OBJEXT): {$(VPATH)}node.h +shape.$(OBJEXT): {$(VPATH)}onigmo.h +shape.$(OBJEXT): {$(VPATH)}oniguruma.h +shape.$(OBJEXT): {$(VPATH)}ruby_assert.h +shape.$(OBJEXT): {$(VPATH)}ruby_atomic.h +shape.$(OBJEXT): {$(VPATH)}shape.c +shape.$(OBJEXT): {$(VPATH)}shape.h +shape.$(OBJEXT): {$(VPATH)}st.h +shape.$(OBJEXT): {$(VPATH)}subst.h +shape.$(OBJEXT): {$(VPATH)}symbol.h +shape.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +shape.$(OBJEXT): {$(VPATH)}thread_native.h +shape.$(OBJEXT): {$(VPATH)}variable.h +shape.$(OBJEXT): {$(VPATH)}vm_core.h +shape.$(OBJEXT): {$(VPATH)}vm_debug.h +shape.$(OBJEXT): {$(VPATH)}vm_opts.h +shape.$(OBJEXT): {$(VPATH)}vm_sync.h signal.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h signal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h signal.$(OBJEXT): $(CCAN_DIR)/list/list.h signal.$(OBJEXT): $(CCAN_DIR)/str/str.h signal.$(OBJEXT): $(hdrdir)/ruby/ruby.h signal.$(OBJEXT): $(top_srcdir)/internal/array.h +signal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h signal.$(OBJEXT): $(top_srcdir)/internal/compilers.h signal.$(OBJEXT): $(top_srcdir)/internal/eval.h signal.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -13907,6 +14346,7 @@ signal.$(OBJEXT): $(top_srcdir)/internal/signal.h signal.$(OBJEXT): $(top_srcdir)/internal/static_assert.h signal.$(OBJEXT): $(top_srcdir)/internal/string.h signal.$(OBJEXT): $(top_srcdir)/internal/thread.h +signal.$(OBJEXT): $(top_srcdir)/internal/variable.h signal.$(OBJEXT): $(top_srcdir)/internal/vm.h signal.$(OBJEXT): $(top_srcdir)/internal/warnings.h signal.$(OBJEXT): {$(VPATH)}assert.h @@ -13921,6 +14361,7 @@ signal.$(OBJEXT): {$(VPATH)}backward/2/long_long.h signal.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h signal.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h signal.$(OBJEXT): {$(VPATH)}config.h +signal.$(OBJEXT): {$(VPATH)}constant.h signal.$(OBJEXT): {$(VPATH)}debug_counter.h signal.$(OBJEXT): {$(VPATH)}defines.h signal.$(OBJEXT): {$(VPATH)}encoding.h @@ -13966,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 @@ -14087,6 +14529,7 @@ signal.$(OBJEXT): {$(VPATH)}ractor.h signal.$(OBJEXT): {$(VPATH)}ractor_core.h signal.$(OBJEXT): {$(VPATH)}ruby_assert.h signal.$(OBJEXT): {$(VPATH)}ruby_atomic.h +signal.$(OBJEXT): {$(VPATH)}shape.h signal.$(OBJEXT): {$(VPATH)}signal.c signal.$(OBJEXT): {$(VPATH)}st.h signal.$(OBJEXT): {$(VPATH)}subst.h @@ -14111,6 +14554,7 @@ sprintf.$(OBJEXT): $(top_srcdir)/internal/serial.h sprintf.$(OBJEXT): $(top_srcdir)/internal/static_assert.h sprintf.$(OBJEXT): $(top_srcdir)/internal/string.h sprintf.$(OBJEXT): $(top_srcdir)/internal/symbol.h +sprintf.$(OBJEXT): $(top_srcdir)/internal/variable.h sprintf.$(OBJEXT): $(top_srcdir)/internal/vm.h sprintf.$(OBJEXT): $(top_srcdir)/internal/warnings.h sprintf.$(OBJEXT): {$(VPATH)}assert.h @@ -14124,6 +14568,7 @@ sprintf.$(OBJEXT): {$(VPATH)}backward/2/long_long.h sprintf.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h sprintf.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h sprintf.$(OBJEXT): {$(VPATH)}config.h +sprintf.$(OBJEXT): {$(VPATH)}constant.h sprintf.$(OBJEXT): {$(VPATH)}defines.h sprintf.$(OBJEXT): {$(VPATH)}encoding.h sprintf.$(OBJEXT): {$(VPATH)}id.h @@ -14285,6 +14730,7 @@ sprintf.$(OBJEXT): {$(VPATH)}onigmo.h sprintf.$(OBJEXT): {$(VPATH)}oniguruma.h sprintf.$(OBJEXT): {$(VPATH)}re.h sprintf.$(OBJEXT): {$(VPATH)}regex.h +sprintf.$(OBJEXT): {$(VPATH)}shape.h sprintf.$(OBJEXT): {$(VPATH)}sprintf.c sprintf.$(OBJEXT): {$(VPATH)}st.h sprintf.$(OBJEXT): {$(VPATH)}subst.h @@ -14452,11 +14898,13 @@ 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 strftime.$(OBJEXT): $(hdrdir)/ruby/ruby.h strftime.$(OBJEXT): $(top_srcdir)/internal/compilers.h +strftime.$(OBJEXT): $(top_srcdir)/internal/encoding.h strftime.$(OBJEXT): $(top_srcdir)/internal/serial.h strftime.$(OBJEXT): $(top_srcdir)/internal/static_assert.h strftime.$(OBJEXT): $(top_srcdir)/internal/string.h @@ -14635,6 +15083,7 @@ strftime.$(OBJEXT): {$(VPATH)}timev.h strftime.$(OBJEXT): {$(VPATH)}util.h string.$(OBJEXT): $(hdrdir)/ruby/ruby.h string.$(OBJEXT): $(top_srcdir)/internal/array.h +string.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h string.$(OBJEXT): $(top_srcdir)/internal/bignum.h string.$(OBJEXT): $(top_srcdir)/internal/bits.h string.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -14653,6 +15102,7 @@ string.$(OBJEXT): $(top_srcdir)/internal/serial.h string.$(OBJEXT): $(top_srcdir)/internal/static_assert.h string.$(OBJEXT): $(top_srcdir)/internal/string.h string.$(OBJEXT): $(top_srcdir)/internal/transcode.h +string.$(OBJEXT): $(top_srcdir)/internal/variable.h string.$(OBJEXT): $(top_srcdir)/internal/vm.h string.$(OBJEXT): $(top_srcdir)/internal/warnings.h string.$(OBJEXT): {$(VPATH)}assert.h @@ -14667,6 +15117,7 @@ string.$(OBJEXT): {$(VPATH)}backward/2/long_long.h string.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h string.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h string.$(OBJEXT): {$(VPATH)}config.h +string.$(OBJEXT): {$(VPATH)}constant.h string.$(OBJEXT): {$(VPATH)}debug_counter.h string.$(OBJEXT): {$(VPATH)}defines.h string.$(OBJEXT): {$(VPATH)}encindex.h @@ -14713,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 @@ -14834,6 +15286,7 @@ string.$(OBJEXT): {$(VPATH)}probes.h string.$(OBJEXT): {$(VPATH)}re.h string.$(OBJEXT): {$(VPATH)}regex.h string.$(OBJEXT): {$(VPATH)}ruby_assert.h +string.$(OBJEXT): {$(VPATH)}shape.h string.$(OBJEXT): {$(VPATH)}st.h string.$(OBJEXT): {$(VPATH)}string.c string.$(OBJEXT): {$(VPATH)}subst.h @@ -14877,6 +15330,7 @@ struct.$(OBJEXT): $(CCAN_DIR)/list/list.h struct.$(OBJEXT): $(CCAN_DIR)/str/str.h struct.$(OBJEXT): $(hdrdir)/ruby/ruby.h struct.$(OBJEXT): $(top_srcdir)/internal/array.h +struct.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h struct.$(OBJEXT): $(top_srcdir)/internal/class.h struct.$(OBJEXT): $(top_srcdir)/internal/compilers.h struct.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -14890,6 +15344,7 @@ struct.$(OBJEXT): $(top_srcdir)/internal/static_assert.h struct.$(OBJEXT): $(top_srcdir)/internal/string.h struct.$(OBJEXT): $(top_srcdir)/internal/struct.h struct.$(OBJEXT): $(top_srcdir)/internal/symbol.h +struct.$(OBJEXT): $(top_srcdir)/internal/variable.h struct.$(OBJEXT): $(top_srcdir)/internal/vm.h struct.$(OBJEXT): $(top_srcdir)/internal/warnings.h struct.$(OBJEXT): {$(VPATH)}assert.h @@ -14905,6 +15360,7 @@ struct.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h struct.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h struct.$(OBJEXT): {$(VPATH)}builtin.h struct.$(OBJEXT): {$(VPATH)}config.h +struct.$(OBJEXT): {$(VPATH)}constant.h struct.$(OBJEXT): {$(VPATH)}defines.h struct.$(OBJEXT): {$(VPATH)}encoding.h struct.$(OBJEXT): {$(VPATH)}id.h @@ -15067,6 +15523,7 @@ struct.$(OBJEXT): {$(VPATH)}onigmo.h struct.$(OBJEXT): {$(VPATH)}oniguruma.h struct.$(OBJEXT): {$(VPATH)}ruby_assert.h struct.$(OBJEXT): {$(VPATH)}ruby_atomic.h +struct.$(OBJEXT): {$(VPATH)}shape.h struct.$(OBJEXT): {$(VPATH)}st.h struct.$(OBJEXT): {$(VPATH)}struct.c struct.$(OBJEXT): {$(VPATH)}subst.h @@ -15086,6 +15543,7 @@ symbol.$(OBJEXT): $(top_srcdir)/internal/serial.h symbol.$(OBJEXT): $(top_srcdir)/internal/static_assert.h symbol.$(OBJEXT): $(top_srcdir)/internal/string.h symbol.$(OBJEXT): $(top_srcdir)/internal/symbol.h +symbol.$(OBJEXT): $(top_srcdir)/internal/variable.h symbol.$(OBJEXT): $(top_srcdir)/internal/vm.h symbol.$(OBJEXT): $(top_srcdir)/internal/warnings.h symbol.$(OBJEXT): {$(VPATH)}assert.h @@ -15098,7 +15556,9 @@ symbol.$(OBJEXT): {$(VPATH)}backward/2/limits.h symbol.$(OBJEXT): {$(VPATH)}backward/2/long_long.h symbol.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h symbol.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +symbol.$(OBJEXT): {$(VPATH)}builtin.h symbol.$(OBJEXT): {$(VPATH)}config.h +symbol.$(OBJEXT): {$(VPATH)}constant.h symbol.$(OBJEXT): {$(VPATH)}debug_counter.h symbol.$(OBJEXT): {$(VPATH)}defines.h symbol.$(OBJEXT): {$(VPATH)}encoding.h @@ -15146,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 @@ -15264,10 +15725,13 @@ symbol.$(OBJEXT): {$(VPATH)}oniguruma.h symbol.$(OBJEXT): {$(VPATH)}probes.dmyh symbol.$(OBJEXT): {$(VPATH)}probes.h symbol.$(OBJEXT): {$(VPATH)}ruby_assert.h +symbol.$(OBJEXT): {$(VPATH)}shape.h symbol.$(OBJEXT): {$(VPATH)}st.h symbol.$(OBJEXT): {$(VPATH)}subst.h symbol.$(OBJEXT): {$(VPATH)}symbol.c symbol.$(OBJEXT): {$(VPATH)}symbol.h +symbol.$(OBJEXT): {$(VPATH)}symbol.rb +symbol.$(OBJEXT): {$(VPATH)}symbol.rbinc symbol.$(OBJEXT): {$(VPATH)}vm_debug.h symbol.$(OBJEXT): {$(VPATH)}vm_sync.h thread.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h @@ -15277,6 +15741,7 @@ thread.$(OBJEXT): $(CCAN_DIR)/str/str.h thread.$(OBJEXT): $(hdrdir)/ruby.h thread.$(OBJEXT): $(hdrdir)/ruby/ruby.h thread.$(OBJEXT): $(top_srcdir)/internal/array.h +thread.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h thread.$(OBJEXT): $(top_srcdir)/internal/bits.h thread.$(OBJEXT): $(top_srcdir)/internal/class.h thread.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -15294,6 +15759,7 @@ thread.$(OBJEXT): $(top_srcdir)/internal/static_assert.h thread.$(OBJEXT): $(top_srcdir)/internal/string.h thread.$(OBJEXT): $(top_srcdir)/internal/thread.h thread.$(OBJEXT): $(top_srcdir)/internal/time.h +thread.$(OBJEXT): $(top_srcdir)/internal/variable.h thread.$(OBJEXT): $(top_srcdir)/internal/vm.h thread.$(OBJEXT): $(top_srcdir)/internal/warnings.h thread.$(OBJEXT): {$(VPATH)}assert.h @@ -15309,6 +15775,7 @@ thread.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h thread.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h thread.$(OBJEXT): {$(VPATH)}builtin.h thread.$(OBJEXT): {$(VPATH)}config.h +thread.$(OBJEXT): {$(VPATH)}constant.h thread.$(OBJEXT): {$(VPATH)}debug.h thread.$(OBJEXT): {$(VPATH)}debug_counter.h thread.$(OBJEXT): {$(VPATH)}defines.h @@ -15482,6 +15949,7 @@ thread.$(OBJEXT): {$(VPATH)}ractor.h thread.$(OBJEXT): {$(VPATH)}ractor_core.h thread.$(OBJEXT): {$(VPATH)}ruby_assert.h thread.$(OBJEXT): {$(VPATH)}ruby_atomic.h +thread.$(OBJEXT): {$(VPATH)}shape.h thread.$(OBJEXT): {$(VPATH)}st.h thread.$(OBJEXT): {$(VPATH)}subst.h thread.$(OBJEXT): {$(VPATH)}thread.c @@ -15490,7 +15958,6 @@ thread.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).c thread.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h thread.$(OBJEXT): {$(VPATH)}thread_native.h thread.$(OBJEXT): {$(VPATH)}thread_sync.c -thread.$(OBJEXT): {$(VPATH)}thread_sync.rb thread.$(OBJEXT): {$(VPATH)}thread_sync.rbinc thread.$(OBJEXT): {$(VPATH)}timev.h thread.$(OBJEXT): {$(VPATH)}vm_core.h @@ -15499,12 +15966,14 @@ thread.$(OBJEXT): {$(VPATH)}vm_opts.h thread.$(OBJEXT): {$(VPATH)}vm_sync.h time.$(OBJEXT): $(hdrdir)/ruby/ruby.h time.$(OBJEXT): $(top_srcdir)/internal/array.h +time.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h time.$(OBJEXT): $(top_srcdir)/internal/bignum.h time.$(OBJEXT): $(top_srcdir)/internal/bits.h time.$(OBJEXT): $(top_srcdir)/internal/compar.h time.$(OBJEXT): $(top_srcdir)/internal/compilers.h time.$(OBJEXT): $(top_srcdir)/internal/fixnum.h time.$(OBJEXT): $(top_srcdir)/internal/gc.h +time.$(OBJEXT): $(top_srcdir)/internal/hash.h time.$(OBJEXT): $(top_srcdir)/internal/numeric.h time.$(OBJEXT): $(top_srcdir)/internal/rational.h time.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -15686,6 +16155,7 @@ time.$(OBJEXT): {$(VPATH)}missing.h time.$(OBJEXT): {$(VPATH)}onigmo.h time.$(OBJEXT): {$(VPATH)}oniguruma.h time.$(OBJEXT): {$(VPATH)}ruby_assert.h +time.$(OBJEXT): {$(VPATH)}shape.h time.$(OBJEXT): {$(VPATH)}st.h time.$(OBJEXT): {$(VPATH)}subst.h time.$(OBJEXT): {$(VPATH)}time.c @@ -15702,6 +16172,7 @@ transcode.$(OBJEXT): $(top_srcdir)/internal/serial.h transcode.$(OBJEXT): $(top_srcdir)/internal/static_assert.h transcode.$(OBJEXT): $(top_srcdir)/internal/string.h transcode.$(OBJEXT): $(top_srcdir)/internal/transcode.h +transcode.$(OBJEXT): $(top_srcdir)/internal/variable.h transcode.$(OBJEXT): $(top_srcdir)/internal/warnings.h transcode.$(OBJEXT): {$(VPATH)}assert.h transcode.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -15714,6 +16185,7 @@ transcode.$(OBJEXT): {$(VPATH)}backward/2/long_long.h transcode.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h transcode.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h transcode.$(OBJEXT): {$(VPATH)}config.h +transcode.$(OBJEXT): {$(VPATH)}constant.h transcode.$(OBJEXT): {$(VPATH)}defines.h transcode.$(OBJEXT): {$(VPATH)}encoding.h transcode.$(OBJEXT): {$(VPATH)}id.h @@ -15872,6 +16344,7 @@ transcode.$(OBJEXT): {$(VPATH)}internal/xmalloc.h transcode.$(OBJEXT): {$(VPATH)}missing.h transcode.$(OBJEXT): {$(VPATH)}onigmo.h transcode.$(OBJEXT): {$(VPATH)}oniguruma.h +transcode.$(OBJEXT): {$(VPATH)}shape.h transcode.$(OBJEXT): {$(VPATH)}st.h transcode.$(OBJEXT): {$(VPATH)}subst.h transcode.$(OBJEXT): {$(VPATH)}transcode.c @@ -16047,6 +16520,7 @@ transient_heap.$(OBJEXT): {$(VPATH)}internal/warning_push.h transient_heap.$(OBJEXT): {$(VPATH)}internal/xmalloc.h transient_heap.$(OBJEXT): {$(VPATH)}missing.h transient_heap.$(OBJEXT): {$(VPATH)}ruby_assert.h +transient_heap.$(OBJEXT): {$(VPATH)}shape.h transient_heap.$(OBJEXT): {$(VPATH)}st.h transient_heap.$(OBJEXT): {$(VPATH)}subst.h transient_heap.$(OBJEXT): {$(VPATH)}transient_heap.c @@ -16226,6 +16700,7 @@ variable.$(OBJEXT): $(CCAN_DIR)/list/list.h variable.$(OBJEXT): $(CCAN_DIR)/str/str.h variable.$(OBJEXT): $(hdrdir)/ruby/ruby.h variable.$(OBJEXT): $(top_srcdir)/internal/array.h +variable.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h variable.$(OBJEXT): $(top_srcdir)/internal/class.h variable.$(OBJEXT): $(top_srcdir)/internal/compilers.h variable.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -16421,6 +16896,7 @@ variable.$(OBJEXT): {$(VPATH)}ractor.h variable.$(OBJEXT): {$(VPATH)}ractor_core.h variable.$(OBJEXT): {$(VPATH)}ruby_assert.h variable.$(OBJEXT): {$(VPATH)}ruby_atomic.h +variable.$(OBJEXT): {$(VPATH)}shape.h variable.$(OBJEXT): {$(VPATH)}st.h variable.$(OBJEXT): {$(VPATH)}subst.h variable.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -16441,14 +16917,16 @@ version.$(OBJEXT): $(hdrdir)/ruby.h version.$(OBJEXT): $(hdrdir)/ruby/ruby.h version.$(OBJEXT): $(hdrdir)/ruby/version.h version.$(OBJEXT): $(top_srcdir)/internal/array.h +version.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +version.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h version.$(OBJEXT): $(top_srcdir)/internal/compilers.h version.$(OBJEXT): $(top_srcdir)/internal/gc.h version.$(OBJEXT): $(top_srcdir)/internal/imemo.h version.$(OBJEXT): $(top_srcdir)/internal/serial.h version.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +version.$(OBJEXT): $(top_srcdir)/internal/variable.h version.$(OBJEXT): $(top_srcdir)/internal/vm.h version.$(OBJEXT): $(top_srcdir)/internal/warnings.h -version.$(OBJEXT): $(top_srcdir)/revision.h version.$(OBJEXT): $(top_srcdir)/version.h version.$(OBJEXT): {$(VPATH)}assert.h version.$(OBJEXT): {$(VPATH)}atomic.h @@ -16462,9 +16940,11 @@ version.$(OBJEXT): {$(VPATH)}backward/2/long_long.h version.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h version.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h version.$(OBJEXT): {$(VPATH)}config.h +version.$(OBJEXT): {$(VPATH)}constant.h version.$(OBJEXT): {$(VPATH)}debug_counter.h version.$(OBJEXT): {$(VPATH)}defines.h version.$(OBJEXT): {$(VPATH)}id.h +version.$(OBJEXT): {$(VPATH)}id_table.h version.$(OBJEXT): {$(VPATH)}intern.h version.$(OBJEXT): {$(VPATH)}internal.h version.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -16611,8 +17091,10 @@ version.$(OBJEXT): {$(VPATH)}method.h version.$(OBJEXT): {$(VPATH)}missing.h version.$(OBJEXT): {$(VPATH)}mjit.h version.$(OBJEXT): {$(VPATH)}node.h +version.$(OBJEXT): {$(VPATH)}revision.h version.$(OBJEXT): {$(VPATH)}ruby_assert.h version.$(OBJEXT): {$(VPATH)}ruby_atomic.h +version.$(OBJEXT): {$(VPATH)}shape.h version.$(OBJEXT): {$(VPATH)}st.h version.$(OBJEXT): {$(VPATH)}subst.h version.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -16628,6 +17110,7 @@ vm.$(OBJEXT): $(CCAN_DIR)/str/str.h vm.$(OBJEXT): $(hdrdir)/ruby.h vm.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm.$(OBJEXT): $(top_srcdir)/internal/array.h +vm.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm.$(OBJEXT): $(top_srcdir)/internal/bignum.h vm.$(OBJEXT): $(top_srcdir)/internal/bits.h vm.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -16848,6 +17331,7 @@ vm.$(OBJEXT): {$(VPATH)}ractor.h vm.$(OBJEXT): {$(VPATH)}ractor_core.h vm.$(OBJEXT): {$(VPATH)}ruby_assert.h vm.$(OBJEXT): {$(VPATH)}ruby_atomic.h +vm.$(OBJEXT): {$(VPATH)}shape.h vm.$(OBJEXT): {$(VPATH)}st.h vm.$(OBJEXT): {$(VPATH)}subst.h vm.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -16877,6 +17361,7 @@ vm_backtrace.$(OBJEXT): $(CCAN_DIR)/list/list.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/array.h +vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/error.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -16884,6 +17369,7 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/imemo.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/string.h +vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_backtrace.$(OBJEXT): {$(VPATH)}assert.h @@ -16898,11 +17384,13 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/long_long.h vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h vm_backtrace.$(OBJEXT): {$(VPATH)}config.h +vm_backtrace.$(OBJEXT): {$(VPATH)}constant.h vm_backtrace.$(OBJEXT): {$(VPATH)}debug.h vm_backtrace.$(OBJEXT): {$(VPATH)}defines.h vm_backtrace.$(OBJEXT): {$(VPATH)}encoding.h vm_backtrace.$(OBJEXT): {$(VPATH)}eval_intern.h vm_backtrace.$(OBJEXT): {$(VPATH)}id.h +vm_backtrace.$(OBJEXT): {$(VPATH)}id_table.h vm_backtrace.$(OBJEXT): {$(VPATH)}intern.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -17062,6 +17550,7 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}onigmo.h vm_backtrace.$(OBJEXT): {$(VPATH)}oniguruma.h vm_backtrace.$(OBJEXT): {$(VPATH)}ruby_assert.h vm_backtrace.$(OBJEXT): {$(VPATH)}ruby_atomic.h +vm_backtrace.$(OBJEXT): {$(VPATH)}shape.h vm_backtrace.$(OBJEXT): {$(VPATH)}st.h vm_backtrace.$(OBJEXT): {$(VPATH)}subst.h vm_backtrace.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -17075,6 +17564,7 @@ vm_dump.$(OBJEXT): $(CCAN_DIR)/list/list.h vm_dump.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_dump.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/array.h +vm_dump.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/imemo.h @@ -17252,6 +17742,7 @@ vm_dump.$(OBJEXT): {$(VPATH)}ractor.h vm_dump.$(OBJEXT): {$(VPATH)}ractor_core.h vm_dump.$(OBJEXT): {$(VPATH)}ruby_assert.h vm_dump.$(OBJEXT): {$(VPATH)}ruby_atomic.h +vm_dump.$(OBJEXT): {$(VPATH)}shape.h vm_dump.$(OBJEXT): {$(VPATH)}st.h vm_dump.$(OBJEXT): {$(VPATH)}subst.h vm_dump.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -17266,11 +17757,13 @@ vm_sync.$(OBJEXT): $(CCAN_DIR)/list/list.h vm_sync.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_sync.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/array.h +vm_sync.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/imemo.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +vm_sync.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_sync.$(OBJEXT): {$(VPATH)}assert.h @@ -17285,6 +17778,7 @@ vm_sync.$(OBJEXT): {$(VPATH)}backward/2/long_long.h vm_sync.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h vm_sync.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h vm_sync.$(OBJEXT): {$(VPATH)}config.h +vm_sync.$(OBJEXT): {$(VPATH)}constant.h vm_sync.$(OBJEXT): {$(VPATH)}debug_counter.h vm_sync.$(OBJEXT): {$(VPATH)}defines.h vm_sync.$(OBJEXT): {$(VPATH)}gc.h @@ -17439,6 +17933,7 @@ vm_sync.$(OBJEXT): {$(VPATH)}ractor.h vm_sync.$(OBJEXT): {$(VPATH)}ractor_core.h vm_sync.$(OBJEXT): {$(VPATH)}ruby_assert.h vm_sync.$(OBJEXT): {$(VPATH)}ruby_atomic.h +vm_sync.$(OBJEXT): {$(VPATH)}shape.h vm_sync.$(OBJEXT): {$(VPATH)}st.h vm_sync.$(OBJEXT): {$(VPATH)}subst.h vm_sync.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -17455,6 +17950,7 @@ vm_trace.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_trace.$(OBJEXT): $(hdrdir)/ruby.h vm_trace.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/array.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/hash.h @@ -17462,6 +17958,7 @@ vm_trace.$(OBJEXT): $(top_srcdir)/internal/imemo.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/symbol.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_trace.$(OBJEXT): {$(VPATH)}assert.h @@ -17477,12 +17974,14 @@ vm_trace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h vm_trace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h vm_trace.$(OBJEXT): {$(VPATH)}builtin.h vm_trace.$(OBJEXT): {$(VPATH)}config.h +vm_trace.$(OBJEXT): {$(VPATH)}constant.h vm_trace.$(OBJEXT): {$(VPATH)}debug.h vm_trace.$(OBJEXT): {$(VPATH)}debug_counter.h vm_trace.$(OBJEXT): {$(VPATH)}defines.h vm_trace.$(OBJEXT): {$(VPATH)}encoding.h vm_trace.$(OBJEXT): {$(VPATH)}eval_intern.h vm_trace.$(OBJEXT): {$(VPATH)}id.h +vm_trace.$(OBJEXT): {$(VPATH)}id_table.h vm_trace.$(OBJEXT): {$(VPATH)}intern.h vm_trace.$(OBJEXT): {$(VPATH)}internal.h vm_trace.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -17644,6 +18143,7 @@ vm_trace.$(OBJEXT): {$(VPATH)}oniguruma.h vm_trace.$(OBJEXT): {$(VPATH)}ractor.h vm_trace.$(OBJEXT): {$(VPATH)}ruby_assert.h vm_trace.$(OBJEXT): {$(VPATH)}ruby_atomic.h +vm_trace.$(OBJEXT): {$(VPATH)}shape.h vm_trace.$(OBJEXT): {$(VPATH)}st.h vm_trace.$(OBJEXT): {$(VPATH)}subst.h vm_trace.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -17659,9 +18159,11 @@ yjit.$(OBJEXT): $(CCAN_DIR)/list/list.h yjit.$(OBJEXT): $(CCAN_DIR)/str/str.h yjit.$(OBJEXT): $(hdrdir)/ruby/ruby.h yjit.$(OBJEXT): $(top_srcdir)/internal/array.h +yjit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h yjit.$(OBJEXT): $(top_srcdir)/internal/class.h yjit.$(OBJEXT): $(top_srcdir)/internal/compile.h yjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h +yjit.$(OBJEXT): $(top_srcdir)/internal/cont.h yjit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h yjit.$(OBJEXT): $(top_srcdir)/internal/gc.h yjit.$(OBJEXT): $(top_srcdir)/internal/hash.h @@ -17859,6 +18361,7 @@ yjit.$(OBJEXT): {$(VPATH)}probes.h yjit.$(OBJEXT): {$(VPATH)}probes_helper.h yjit.$(OBJEXT): {$(VPATH)}ruby_assert.h yjit.$(OBJEXT): {$(VPATH)}ruby_atomic.h +yjit.$(OBJEXT): {$(VPATH)}shape.h yjit.$(OBJEXT): {$(VPATH)}st.h yjit.$(OBJEXT): {$(VPATH)}subst.h yjit.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h @@ -17871,6 +18374,5 @@ yjit.$(OBJEXT): {$(VPATH)}vm_opts.h yjit.$(OBJEXT): {$(VPATH)}vm_sync.h yjit.$(OBJEXT): {$(VPATH)}yjit.c yjit.$(OBJEXT): {$(VPATH)}yjit.h -yjit.$(OBJEXT): {$(VPATH)}yjit.rb yjit.$(OBJEXT): {$(VPATH)}yjit.rbinc # AUTOGENERATED DEPENDENCIES END @@ -50,7 +50,7 @@ VALUE rb_invcmp(VALUE x, VALUE y) { VALUE invcmp = rb_exec_recursive(invcmp_recursive, x, y); - if (invcmp == Qundef || NIL_P(invcmp)) { + if (NIL_OR_UNDEF_P(invcmp)) { return Qnil; } else { @@ -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; } @@ -2058,20 +2087,7 @@ cdhash_set_label_i(VALUE key, VALUE val, VALUE ptr) static inline VALUE get_ivar_ic_value(rb_iseq_t *iseq,ID id) { - VALUE val; - struct rb_id_table *tbl = ISEQ_COMPILE_DATA(iseq)->ivar_cache_table; - if (tbl) { - if (rb_id_table_lookup(tbl,id,&val)) { - return val; - } - } - else { - tbl = rb_id_table_create(1); - ISEQ_COMPILE_DATA(iseq)->ivar_cache_table = tbl; - } - val = INT2FIX(ISEQ_BODY(iseq)->ivc_size++); - rb_id_table_insert(tbl,id,val); - return val; + return INT2FIX(ISEQ_BODY(iseq)->ivc_size++); } static inline VALUE @@ -2472,9 +2488,23 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) generated_iseq[code_index + 1 + j] = (VALUE)ic; } break; + case TS_IVC: /* inline ivar cache */ + { + unsigned int ic_index = FIX2UINT(operands[j]); + + IVC cache = ((IVC)&body->is_entries[ic_index]); + + if (insn == BIN(setinstancevariable)) { + cache->iv_set_name = SYM2ID(operands[j - 1]); + } + else { + cache->iv_set_name = 0; + } + + vm_ic_attr_index_initialize(cache, INVALID_SHAPE_ID); + } case TS_ISE: /* inline storage entry: `once` insn */ case TS_ICVARC: /* inline cvar cache */ - case TS_IVC: /* inline ivar cache */ { unsigned int ic_index = FIX2UINT(operands[j]); IC ic = &ISEQ_IS_ENTRY_START(body, type)[ic_index].ic_cache; @@ -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; @@ -4829,7 +4866,7 @@ when_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, const NODE *val = vals->nd_head; VALUE lit = rb_node_case_when_optimizable_literal(val); - if (lit == Qundef) { + if (UNDEF_P(lit)) { only_special_literals = 0; } else if (NIL_P(rb_hash_lookup(literals, lit))) { @@ -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); @@ -7403,7 +7449,7 @@ compile_loop(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ADD_LABEL(ret, end_label); ADD_ADJUST_RESTORE(ret, adjust_label); - if (node->nd_state == Qundef) { + if (UNDEF_P(node->nd_state)) { /* ADD_INSN(ret, line_node, putundef); */ COMPILE_ERROR(ERROR_ARGS "unsupported: putundef"); return COMPILE_NG; @@ -7458,7 +7504,30 @@ compile_iter(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ISEQ_TYPE_BLOCK, line); CHECK(COMPILE(ret, "iter caller", node->nd_iter)); } - ADD_LABEL(ret, retry_end_l); + + { + // We need to put the label "retry_end_l" immediately after the last "send" instruction. + // This because vm_throw checks if the break cont is equal to the index of next insn of the "send". + // (Otherwise, it is considered "break from proc-closure". See "TAG_BREAK" handling in "vm_throw_start".) + // + // Normally, "send" instruction is at the last. + // However, qcall under branch coverage measurement adds some instructions after the "send". + // + // Note that "invokesuper" appears instead of "send". + INSN *iobj; + LINK_ELEMENT *last_elem = LAST_ELEMENT(ret); + iobj = IS_INSN(last_elem) ? (INSN*) last_elem : (INSN*) get_prev_insn((INSN*) last_elem); + while (INSN_OF(iobj) != BIN(send) && INSN_OF(iobj) != BIN(invokesuper)) { + iobj = (INSN*) get_prev_insn(iobj); + } + ELEM_INSERT_NEXT(&iobj->link, (LINK_ELEMENT*) retry_end_l); + + // LINK_ANCHOR has a pointer to the last element, but ELEM_INSERT_NEXT does not update it + // even if we add an insn to the last of LINK_ANCHOR. So this updates it manually. + if (&iobj->link == LAST_ELEMENT(ret)) { + ret->last = (LINK_ELEMENT*) retry_end_l; + } + } if (popped) { ADD_INSN(ret, line_node, pop); @@ -7588,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); @@ -9253,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 && @@ -9451,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; } @@ -9693,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; @@ -10711,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) { @@ -10737,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; } @@ -11514,6 +11575,21 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod ISE ic = ISEQ_IS_ENTRY_START(load_body, operand_type) + op; code[code_index] = (VALUE)ic; + + if (operand_type == TS_IVC) { + IVC cache = (IVC)ic; + + if (insn == BIN(setinstancevariable)) { + ID iv_name = (ID)code[code_index - 1]; + cache->iv_set_name = iv_name; + } + else { + cache->iv_set_name = 0; + } + + vm_ic_attr_index_initialize(cache, INVALID_SHAPE_ID); + } + } break; case TS_CALLDATA: @@ -12161,6 +12237,40 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) const char catch_except_p = (char)ibf_load_small_value(load, &reading_pos); const bool builtin_inline_p = (bool)ibf_load_small_value(load, &reading_pos); + // setup fname and dummy frame + VALUE path = ibf_load_object(load, location_pathobj_index); + { + VALUE realpath = Qnil; + + if (RB_TYPE_P(path, T_STRING)) { + realpath = path = rb_fstring(path); + } + else if (RB_TYPE_P(path, T_ARRAY)) { + VALUE pathobj = path; + if (RARRAY_LEN(pathobj) != 2) { + rb_raise(rb_eRuntimeError, "path object size mismatch"); + } + path = rb_fstring(RARRAY_AREF(pathobj, 0)); + realpath = RARRAY_AREF(pathobj, 1); + if (!NIL_P(realpath)) { + if (!RB_TYPE_P(realpath, T_STRING)) { + rb_raise(rb_eArgError, "unexpected realpath %"PRIxVALUE + "(%x), path=%+"PRIsVALUE, + realpath, TYPE(realpath), path); + } + realpath = rb_fstring(realpath); + } + } + else { + rb_raise(rb_eRuntimeError, "unexpected path object"); + } + rb_iseq_pathobj_set(iseq, path, realpath); + } + + // push dummy frame + rb_execution_context_t *ec = GET_EC(); + VALUE dummy_frame = rb_vm_push_frame_fname(ec, path); + #undef IBF_BODY_OFFSET load_body->type = type; @@ -12229,33 +12339,6 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load->current_buffer = &load->global_buffer; #endif - { - VALUE realpath = Qnil, path = ibf_load_object(load, location_pathobj_index); - if (RB_TYPE_P(path, T_STRING)) { - realpath = path = rb_fstring(path); - } - else if (RB_TYPE_P(path, T_ARRAY)) { - VALUE pathobj = path; - if (RARRAY_LEN(pathobj) != 2) { - rb_raise(rb_eRuntimeError, "path object size mismatch"); - } - path = rb_fstring(RARRAY_AREF(pathobj, 0)); - realpath = RARRAY_AREF(pathobj, 1); - if (!NIL_P(realpath)) { - if (!RB_TYPE_P(realpath, T_STRING)) { - rb_raise(rb_eArgError, "unexpected realpath %"PRIxVALUE - "(%x), path=%+"PRIsVALUE, - realpath, TYPE(realpath), path); - } - realpath = rb_fstring(realpath); - } - } - else { - rb_raise(rb_eRuntimeError, "unexpected path object"); - } - rb_iseq_pathobj_set(iseq, path, realpath); - } - RB_OBJ_WRITE(iseq, &load_body->location.base_label, ibf_load_location_str(load, location_base_label_index)); RB_OBJ_WRITE(iseq, &load_body->location.label, ibf_load_location_str(load, location_label_index)); @@ -12263,6 +12346,9 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load->current_buffer = saved_buffer; #endif verify_call_cache(iseq); + + RB_GC_GUARD(dummy_frame); + rb_vm_pop_frame_no_int(ec); } struct ibf_dump_iseq_list_arg @@ -13013,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 @@ -421,15 +421,22 @@ f_complex_new_bang2(VALUE klass, VALUE x, VALUE y) return nucomp_s_new_internal(klass, x, y); } -inline static void +WARN_UNUSED_RESULT(inline static VALUE nucomp_real_check(VALUE num)); +inline static VALUE nucomp_real_check(VALUE num) { if (!RB_INTEGER_TYPE_P(num) && !RB_FLOAT_TYPE_P(num) && !RB_TYPE_P(num, T_RATIONAL)) { + if (RB_TYPE_P(num, T_COMPLEX) && nucomp_real_p(num)) { + VALUE real = RCOMPLEX(num)->real; + assert(!RB_TYPE_P(real, T_COMPLEX)); + return real; + } if (!k_numeric_p(num) || !f_real_p(num)) rb_raise(rb_eTypeError, "not a real"); } + return num; } inline static VALUE @@ -480,16 +487,16 @@ nucomp_s_new(int argc, VALUE *argv, VALUE klass) switch (rb_scan_args(argc, argv, "11", &real, &imag)) { case 1: - nucomp_real_check(real); + real = nucomp_real_check(real); imag = ZERO; break; default: - nucomp_real_check(real); - nucomp_real_check(imag); + real = nucomp_real_check(real); + imag = nucomp_real_check(imag); break; } - return nucomp_s_canonicalize_internal(klass, real, imag); + return nucomp_s_new_internal(klass, real, imag); } inline static VALUE @@ -554,7 +561,7 @@ nucomp_f_complex(int argc, VALUE *argv, VALUE klass) if (!NIL_P(opts)) { raise = rb_opts_exception_p(opts, raise); } - if (argc > 0 && CLASS_OF(a1) == rb_cComplex && a2 == Qundef) { + if (argc > 0 && CLASS_OF(a1) == rb_cComplex && UNDEF_P(a2)) { return a1; } return nucomp_convert(rb_cComplex, a1, a2, raise); @@ -611,16 +618,8 @@ m_sin(VALUE x) } static VALUE -f_complex_polar(VALUE klass, VALUE x, VALUE y) +f_complex_polar_real(VALUE klass, VALUE x, VALUE y) { - if (RB_TYPE_P(x, T_COMPLEX)) { - get_dat1(x); - x = dat->real; - } - if (RB_TYPE_P(y, T_COMPLEX)) { - get_dat1(y); - y = dat->real; - } if (f_zero_p(x) || f_zero_p(y)) { return nucomp_s_new_internal(klass, x, RFLOAT_0); } @@ -656,6 +655,14 @@ f_complex_polar(VALUE klass, VALUE x, VALUE y) f_mul(x, m_sin(y))); } +static VALUE +f_complex_polar(VALUE klass, VALUE x, VALUE y) +{ + x = nucomp_real_check(x); + y = nucomp_real_check(y); + return f_complex_polar_real(klass, x, y); +} + #ifdef HAVE___COSPI # define cospi(x) __cospi(x) #else @@ -704,16 +711,15 @@ nucomp_s_polar(int argc, VALUE *argv, VALUE klass) { VALUE abs, arg; - switch (rb_scan_args(argc, argv, "11", &abs, &arg)) { - case 1: - nucomp_real_check(abs); - return nucomp_s_new_internal(klass, abs, ZERO); - default: - nucomp_real_check(abs); - nucomp_real_check(arg); - break; + argc = rb_scan_args(argc, argv, "11", &abs, &arg); + abs = nucomp_real_check(abs); + if (argc == 2) { + arg = nucomp_real_check(arg); + } + else { + arg = ZERO; } - return f_complex_polar(klass, abs, arg); + return f_complex_polar_real(klass, abs, arg); } /* @@ -1745,9 +1751,9 @@ read_digits(const char **s, int strict, while (isdecimal(**s) || **s == '_') { if (**s == '_') { - if (strict) { - if (us) - return 0; + if (us) { + if (strict) return 0; + break; } us = 1; } @@ -2026,6 +2032,12 @@ string_to_c_strict(VALUE self, int raise) * '1/2+3/4i'.to_c #=> ((1/2)+(3/4)*i) * 'ruby'.to_c #=> (0+0i) * + * Polar form: + * include Math + * "1.0@0".to_c #=> (1+0.0i) + * "1.0@#{PI/2}".to_c #=> (0.0+1i) + * "1.0@#{PI}".to_c #=> (-1+0.0i) + * * See Kernel.Complex. */ static VALUE @@ -2095,17 +2107,20 @@ nucomp_convert(VALUE klass, VALUE a1, VALUE a2, int raise) } if (RB_TYPE_P(a1, T_COMPLEX)) { - if (a2 == Qundef || (k_exact_zero_p(a2))) + if (UNDEF_P(a2) || (k_exact_zero_p(a2))) return a1; } - if (a2 == Qundef) { + if (UNDEF_P(a2)) { if (k_numeric_p(a1) && !f_real_p(a1)) 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); } } @@ -2121,7 +2136,7 @@ nucomp_convert(VALUE klass, VALUE a1, VALUE a2, int raise) int argc; VALUE argv2[2]; argv2[0] = a1; - if (a2 == Qundef) { + if (UNDEF_P(a2)) { argv2[1] = Qnil; argc = 1; } @@ -2149,31 +2164,6 @@ nucomp_s_convert(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * num.real -> self - * - * Returns self. - */ -static VALUE -numeric_real(VALUE self) -{ - return self; -} - -/* - * call-seq: - * num.imag -> 0 - * num.imaginary -> 0 - * - * Returns zero. - */ -static VALUE -numeric_imag(VALUE self) -{ - return INT2FIX(0); -} - -/* - * call-seq: * num.abs2 -> real * * Returns square of self. @@ -2245,19 +2235,6 @@ numeric_polar(VALUE self) /* * call-seq: - * num.conj -> self - * num.conjugate -> self - * - * Returns self. - */ -static VALUE -numeric_conj(VALUE self) -{ - return self; -} - -/* - * call-seq: * flo.arg -> 0 or float * flo.angle -> 0 or float * flo.phase -> 0 or float @@ -2421,9 +2398,6 @@ Init_Complex(void) rb_define_private_method(CLASS_OF(rb_cComplex), "convert", nucomp_s_convert, -1); - rb_define_method(rb_cNumeric, "real", numeric_real, 0); - rb_define_method(rb_cNumeric, "imaginary", numeric_imag, 0); - rb_define_method(rb_cNumeric, "imag", numeric_imag, 0); rb_define_method(rb_cNumeric, "abs2", numeric_abs2, 0); rb_define_method(rb_cNumeric, "arg", numeric_arg, 0); rb_define_method(rb_cNumeric, "angle", numeric_arg, 0); @@ -2431,8 +2405,6 @@ Init_Complex(void) rb_define_method(rb_cNumeric, "rectangular", numeric_rect, 0); rb_define_method(rb_cNumeric, "rect", numeric_rect, 0); rb_define_method(rb_cNumeric, "polar", numeric_polar, 0); - rb_define_method(rb_cNumeric, "conjugate", numeric_conj, 0); - rb_define_method(rb_cNumeric, "conj", numeric_conj, 0); rb_define_method(rb_cFloat, "arg", float_arg, 0); rb_define_method(rb_cFloat, "angle", float_arg, 0); diff --git a/configure.ac b/configure.ac index d20d70b8d1..220392d120 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,7 @@ AC_ARG_WITH(baseruby, [ AC_PATH_PROG([BASERUBY], [ruby], [false]) ]) +# BASERUBY must be >= 2.2.0. Note that `"2.2.0" > "2.2"` is true. AS_IF([test "$HAVE_BASERUBY" != no -a "`RUBYOPT=- $BASERUBY --disable=gems -e 'print 42 if RUBY_VERSION > "2.2"' 2>/dev/null`" = 42], [ AS_CASE(["$build_os"], [mingw*], [ # Can MSys shell run a command with a drive letter? @@ -132,9 +133,12 @@ AC_CANONICAL_TARGET AS_CASE(["$target_cpu-$target_os"], [aarch64-darwin*], [ target_cpu=arm64 - AS_CASE(["$target_vendor"], [unknown], [target_vendor=apple target=${target/-unknown-/-apple-}]) - target="${target/aarch64/arm64}" - target_alias="${target_alias/aarch64/arm64}" + AS_CASE(["$target_vendor"], [unknown], [ + target_vendor=apple + target=${target%%-unknown-*}-apple-${target@%:@*-unknown-} + ]) + target="arm64-${target@%:@aarch64-}" + AS_IF([test -n "$target_alias"], [target_alias="arm64-${target_alias@%:@aarch64-}"]) ]) AC_ARG_PROGRAM @@ -210,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) @@ -362,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 @@ -387,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= ]) @@ -520,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], [], [ @@ -785,7 +800,8 @@ AS_IF([test "$GCC" = yes], [ [disable -D_FORTIFY_SOURCE=2 option, which causes link error on mingw]), [fortify_source=$enableval]) AS_IF([test "x$fortify_source" != xno], [ - RUBY_TRY_CFLAGS([$optflags -D_FORTIFY_SOURCE=2], [RUBY_APPEND_OPTION(XCFLAGS, -D_FORTIFY_SOURCE=2)], [], + RUBY_TRY_CFLAGS([$optflags -D_FORTIFY_SOURCE=2], + [RUBY_APPEND_OPTION(XCFLAGS, -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2)], [], [@%:@include <stdio.h>]) ]) @@ -793,7 +809,7 @@ AS_IF([test "$GCC" = yes], [ # -fstack-protector AS_CASE(["$target_os"], - [mingw*|emscripten*|wasi*], [ + [emscripten*|wasi*], [ stack_protector=no ]) AS_IF([test -z "${stack_protector+set}"], [ @@ -805,6 +821,8 @@ AS_IF([test "$GCC" = yes], [ AS_IF([test "x$stack_protector" = xyes], [stack_protector=option; break]) ]) ]) + AC_MSG_CHECKING([for -fstack-protector]) + AC_MSG_RESULT(["$stack_protector"]) AS_CASE(["$stack_protector"], [-*], [ RUBY_APPEND_OPTION(XCFLAGS, $stack_protector) RUBY_APPEND_OPTION(XLDFLAGS, $stack_protector) @@ -957,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\"" @@ -1184,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 @@ -1280,6 +1297,11 @@ dnl AC_HEADER_STDC has been checked in AC_USE_SYSTEM_EXTENSIONS AC_HEADER_STDBOOL AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([afunix.h], [], [], +[#ifdef _WIN32 +# include <winsock2.h> +#endif +]) AC_CHECK_HEADERS(atomic.h) AC_CHECK_HEADERS(copyfile.h) AC_CHECK_HEADERS(direct.h) @@ -1326,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) ]) @@ -1342,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]) @@ -1373,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], @@ -1902,8 +1930,8 @@ AS_CASE(["${target_cpu}-${target_os}:${target_archs}"], [universal-darwin*:*ppc*], [ AC_LIBSOURCES(alloca.c) AC_SUBST([ALLOCA], [\${LIBOBJDIR}alloca.${ac_objext}]) - RUBY_DEFINE_IF([defined __powerpc__], C_ALLOCA, 1) - RUBY_DEFINE_IF([defined __powerpc__], alloca, alloca) + RUBY_DEFINE_IF([defined __POWERPC__], C_ALLOCA, 1) # Darwin defines __POWERPC__ for ppc and ppc64 both + RUBY_DEFINE_IF([defined __POWERPC__], alloca, alloca) ], [ AC_FUNC_ALLOCA @@ -1983,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) @@ -2573,10 +2602,13 @@ AS_CASE([$coroutine_type], [yes|''], [ [arm64-darwin*], [ coroutine_type=arm64 ], - [powerpc-darwin*], [ + # Correct target name is powerpc*-, but Ruby seems to prefer ppc*-. + # Notice that Darwin PPC ABI differs from AIX and ELF. + # Adding PPC targets for AIX, *BSD and *Linux will require separate implementations. + [powerpc-darwin*|ppc-darwin*], [ coroutine_type=ppc ], - [powerpc64-darwin*], [ + [powerpc64-darwin*|ppc64-darwin*], [ coroutine_type=ppc64 ], [x*64-linux*], [ @@ -2958,6 +2990,23 @@ STATIC= ]) } +EXTSTATIC= +AC_SUBST(EXTSTATIC)dnl +AC_ARG_WITH(static-linked-ext, + AS_HELP_STRING([--with-static-linked-ext], [link external modules statically]), + [AS_CASE([$withval],[yes],[STATIC=;EXTSTATIC=static],[no],[],[EXTSTATIC="$withval"])]) +AS_CASE([",$EXTSTATIC,"], [,static,|*,enc,*], [ + ENCOBJS='enc/encinit.$(OBJEXT) enc/libenc.$(LIBEXT) enc/libtrans.$(LIBEXT)' + EXTOBJS='ext/extinit.$(OBJEXT)' + AC_DEFINE_UNQUOTED(EXTSTATIC, 1) + AC_SUBST(ENCSTATIC, static) +], [ + ENCOBJS='dmyenc.$(OBJEXT)' + EXTOBJS='dmyext.$(OBJEXT)' +]) +AC_SUBST(ENCOBJS) +AC_SUBST(EXTOBJS) + : "rpath" && { AS_CASE(["$target_os"], [solaris*], [ AS_IF([test "$GCC" = yes], [ @@ -3017,7 +3066,7 @@ STATIC= [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 @@ -3070,12 +3119,12 @@ AS_IF([test "$rb_cv_dlopen" = yes], [ "-undefined dynamic_lookup" \ ; do test "x${linker_flag}" = x || flag="${linker_flag}`echo ${flag} | tr ' ' ,`" - RUBY_TRY_LDFLAGS([$flag], [], [$flag=]) + RUBY_TRY_LDFLAGS([$flag], [], [flag=]) AS_IF([test x"$flag" = x], [continue]) AC_MSG_CHECKING([whether $flag is accepted for bundle]) : > conftest.c - AS_IF([${LDSHARED/'$(CC)'/$CC} -o conftest.bundle $flag conftest.c >/dev/null 2>conftest.err && + AS_IF([${LDSHARED%%'$(CC)'*}$CC${LDSHARED@%:@*'$(CC)'} -o conftest.bundle $flag conftest.c >/dev/null 2>conftest.err && test ! -s conftest.err], [ AC_MSG_RESULT([yes]) RUBY_APPEND_OPTIONS(DLDFLAGS, [$flag]) @@ -3262,23 +3311,6 @@ AC_ARG_WITH(ext, AC_ARG_WITH(out-ext, AS_HELP_STRING([--with-out-ext=EXTS], [pass to --without-ext option of extmk.rb])) -EXTSTATIC= -AC_SUBST(EXTSTATIC)dnl -AC_ARG_WITH(static-linked-ext, - AS_HELP_STRING([--with-static-linked-ext], [link external modules statically]), - [AS_CASE([$withval],[yes],[STATIC=;EXTSTATIC=static],[no],[],[EXTSTATIC="$withval"])]) -AS_CASE([",$EXTSTATIC,"], [,static,|*,enc,*], [ - ENCOBJS='enc/encinit.$(OBJEXT) enc/libenc.$(LIBEXT) enc/libtrans.$(LIBEXT)' - EXTOBJS='ext/extinit.$(OBJEXT)' - AC_DEFINE_UNQUOTED(EXTSTATIC, 1) - AC_SUBST(ENCSTATIC, static) -], [ - ENCOBJS='dmyenc.$(OBJEXT)' - EXTOBJS='dmyext.$(OBJEXT)' -]) -AC_SUBST(ENCOBJS) -AC_SUBST(EXTOBJS) - AC_ARG_WITH(setup, AS_HELP_STRING([--with-setup=SETUP], [use extension libraries setup]), [setup=$withval]) @@ -3337,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)' @@ -3727,10 +3763,58 @@ AS_IF([test x"$MJIT_SUPPORT" = "xyes"], AC_SUBST(MJIT_SUPPORT) +AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix + +dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0) +YJIT_RUSTC_OK=no +AS_IF([test "$RUSTC" != "no"], + AC_MSG_CHECKING([whether ${RUSTC} works for YJIT]) + YJIT_TARGET_ARCH= + AS_CASE(["$target_cpu"], + [arm64|aarch64], [YJIT_TARGET_ARCH=aarch64], + [x86_64], [YJIT_TARGET_ARCH=x86_64], + ) + dnl Fails in case rustc target doesn't match ruby target. + dnl Can happen on Rosetta, for example. + AS_IF([echo "#[cfg(target_arch = \"$YJIT_TARGET_ARCH\")] fn main() { let x = 1; format!(\"{x}\"); }" | + $RUSTC - --emit asm=/dev/null 2>/dev/null], + [YJIT_RUSTC_OK=yes] + ) + AC_MSG_RESULT($YJIT_RUSTC_OK) +) + +dnl check if we can build YJIT on this target platform +dnl we can't easily cross-compile with rustc so we don't support that +YJIT_TARGET_OK=no +AS_IF([test "$cross_compiling" = no], + AS_CASE(["$target_cpu-$target_os"], + [*android*], [ + YJIT_TARGET_OK=no + ], + [arm64-darwin*|aarch64-darwin*|x86_64-darwin*], [ + YJIT_TARGET_OK=yes + ], + [arm64-*linux*|aarch64-*linux*|x86_64-*linux*], [ + YJIT_TARGET_OK=yes + ], + [arm64-*bsd*|aarch64-*bsd*|x86_64-*bsd*], [ + YJIT_TARGET_OK=yes + ] + ) +) + +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]]), - [YJIT_SUPPORT=$enableval], [YJIT_SUPPORT=no]) + AS_HELP_STRING([--enable-yjit], + [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], [ + YJIT_SUPPORT=yes + ], + [YJIT_SUPPORT=no] + )] +) CARGO= CARGO_BUILD_ARGS= @@ -3740,7 +3824,6 @@ AS_CASE(["${YJIT_SUPPORT}"], AS_IF([test x"$enable_jit_support" = "xno"], AC_MSG_ERROR([--disable-jit-support but --enable-yjit. YJIT requires JIT support]) ) - AC_CHECK_TOOL(RUSTC, [rustc], [no]) AS_IF([test x"$RUSTC" = "xno"], AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) ) @@ -3751,16 +3834,17 @@ AS_CASE(["${YJIT_SUPPORT}"], ], [dev], [ rb_rust_target_subdir=debug - CARGO_BUILD_ARGS='--features stats,disasm,asm_comments' + CARGO_BUILD_ARGS='--features stats,disasm' AC_DEFINE(RUBY_DEBUG, 1) ], [dev_nodebug], [ rb_rust_target_subdir=dev_nodebug - CARGO_BUILD_ARGS='--profile dev_nodebug --features stats,disasm,asm_comments' + CARGO_BUILD_ARGS='--profile dev_nodebug --features stats,disasm' ], [stats], [ rb_rust_target_subdir=stats CARGO_BUILD_ARGS='--profile stats --features stats' + AC_DEFINE(YJIT_STATS, 1) ]) AS_IF([test -n "${CARGO_BUILD_ARGS}"], [ @@ -3775,10 +3859,12 @@ AS_CASE(["${YJIT_SUPPORT}"], LDFLAGS="$LDFLAGS -lpthread -lc++abi" ]) YJIT_OBJ='yjit.$(OBJEXT)' + AS_IF([test x"$YJIT_SUPPORT" != "xyes" ], [ + AC_DEFINE_UNQUOTED(YJIT_SUPPORT, [$YJIT_SUPPORT]) + ]) AC_DEFINE(USE_YJIT, 1) ], [AC_DEFINE(USE_YJIT, 0)]) - dnl These variables end up in ::RbConfig::CONFIG AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes AC_SUBST(RUSTC)dnl Rust compiler command @@ -29,12 +29,15 @@ 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" #include "ruby/fiber/scheduler.h" #include "mjit.h" +#include "yjit.h" #include "vm_core.h" +#include "vm_sync.h" #include "id_table.h" #include "ractor_core.h" @@ -67,6 +70,8 @@ static VALUE rb_cFiberPool; #define FIBER_POOL_ALLOCATION_FREE #endif +#define jit_cont_enabled (mjit_enabled || rb_yjit_enabled_p()) + enum context_type { CONTINUATION_CONTEXT = 0, FIBER_CONTEXT = 1 @@ -195,6 +200,15 @@ struct fiber_pool { size_t vm_stack_size; }; +// Continuation contexts used by JITs +struct rb_jit_cont { + rb_execution_context_t *ec; // continuation ec + struct rb_jit_cont *prev, *next; // used to form lists +}; + +// Doubly linked list for enumerating all on-stack ISEQs. +static struct rb_jit_cont *first_jit_cont; + typedef struct rb_context_struct { enum context_type type; int argc; @@ -212,8 +226,7 @@ typedef struct rb_context_struct { rb_execution_context_t saved_ec; rb_jmpbuf_t jmpbuf; rb_ensure_entry_t *ensure_array; - /* Pointer to MJIT info about the continuation. */ - struct mjit_cont *mjit_cont; + struct rb_jit_cont *jit_cont; // Continuation contexts for JITs } rb_context_t; @@ -259,7 +272,7 @@ struct rb_fiber_struct { static struct fiber_pool shared_fiber_pool = {NULL, NULL, 0, 0, 0, 0}; -static ID fiber_initialize_keywords[2] = {0}; +static ID fiber_initialize_keywords[3] = {0}; /* * FreeBSD require a first (i.e. addr) argument of mmap(2) is not NULL @@ -681,21 +694,36 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack) if (DEBUG) fprintf(stderr, "fiber_pool_stack_free: %p+%"PRIuSIZE" [base=%p, size=%"PRIuSIZE"]\n", base, size, stack->base, stack->size); -#if VM_CHECK_MODE > 0 && defined(MADV_DONTNEED) + // The pages being used by the stack can be returned back to the system. + // That doesn't change the page mapping, but it does allow the system to + // reclaim the physical memory. + // Since we no longer care about the data itself, we don't need to page + // out to disk, since that is costly. Not all systems support that, so + // we try our best to select the most efficient implementation. + // In addition, it's actually slightly desirable to not do anything here, + // but that results in higher memory usage. + +#ifdef __wasi__ + // WebAssembly doesn't support madvise, so we just don't do anything. +#elif VM_CHECK_MODE > 0 && defined(MADV_DONTNEED) // This immediately discards the pages and the memory is reset to zero. madvise(base, size, MADV_DONTNEED); -#elif defined(POSIX_MADV_DONTNEED) - posix_madvise(base, size, POSIX_MADV_DONTNEED); #elif defined(MADV_FREE_REUSABLE) + // Darwin / macOS / iOS. // Acknowledge the kernel down to the task info api we make this // page reusable for future use. // As for MADV_FREE_REUSE below we ensure in the rare occasions the task was not // completed at the time of the call to re-iterate. while (madvise(base, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN); #elif defined(MADV_FREE) + // Recent Linux. madvise(base, size, MADV_FREE); #elif defined(MADV_DONTNEED) + // Old Linux. madvise(base, size, MADV_DONTNEED); +#elif defined(POSIX_MADV_DONTNEED) + // Solaris? + posix_madvise(base, size, POSIX_MADV_DONTNEED); #elif defined(_WIN32) VirtualAlloc(base, size, MEM_RESET, PAGE_READWRITE); // Not available in all versions of Windows. @@ -1000,6 +1028,8 @@ fiber_is_root_p(const rb_fiber_t *fiber) } #endif +static void jit_cont_free(struct rb_jit_cont *cont); + static void cont_free(void *ptr) { @@ -1020,9 +1050,9 @@ cont_free(void *ptr) RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr); - if (mjit_enabled) { - VM_ASSERT(cont->mjit_cont != NULL); - mjit_cont_free(cont->mjit_cont); + if (jit_cont_enabled) { + VM_ASSERT(cont->jit_cont != NULL); + jit_cont_free(cont->jit_cont); } /* free rb_cont_t or rb_fiber_t */ ruby_xfree(ptr); @@ -1127,7 +1157,9 @@ fiber_memsize(const void *ptr) */ if (saved_ec->local_storage && fiber != th->root_fiber) { size += rb_id_table_memsize(saved_ec->local_storage); + size += rb_obj_memsize_of(saved_ec->storage); } + size += cont_memsize(&fiber->cont); return size; } @@ -1187,12 +1219,103 @@ cont_save_thread(rb_context_t *cont, rb_thread_t *th) sec->machine.stack_end = NULL; } +static rb_nativethread_lock_t jit_cont_lock; + +// Register a new continuation with execution context `ec`. Return JIT info about +// the continuation. +static struct rb_jit_cont * +jit_cont_new(rb_execution_context_t *ec) +{ + struct rb_jit_cont *cont; + + // We need to use calloc instead of something like ZALLOC to avoid triggering GC here. + // When this function is called from rb_thread_alloc through rb_threadptr_root_fiber_setup, + // the thread is still being prepared and marking it causes SEGV. + cont = calloc(1, sizeof(struct rb_jit_cont)); + if (cont == NULL) + rb_memerror(); + cont->ec = ec; + + rb_native_mutex_lock(&jit_cont_lock); + if (first_jit_cont == NULL) { + cont->next = cont->prev = NULL; + } + else { + cont->prev = NULL; + cont->next = first_jit_cont; + first_jit_cont->prev = cont; + } + first_jit_cont = cont; + rb_native_mutex_unlock(&jit_cont_lock); + + return cont; +} + +// Unregister continuation `cont`. +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; + if (first_jit_cont != NULL) + first_jit_cont->prev = NULL; + } + else { + cont->prev->next = cont->next; + if (cont->next != NULL) + cont->next->prev = cont->prev; + } + rb_native_mutex_unlock(&jit_cont_lock); + + free(cont); +} + +// Call a given callback against all on-stack ISEQs. +void +rb_jit_cont_each_iseq(rb_iseq_callback callback, void *data) +{ + struct rb_jit_cont *cont; + for (cont = first_jit_cont; cont != NULL; cont = cont->next) { + if (cont->ec->vm_stack == NULL) + continue; + + const rb_control_frame_t *cfp; + for (cfp = RUBY_VM_END_CONTROL_FRAME(cont->ec) - 1; ; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { + const rb_iseq_t *iseq; + if (cfp->pc && (iseq = cfp->iseq) != NULL && imemo_type((VALUE)iseq) == imemo_iseq) { + callback(iseq, data); + } + + if (cfp == cont->ec->cfp) + break; // reached the most recent cfp + } + } +} + +// Finish working with jit_cont. +void +rb_jit_cont_finish(void) +{ + if (!jit_cont_enabled) + return; + + struct rb_jit_cont *cont, *next; + for (cont = first_jit_cont; cont != NULL; cont = next) { + next = cont->next; + free(cont); // Don't use xfree because it's allocated by calloc. + } + rb_native_mutex_destroy(&jit_cont_lock); +} + static void -cont_init_mjit_cont(rb_context_t *cont) +cont_init_jit_cont(rb_context_t *cont) { - VM_ASSERT(cont->mjit_cont == NULL); - if (mjit_enabled) { - cont->mjit_cont = mjit_cont_new(&(cont->saved_ec)); + VM_ASSERT(cont->jit_cont == NULL); + if (jit_cont_enabled) { + cont->jit_cont = jit_cont_new(&(cont->saved_ec)); } } @@ -1211,7 +1334,7 @@ cont_init(rb_context_t *cont, rb_thread_t *th) cont->saved_ec.local_storage = NULL; cont->saved_ec.local_storage_recursive_hash = Qnil; cont->saved_ec.local_storage_recursive_hash_for_trace = Qnil; - cont_init_mjit_cont(cont); + cont_init_jit_cont(cont); } static rb_context_t * @@ -1240,11 +1363,15 @@ rb_fiberptr_blocking(struct rb_fiber_struct *fiber) return fiber->blocking; } -// This is used for root_fiber because other fibers call cont_init_mjit_cont through cont_new. +// Start working with jit_cont. void -rb_fiber_init_mjit_cont(struct rb_fiber_struct *fiber) +rb_jit_cont_init(void) { - cont_init_mjit_cont(&fiber->cont); + if (!jit_cont_enabled) + return; + + rb_native_mutex_initialize(&jit_cont_lock); + cont_init_jit_cont(&GET_EC()->fiber_ptr->cont); } #if 0 @@ -1454,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; @@ -1709,7 +1836,7 @@ rollback_ensure_stack(VALUE self,rb_ensure_list_t *current,rb_ensure_entry_t *ta /* push ensure stack */ for (j = 0; j < i; j++) { func = lookup_rollback_func(target[i - j - 1].e_proc); - if ((VALUE)func != Qundef) { + if (!UNDEF_P((VALUE)func)) { (*func)(target[i - j - 1].data2); } } @@ -1834,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 @@ -1885,11 +2012,207 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) return fiber; } +static rb_fiber_t * +root_fiber_alloc(rb_thread_t *th) +{ + VALUE fiber_value = fiber_alloc(rb_cFiber); + rb_fiber_t *fiber = th->ec->fiber_ptr; + + VM_ASSERT(DATA_PTR(fiber_value) == NULL); + VM_ASSERT(fiber->cont.type == FIBER_CONTEXT); + VM_ASSERT(FIBER_RESUMED_P(fiber)); + + th->root_fiber = fiber; + DATA_PTR(fiber_value) = fiber; + fiber->cont.self = fiber_value; + + coroutine_initialize_main(&fiber->context); + + return fiber; +} + +static inline rb_fiber_t* +fiber_current(void) +{ + rb_execution_context_t *ec = GET_EC(); + if (ec->fiber_ptr->cont.self == 0) { + root_fiber_alloc(rb_ec_thread_ptr(ec)); + } + return ec->fiber_ptr; +} + +static inline VALUE +current_fiber_storage(void) +{ + rb_execution_context_t *ec = GET_EC(); + return ec->storage; +} + +static inline VALUE +inherit_fiber_storage(void) +{ + return rb_obj_dup(current_fiber_storage()); +} + +static inline void +fiber_storage_set(struct rb_fiber_struct *fiber, VALUE storage) +{ + fiber->cont.saved_ec.storage = storage; +} + +static inline VALUE +fiber_storage_get(rb_fiber_t *fiber) +{ + VALUE storage = fiber->cont.saved_ec.storage; + if (storage == Qnil) { + storage = rb_hash_new(); + fiber_storage_set(fiber, storage); + } + 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.storage -> hash (dup) + * + * 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) +{ + Check_Type(key, T_SYMBOL); + + return ST_CONTINUE; +} + +static void +fiber_storage_validate(VALUE value) +{ + // nil is an allowed value and will be lazily initialized. + if (value == Qnil) return; + + if (!RB_TYPE_P(value, T_HASH)) { + rb_raise(rb_eTypeError, "storage must be a hash"); + } + + if (RB_OBJ_FROZEN(value)) { + rb_raise(rb_eFrozenError, "storage must not be frozen"); + } + + rb_hash_foreach(value, fiber_storage_validate_each, Qundef); +} + +/** + * call-seq: fiber.storage = hash + * + * 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::[]=. + * + * You can also use <tt>Fiber.new(storage: nil)</tt> to create a fiber with an empty + * storage. + * + * Example: + * + * while request = request_queue.pop + * # Reset the per-request state: + * Fiber.current.storage = nil + * handle_request(request) + * end + */ static VALUE -fiber_initialize(VALUE self, VALUE proc, struct fiber_pool * fiber_pool, unsigned int blocking) +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); + return value; +} + +/** + * call-seq: Fiber[key] -> value + * + * 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::[]=. + */ +static VALUE +rb_fiber_storage_aref(VALUE class, VALUE key) +{ + Check_Type(key, T_SYMBOL); + + VALUE storage = fiber_storage_get(fiber_current()); + + if (storage == Qnil) return Qnil; + + return rb_hash_aref(storage, key); +} + +/** + * call-seq: Fiber[key] = value + * + * 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::[]. + */ +static VALUE +rb_fiber_storage_aset(VALUE class, VALUE key, VALUE value) +{ + Check_Type(key, T_SYMBOL); + + VALUE storage = fiber_storage_get(fiber_current()); + + return rb_hash_aset(storage, key, value); +} + +static VALUE +fiber_initialize(VALUE self, VALUE proc, struct fiber_pool * fiber_pool, unsigned int blocking, VALUE storage) +{ + if (storage == Qundef || storage == Qtrue) { + // The default, inherit storage (dup) from the current fiber: + storage = inherit_fiber_storage(); + } + else /* nil, hash, etc. */ { + fiber_storage_validate(storage); + storage = rb_obj_dup(storage); + } + rb_fiber_t *fiber = fiber_t_alloc(self, blocking); + fiber->cont.saved_ec.storage = storage; fiber->first_proc = proc; fiber->stack.base = NULL; fiber->stack.pool = fiber_pool; @@ -1922,54 +2245,90 @@ rb_fiber_pool_default(VALUE pool) return &shared_fiber_pool; } +VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb_fiber_struct *fiber) +{ + VALUE storage = rb_obj_dup(ec->storage); + fiber->cont.saved_ec.storage = storage; + return storage; +} + /* :nodoc: */ static VALUE rb_fiber_initialize_kw(int argc, VALUE* argv, VALUE self, int kw_splat) { VALUE pool = Qnil; VALUE blocking = Qfalse; + VALUE storage = Qundef; if (kw_splat != RB_NO_KEYWORDS) { VALUE options = Qnil; - VALUE arguments[2] = {Qundef}; + VALUE arguments[3] = {Qundef}; argc = rb_scan_args_kw(kw_splat, argc, argv, ":", &options); - rb_get_kwargs(options, fiber_initialize_keywords, 0, 2, arguments); + rb_get_kwargs(options, fiber_initialize_keywords, 0, 3, arguments); - if (arguments[0] != Qundef) { + if (!UNDEF_P(arguments[0])) { blocking = arguments[0]; } - if (arguments[1] != Qundef) { + if (!UNDEF_P(arguments[1])) { pool = arguments[1]; } + + storage = arguments[2]; } - return fiber_initialize(self, rb_block_proc(), rb_fiber_pool_default(pool), RTEST(blocking)); + return fiber_initialize(self, rb_block_proc(), rb_fiber_pool_default(pool), RTEST(blocking), storage); } /* * call-seq: - * Fiber.new(blocking: false) { |*args| ... } -> fiber + * Fiber.new(blocking: false, storage: true) { |*args| ... } -> fiber * - * Creates new Fiber. Initially, the fiber is not running and can be resumed with - * #resume. Arguments to the first #resume call will be passed to the block: + * Creates new Fiber. Initially, the fiber is not running and can be resumed + * with #resume. Arguments to the first #resume call will be passed to the + * block: * - * f = Fiber.new do |initial| - * current = initial - * loop do - * puts "current: #{current.inspect}" - * current = Fiber.yield - * end - * end - * f.resume(100) # prints: current: 100 - * f.resume(1, 2, 3) # prints: current: [1, 2, 3] - * f.resume # prints: current: nil - * # ... and so on ... - * - * If <tt>blocking: false</tt> is passed to <tt>Fiber.new</tt>, _and_ current thread - * has a Fiber.scheduler defined, the Fiber becomes non-blocking (see "Non-blocking - * Fibers" section in class docs). + * f = Fiber.new do |initial| + * current = initial + * loop do + * puts "current: #{current.inspect}" + * current = Fiber.yield + * end + * end + * f.resume(100) # prints: current: 100 + * f.resume(1, 2, 3) # prints: current: [1, 2, 3] + * f.resume # prints: current: nil + * # ... and so on ... + * + * If <tt>blocking: false</tt> is passed to <tt>Fiber.new</tt>, _and_ current + * thread has a Fiber.scheduler defined, the Fiber becomes non-blocking (see + * "Non-blocking Fibers" section in class docs). + * + * If the <tt>storage</tt> is unspecified, the default is to inherit a copy of + * the storage from the current fiber. This is the same as specifying + * <tt>storage: true</tt>. + * + * Fiber[:x] = 1 + * Fiber.new do + * Fiber[:x] # => 1 + * Fiber[:x] = 2 + * end.resume + * Fiber[:x] # => 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. + * + * Fiber[:x] = "Hello World" + * Fiber.new(storage: nil) do + * Fiber[:x] # nil + * end + * + * Otherwise, the given <tt>storage</tt> is used as the new fiber's storage, + * and it must be an instance of Hash. + * + * Explicitly using <tt>storage: true</tt> is currently experimental and may + * change in the future. */ static VALUE rb_fiber_initialize(int argc, VALUE* argv, VALUE self) @@ -1978,9 +2337,15 @@ 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), 0, storage); +} + +VALUE rb_fiber_new(rb_block_call_func_t func, VALUE obj) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1); + return rb_fiber_new_storage(func, obj, Qtrue); } static VALUE @@ -1991,7 +2356,7 @@ rb_fiber_s_schedule_kw(int argc, VALUE* argv, int kw_splat) VALUE fiber = Qnil; if (scheduler != Qnil) { - fiber = rb_funcall_passing_block_kw(scheduler, rb_intern("fiber"), argc, argv, kw_splat); + fiber = rb_fiber_scheduler_fiber(scheduler, argc, argv, kw_splat); } else { rb_raise(rb_eRuntimeError, "No scheduler is available!"); @@ -2034,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 @@ -2053,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). * */ @@ -2087,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. @@ -2154,25 +2519,7 @@ rb_fiber_start(rb_fiber_t *fiber) rb_fiber_terminate(fiber, need_interrupt, err); } -static rb_fiber_t * -root_fiber_alloc(rb_thread_t *th) -{ - VALUE fiber_value = fiber_alloc(rb_cFiber); - rb_fiber_t *fiber = th->ec->fiber_ptr; - - VM_ASSERT(DATA_PTR(fiber_value) == NULL); - VM_ASSERT(fiber->cont.type == FIBER_CONTEXT); - VM_ASSERT(fiber->status == FIBER_RESUMED); - - th->root_fiber = fiber; - DATA_PTR(fiber_value) = fiber; - fiber->cont.self = fiber_value; - - coroutine_initialize_main(&fiber->context); - - return fiber; -} - +// Set up a "root fiber", which is the fiber that every Ractor has. void rb_threadptr_root_fiber_setup(rb_thread_t *th) { @@ -2187,9 +2534,11 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) fiber->blocking = 1; fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ th->ec = &fiber->cont.saved_ec; - // This skips mjit_cont_new for the initial thread because mjit_enabled is always false - // at this point. mjit_init calls rb_fiber_init_mjit_cont again for this root_fiber. - rb_fiber_init_mjit_cont(fiber); + // When rb_threadptr_root_fiber_setup is called for the first time, mjit_enabled and + // rb_yjit_enabled_p() are still false. So this does nothing and rb_jit_cont_init() that is + // called later will take care of it. However, you still have to call cont_init_jit_cont() + // here for other Ractors, which are not initialized by rb_jit_cont_init(). + cont_init_jit_cont(&fiber->cont); } void @@ -2224,16 +2573,6 @@ rb_threadptr_root_fiber_terminate(rb_thread_t *th) } static inline rb_fiber_t* -fiber_current(void) -{ - rb_execution_context_t *ec = GET_EC(); - if (ec->fiber_ptr->cont.self == 0) { - root_fiber_alloc(rb_ec_thread_ptr(ec)); - } - return ec->fiber_ptr; -} - -static inline rb_fiber_t* return_fiber(bool terminate) { rb_fiber_t *fiber = fiber_current(); @@ -2410,7 +2749,60 @@ rb_fiber_transfer(VALUE fiber_value, int argc, const VALUE *argv) VALUE rb_fiber_blocking_p(VALUE fiber) { - return RBOOL(fiber_ptr(fiber)->blocking != 0); + return RBOOL(fiber_ptr(fiber)->blocking); +} + +static VALUE +fiber_blocking_yield(VALUE fiber_value) +{ + rb_fiber_t *fiber = fiber_ptr(fiber_value); + rb_thread_t * volatile th = fiber->cont.saved_ec.thread_ptr; + + // fiber->blocking is `unsigned int : 1`, so we use it as a boolean: + fiber->blocking = 1; + + // Once the fiber is blocking, and current, we increment the thread blocking state: + th->blocking += 1; + + return rb_yield(fiber_value); +} + +static VALUE +fiber_blocking_ensure(VALUE fiber_value) +{ + rb_fiber_t *fiber = fiber_ptr(fiber_value); + rb_thread_t * volatile th = fiber->cont.saved_ec.thread_ptr; + + // We are no longer blocking: + fiber->blocking = 0; + th->blocking -= 1; + + return Qnil; +} + +/* + * call-seq: + * Fiber.blocking{|fiber| ...} -> result + * + * Forces the fiber to be blocking for the duration of the block. Returns the + * result of the block. + * + * See the "Non-blocking fibers" section in class docs for details. + * + */ +VALUE +rb_fiber_blocking(VALUE class) +{ + VALUE fiber_value = rb_fiber_current(); + rb_fiber_t *fiber = fiber_ptr(fiber_value); + + // If we are already blocking, this is essentially a no-op: + if (fiber->blocking) { + return rb_yield(fiber_value); + } + else { + return rb_ensure(fiber_blocking_yield, fiber_value, fiber_blocking_ensure, fiber_value); + } } /* @@ -2948,329 +3340,6 @@ rb_fiber_pool_initialize(int argc, VALUE* argv, VALUE self) * fiber.resume #=> FiberError: dead fiber called */ -/* - * Document-class: Fiber::SchedulerInterface - * - * This is not an existing class, but documentation of the interface that Scheduler - * object should comply to in order to be used as argument to Fiber.scheduler and handle non-blocking - * fibers. See also the "Non-blocking fibers" section in Fiber class docs for explanations - * of some concepts. - * - * Scheduler's behavior and usage are expected to be as follows: - * - * * When the execution in the non-blocking Fiber reaches some blocking operation (like - * sleep, wait for a process, or a non-ready I/O), it calls some of the scheduler's - * hook methods, listed below. - * * Scheduler somehow registers what the current fiber is waiting on, and yields control - * to other fibers with Fiber.yield (so the fiber would be suspended while expecting its - * wait to end, and other fibers in the same thread can perform) - * * At the end of the current thread execution, the scheduler's method #close is called - * * The scheduler runs into a wait loop, checking all the blocked fibers (which it has - * registered on hook calls) and resuming them when the awaited resource is ready - * (e.g. I/O ready or sleep time elapsed). - * - * A typical implementation would probably rely for this closing loop on a gem like - * EventMachine[https://github.com/eventmachine/eventmachine] or - * Async[https://github.com/socketry/async]. - * - * This way concurrent execution will be achieved transparently for every - * individual Fiber's code. - * - * Hook methods are: - * - * * #io_wait, #io_read, and #io_write - * * #process_wait - * * #kernel_sleep - * * #timeout_after - * * #address_resolve - * * #block and #unblock - * * (the list is expanded as Ruby developers make more methods having non-blocking calls) - * - * When not specified otherwise, the hook implementations are mandatory: if they are not - * implemented, the methods trying to call hook will fail. To provide backward compatibility, - * in the future hooks will be optional (if they are not implemented, due to the scheduler - * being created for the older Ruby version, the code which needs this hook will not fail, - * and will just behave in a blocking fashion). - * - * It is also strongly recommended that the scheduler implements the #fiber method, which is - * delegated to by Fiber.schedule. - * - * Sample _toy_ implementation of the scheduler can be found in Ruby's code, in - * <tt>test/fiber/scheduler.rb</tt> - * - */ - -#if 0 /* for RDoc */ -/* - * - * Document-method: Fiber::SchedulerInterface#close - * - * Called when the current thread exits. The scheduler is expected to implement this - * method in order to allow all waiting fibers to finalize their execution. - * - * The suggested pattern is to implement the main event loop in the #close method. - * - */ -static VALUE -rb_fiber_scheduler_interface_close(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#process_wait - * call-seq: process_wait(pid, flags) - * - * Invoked by Process::Status.wait in order to wait for a specified process. - * See that method description for arguments description. - * - * Suggested minimal implementation: - * - * Thread.new do - * Process::Status.wait(pid, flags) - * end.value - * - * This hook is optional: if it is not present in the current scheduler, - * Process::Status.wait will behave as a blocking method. - * - * Expected to return a Process::Status instance. - */ -static VALUE -rb_fiber_scheduler_interface_process_wait(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#io_wait - * call-seq: io_wait(io, events, timeout) - * - * Invoked by IO#wait, IO#wait_readable, IO#wait_writable to ask whether the - * specified descriptor is ready for specified events within - * the specified +timeout+. - * - * +events+ is a bit mask of <tt>IO::READABLE</tt>, <tt>IO::WRITABLE</tt>, and - * <tt>IO::PRIORITY</tt>. - * - * Suggested implementation should register which Fiber is waiting for which - * resources and immediately calling Fiber.yield to pass control to other - * fibers. Then, in the #close method, the scheduler might dispatch all the - * I/O resources to fibers waiting for it. - * - * Expected to return the subset of events that are ready immediately. - * - */ -static VALUE -rb_fiber_scheduler_interface_io_wait(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#io_read - * call-seq: io_read(io, buffer, length) -> read length or -errno - * - * Invoked by IO#read to read +length+ bytes from +io+ into a specified - * +buffer+ (see IO::Buffer). - * - * The +length+ argument is the "minimum length to be read". - * If the IO buffer size is 8KiB, but the +length+ is +1024+ (1KiB), up to - * 8KiB might be read, but at least 1KiB will be. - * Generally, the only case where less data than +length+ will be read is if - * there is an error reading the data. - * - * Specifying a +length+ of 0 is valid and means try reading at least once - * and return any available data. - * - * Suggested implementation should try to read from +io+ in a non-blocking - * manner and call #io_wait if the +io+ is not ready (which will yield control - * to other fibers). - * - * See IO::Buffer for an interface available to return data. - * - * Expected to return number of bytes read, or, in case of an error, <tt>-errno</tt> - * (negated number corresponding to system's error code). - * - * The method should be considered _experimental_. - */ -static VALUE -rb_fiber_scheduler_interface_io_read(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#io_write - * call-seq: io_write(io, buffer, length) -> written length or -errno - * - * Invoked by IO#write to write +length+ bytes to +io+ from - * from a specified +buffer+ (see IO::Buffer). - * - * The +length+ argument is the "(minimum) length to be written". - * If the IO buffer size is 8KiB, but the +length+ specified is 1024 (1KiB), - * at most 8KiB will be written, but at least 1KiB will be. - * Generally, the only case where less data than +length+ will be written is if - * there is an error writing the data. - * - * Specifying a +length+ of 0 is valid and means try writing at least once, - * as much data as possible. - * - * Suggested implementation should try to write to +io+ in a non-blocking - * manner and call #io_wait if the +io+ is not ready (which will yield control - * to other fibers). - * - * See IO::Buffer for an interface available to get data from buffer efficiently. - * - * Expected to return number of bytes written, or, in case of an error, <tt>-errno</tt> - * (negated number corresponding to system's error code). - * - * The method should be considered _experimental_. - */ -static VALUE -rb_fiber_scheduler_interface_io_write(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#kernel_sleep - * call-seq: kernel_sleep(duration = nil) - * - * Invoked by Kernel#sleep and Mutex#sleep and is expected to provide - * an implementation of sleeping in a non-blocking way. Implementation might - * register the current fiber in some list of "which fiber wait until what - * moment", call Fiber.yield to pass control, and then in #close resume - * the fibers whose wait period has elapsed. - * - */ -static VALUE -rb_fiber_scheduler_interface_kernel_sleep(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#address_resolve - * call-seq: address_resolve(hostname) -> array_of_strings or nil - * - * Invoked by any method that performs a non-reverse DNS lookup. The most - * notable method is Addrinfo.getaddrinfo, but there are many other. - * - * The method is expected to return an array of strings corresponding to ip - * addresses the +hostname+ is resolved to, or +nil+ if it can not be resolved. - * - * Fairly exhaustive list of all possible call-sites: - * - * - Addrinfo.getaddrinfo - * - Addrinfo.tcp - * - Addrinfo.udp - * - Addrinfo.ip - * - Addrinfo.new - * - Addrinfo.marshal_load - * - SOCKSSocket.new - * - TCPServer.new - * - TCPSocket.new - * - IPSocket.getaddress - * - TCPSocket.gethostbyname - * - UDPSocket#connect - * - UDPSocket#bind - * - UDPSocket#send - * - Socket.getaddrinfo - * - Socket.gethostbyname - * - Socket.pack_sockaddr_in - * - Socket.sockaddr_in - * - Socket.unpack_sockaddr_in - */ -static VALUE -rb_fiber_scheduler_interface_address_resolve(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#timeout_after - * call-seq: timeout_after(duration, exception_class, *exception_arguments, &block) -> result of block - * - * Invoked by Timeout.timeout to execute the given +block+ within the given - * +duration+. It can also be invoked directly by the scheduler or user code. - * - * Attempt to limit the execution time of a given +block+ to the given - * +duration+ if possible. When a non-blocking operation causes the +block+'s - * execution time to exceed the specified +duration+, that non-blocking - * operation should be interrupted by raising the specified +exception_class+ - * constructed with the given +exception_arguments+. - * - * General execution timeouts are often considered risky. This implementation - * will only interrupt non-blocking operations. This is by design because it's - * expected that non-blocking operations can fail for a variety of - * unpredictable reasons, so applications should already be robust in handling - * these conditions and by implication timeouts. - * - * However, as a result of this design, if the +block+ does not invoke any - * non-blocking operations, it will be impossible to interrupt it. If you - * desire to provide predictable points for timeouts, consider adding - * +sleep(0)+. - * - * If the block is executed successfully, its result will be returned. - * - * The exception will typically be raised using Fiber#raise. - */ -static VALUE -rb_fiber_scheduler_interface_timeout_after(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#block - * call-seq: block(blocker, timeout = nil) - * - * Invoked by methods like Thread.join, and by Mutex, to signify that current - * Fiber is blocked until further notice (e.g. #unblock) or until +timeout+ has - * elapsed. - * - * +blocker+ is what we are waiting on, informational only (for debugging and - * logging). There are no guarantee about its value. - * - * Expected to return boolean, specifying whether the blocking operation was - * successful or not. - */ -static VALUE -rb_fiber_scheduler_interface_block(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#unblock - * call-seq: unblock(blocker, fiber) - * - * Invoked to wake up Fiber previously blocked with #block (for example, Mutex#lock - * calls #block and Mutex#unlock calls #unblock). The scheduler should use - * the +fiber+ parameter to understand which fiber is unblocked. - * - * +blocker+ is what was awaited for, but it is informational only (for debugging - * and logging), and it is not guaranteed to be the same value as the +blocker+ for - * #block. - * - */ -static VALUE -rb_fiber_scheduler_interface_unblock(VALUE self) -{ -} - -/* - * Document-method: SchedulerInterface#fiber - * call-seq: fiber(&block) - * - * Implementation of the Fiber.schedule. The method is <em>expected</em> to immediately - * run the given block of code in a separate non-blocking fiber, and to return that Fiber. - * - * Minimal suggested implementation is: - * - * def fiber(&block) - * fiber = Fiber.new(blocking: false, &block) - * fiber.resume - * fiber - * end - */ -static VALUE -rb_fiber_scheduler_interface_fiber(VALUE self) -{ -} -#endif - void Init_Cont(void) { @@ -3292,6 +3361,7 @@ Init_Cont(void) fiber_initialize_keywords[0] = rb_intern_const("blocking"); fiber_initialize_keywords[1] = rb_intern_const("pool"); + fiber_initialize_keywords[2] = rb_intern_const("storage"); const char *fiber_shared_fiber_pool_free_stacks = getenv("RUBY_SHARED_FIBER_POOL_FREE_STACKS"); if (fiber_shared_fiber_pool_free_stacks) { @@ -3303,8 +3373,14 @@ Init_Cont(void) rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1); rb_define_singleton_method(rb_cFiber, "current", rb_fiber_s_current, 0); + rb_define_singleton_method(rb_cFiber, "blocking", rb_fiber_blocking, 0); + rb_define_singleton_method(rb_cFiber, "[]", rb_fiber_storage_aref, 1); + rb_define_singleton_method(rb_cFiber, "[]=", rb_fiber_storage_aset, 2); + rb_define_method(rb_cFiber, "initialize", rb_fiber_initialize, -1); rb_define_method(rb_cFiber, "blocking?", rb_fiber_blocking_p, 0); + rb_define_method(rb_cFiber, "storage", rb_fiber_storage_get, 0); + rb_define_method(rb_cFiber, "storage=", rb_fiber_storage_set, 1); rb_define_method(rb_cFiber, "resume", rb_fiber_m_resume, -1); rb_define_method(rb_cFiber, "raise", rb_fiber_m_raise, -1); rb_define_method(rb_cFiber, "backtrace", rb_fiber_backtrace, -1); @@ -3321,21 +3397,6 @@ Init_Cont(void) rb_define_singleton_method(rb_cFiber, "schedule", rb_fiber_s_schedule, -1); -#if 0 /* for RDoc */ - rb_cFiberScheduler = rb_define_class_under(rb_cFiber, "SchedulerInterface", rb_cObject); - rb_define_method(rb_cFiberScheduler, "close", rb_fiber_scheduler_interface_close, 0); - rb_define_method(rb_cFiberScheduler, "process_wait", rb_fiber_scheduler_interface_process_wait, 0); - rb_define_method(rb_cFiberScheduler, "io_wait", rb_fiber_scheduler_interface_io_wait, 0); - rb_define_method(rb_cFiberScheduler, "io_read", rb_fiber_scheduler_interface_io_read, 0); - rb_define_method(rb_cFiberScheduler, "io_write", rb_fiber_scheduler_interface_io_write, 0); - rb_define_method(rb_cFiberScheduler, "kernel_sleep", rb_fiber_scheduler_interface_kernel_sleep, 0); - rb_define_method(rb_cFiberScheduler, "address_resolve", rb_fiber_scheduler_interface_address_resolve, 0); - rb_define_method(rb_cFiberScheduler, "timeout_after", rb_fiber_scheduler_interface_timeout_after, 0); - rb_define_method(rb_cFiberScheduler, "block", rb_fiber_scheduler_interface_block, 0); - rb_define_method(rb_cFiberScheduler, "unblock", rb_fiber_scheduler_interface_unblock, 0); - rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler_interface_fiber, 0); -#endif - #ifdef RB_EXPERIMENTAL_FIBER_POOL rb_cFiberPool = rb_define_class_under(rb_cFiber, "Pool", rb_cObject); rb_define_alloc_func(rb_cFiberPool, fiber_pool_alloc); 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/coroutine/ppc/Context.S b/coroutine/ppc/Context.S index fe28390df0..cdda93e179 100644 --- a/coroutine/ppc/Context.S +++ b/coroutine/ppc/Context.S @@ -1,73 +1,90 @@ +; Based on the code by Samuel Williams. Created by Sergey Fedorov on 04/06/2022. +; Credits to Samuel Williams, Rei Odaira and Iain Sandoe. Errors, if any, are mine. +; Some relevant examples: https://github.com/gcc-mirror/gcc/blob/master/libphobos/libdruntime/config/powerpc/switchcontext.S +; https://github.com/gcc-mirror/gcc/blob/master/libgcc/config/rs6000/darwin-gpsave.S +; https://www.ibm.com/docs/en/aix/7.2?topic=epilogs-saving-gprs-only +; ppc32 version may be re-written compactly with stmw/lwm, but the code wonʼt be faster, see: https://github.com/ruby/ruby/pull/5927#issuecomment-1139730541 + +; Notice that this code is only for Darwin (macOS). Darwin ABI differs from AIX and ELF. +; To add support for AIX, *BSD or *Linux, please make separate implementations. + #define TOKEN_PASTE(x,y) x##y #define PREFIXED_SYMBOL(prefix,name) TOKEN_PASTE(prefix,name) +.machine ppc7400 ; = G4, Rosetta .text -.align 2 .globl PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer) +.align 2 + PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): - # Make space on the stack for caller registers - addi r1,r1,-80 + ; Make space on the stack for caller registers + ; (Should we rather use red zone? See libphobos example.) + subi r1,r1,80 + + ; Get LR + mflr r0 - # Save caller registers - stw r13,0(r1) - stw r14,4(r1) - stw r15,8(r1) - stw r16,12(r1) - stw r17,16(r1) - stw r18,20(r1) - stw r19,24(r1) - stw r20,28(r1) - stw r21,32(r1) + ; Save caller registers + stw r31,0(r1) + stw r30,4(r1) + stw r29,8(r1) + stw r28,12(r1) + stw r27,16(r1) + stw r26,20(r1) + stw r25,24(r1) + stw r24,28(r1) + stw r23,32(r1) stw r22,36(r1) - stw r23,40(r1) - stw r24,44(r1) - stw r25,48(r1) - stw r26,52(r1) - stw r27,56(r1) - stw r28,60(r1) - stw r29,64(r1) - stw r30,68(r1) - stw r31,72(r1) + stw r21,40(r1) + stw r20,44(r1) + stw r19,48(r1) + stw r18,52(r1) + stw r17,56(r1) + stw r16,60(r1) + stw r15,64(r1) + stw r14,68(r1) + stw r13,72(r1) - # Save return address - mflr r0 + ; Save return address + ; Possibly should rather be saved into linkage area, see libphobos and IBM docs stw r0,76(r1) - # Save stack pointer to first argument + ; Save stack pointer to first argument stw r1,0(r3) - # Load stack pointer from second argument + ; Load stack pointer from second argument lwz r1,0(r4) - # Restore caller registers - lwz r13,0(r1) - lwz r14,4(r1) - lwz r15,8(r1) - lwz r16,12(r1) - lwz r17,16(r1) - lwz r18,20(r1) - lwz r19,24(r1) - lwz r20,28(r1) - lwz r21,32(r1) + ; Load return address + lwz r0,76(r1) + + ; Restore caller registers + lwz r13,72(r1) + lwz r14,68(r1) + lwz r15,64(r1) + lwz r16,60(r1) + lwz r17,56(r1) + lwz r18,52(r1) + lwz r19,48(r1) + lwz r20,44(r1) + lwz r21,40(r1) lwz r22,36(r1) - lwz r23,40(r1) - lwz r24,44(r1) - lwz r25,48(r1) - lwz r26,52(r1) - lwz r27,56(r1) - lwz r28,60(r1) - lwz r29,64(r1) - lwz r30,68(r1) - lwz r31,72(r1) + lwz r23,32(r1) + lwz r24,28(r1) + lwz r25,24(r1) + lwz r26,20(r1) + lwz r27,16(r1) + lwz r28,12(r1) + lwz r29,8(r1) + lwz r30,4(r1) + lwz r31,0(r1) - # Load return address - lwz r0,76(r1) + ; Set LR mtlr r0 - # Pop stack frame + ; Pop stack frame addi r1,r1,80 - # Jump to return address + ; Jump to return address blr - diff --git a/coroutine/ppc/Context.h b/coroutine/ppc/Context.h index 9f69390388..1fce112579 100644 --- a/coroutine/ppc/Context.h +++ b/coroutine/ppc/Context.h @@ -9,6 +9,7 @@ #include <string.h> #define COROUTINE __attribute__((noreturn)) void +#define COROUTINE_LIMITED_ADDRESS_SPACE enum { COROUTINE_REGISTERS = diff --git a/coroutine/ppc64/Context.S b/coroutine/ppc64/Context.S index 1bd9268f93..f8561e0e7d 100644 --- a/coroutine/ppc64/Context.S +++ b/coroutine/ppc64/Context.S @@ -1,70 +1,89 @@ +; Based on the code by Samuel Williams. Created by Sergey Fedorov on 04/06/2022. +; Credits to Samuel Williams, Rei Odaira and Iain Sandoe. Errors, if any, are mine. +; Some relevant examples: https://github.com/gcc-mirror/gcc/blob/master/libphobos/libdruntime/config/powerpc/switchcontext.S +; https://github.com/gcc-mirror/gcc/blob/master/libgcc/config/rs6000/darwin-gpsave.S +; https://www.ibm.com/docs/en/aix/7.2?topic=epilogs-saving-gprs-only + +; Notice that this code is only for Darwin (macOS). Darwin ABI differs from AIX and ELF. +; To add support for AIX, *BSD or *Linux, please make separate implementations. + #define TOKEN_PASTE(x,y) x##y #define PREFIXED_SYMBOL(prefix,name) TOKEN_PASTE(prefix,name) +.machine ppc64 ; = G5 .text -.align 3 .globl PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer) -PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): - # Make space on the stack for caller registers - addi r1,r1,-152 +.align 2 - # Save caller registers - std r14,0(r1) - std r15,8(r1) - std r16,16(r1) - std r17,24(r1) - std r18,32(r1) - std r19,40(r1) - std r20,48(r1) - std r21,56(r1) - std r22,64(r1) - std r23,72(r1) - std r24,80(r1) - std r25,88(r1) - std r26,96(r1) - std r27,104(r1) - std r28,112(r1) - std r29,120(r1) - std r30,128(r1) - std r31,136(r1) +PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): + ; Make space on the stack for caller registers + ; (Should we rather use red zone? See libphobos example.) + subi r1,r1,160 - # Save return address + ; Get LR mflr r0 - std r0,144(r1) - # Save stack pointer to first argument + ; Save caller registers + std r31,0(r1) + std r30,8(r1) + std r29,16(r1) + std r28,24(r1) + std r27,32(r1) + std r26,40(r1) + std r25,48(r1) + std r24,56(r1) + std r23,64(r1) + std r22,72(r1) + std r21,80(r1) + std r20,88(r1) + std r19,96(r1) + std r18,104(r1) + std r17,112(r1) + std r16,120(r1) + std r15,128(r1) + std r14,136(r1) + std r13,144(r1) + + ; Save return address + ; Possibly should rather be saved into linkage area, see libphobos and IBM docs + std r0,152(r1) + + ; Save stack pointer to first argument std r1,0(r3) - # Load stack pointer from second argument + ; Load stack pointer from second argument ld r1,0(r4) - # Restore caller registers - ld r14,0(r1) - ld r15,8(r1) - ld r16,16(r1) - ld r17,24(r1) - ld r18,32(r1) - ld r19,40(r1) - ld r20,48(r1) - ld r21,56(r1) - ld r22,64(r1) - ld r23,72(r1) - ld r24,80(r1) - ld r25,88(r1) - ld r26,96(r1) - ld r27,104(r1) - ld r28,112(r1) - ld r29,120(r1) - ld r30,128(r1) - ld r31,136(r1) + ; Load return address + ld r0,152(r1) + + ; Restore caller registers + ld r13,144(r1) + ld r14,136(r1) + ld r15,128(r1) + ld r16,120(r1) + ld r17,112(r1) + ld r18,104(r1) + ld r19,96(r1) + ld r20,88(r1) + ld r21,80(r1) + ld r22,72(r1) + ld r23,64(r1) + ld r24,56(r1) + ld r25,48(r1) + ld r26,40(r1) + ld r27,32(r1) + ld r28,24(r1) + ld r29,16(r1) + ld r30,8(r1) + ld r31,0(r1) - # Load return address - ld r0,144(r1) + ; Set LR mtlr r0 - # Pop stack frame - addi r1,r1,152 + ; Pop stack frame + addi r1,r1,160 - # Jump to return address + ; Jump to return address blr diff --git a/coroutine/ppc64/Context.h b/coroutine/ppc64/Context.h index 5b47511b9c..3e6f77f55a 100644 --- a/coroutine/ppc64/Context.h +++ b/coroutine/ppc64/Context.h @@ -12,7 +12,7 @@ enum { COROUTINE_REGISTERS = - 19 /* 18 general purpose registers (r14–r31) and 1 return address */ + 20 /* 19 general purpose registers (r13–r31) and 1 return address */ + 4 /* space for fiber_entry() to store the link register */ }; @@ -44,7 +44,7 @@ static inline void coroutine_initialize( memset(context->stack_pointer, 0, sizeof(void*) * COROUTINE_REGISTERS); /* Skip a global prologue that sets the TOC register */ - context->stack_pointer[18] = ((char*)start) + 8; + context->stack_pointer[19] = ((char*)start) + 8; } struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target); 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.c b/debug_counter.c index 0fd0e20c6d..463bebf849 100644 --- a/debug_counter.c +++ b/debug_counter.c @@ -16,7 +16,7 @@ #if USE_DEBUG_COUNTER -static const char *const debug_counter_names[] = { +const char *const rb_debug_counter_names[] = { #define DEBUG_COUNTER_NAME_EMPTY "" /* Suppress -Wstring-concatenation */ DEBUG_COUNTER_NAME_EMPTY #undef DEBUG_COUNTER_NAME_EMPTY @@ -26,7 +26,7 @@ static const char *const debug_counter_names[] = { }; MJIT_SYMBOL_EXPORT_BEGIN -size_t rb_debug_counter[numberof(debug_counter_names)]; +size_t rb_debug_counter[numberof(rb_debug_counter_names)]; void rb_debug_counter_add_atomic(enum rb_debug_counter_type type, int add); MJIT_SYMBOL_EXPORT_END @@ -56,17 +56,7 @@ void ruby_debug_counter_reset(void) { for (int i = 0; i < RB_DEBUG_COUNTER_MAX; i++) { - switch (i) { - case RB_DEBUG_COUNTER_mjit_length_unit_queue: - case RB_DEBUG_COUNTER_mjit_length_active_units: - case RB_DEBUG_COUNTER_mjit_length_compact_units: - case RB_DEBUG_COUNTER_mjit_length_stale_units: - // These counters may be decreased and should not be reset. - break; - default: - rb_debug_counter[i] = 0; - break; - } + rb_debug_counter[i] = 0; } } @@ -77,7 +67,7 @@ ruby_debug_counter_get(const char **names_ptr, size_t *counters_ptr) int i; if (names_ptr != NULL) { for (i=0; i<RB_DEBUG_COUNTER_MAX; i++) { - names_ptr[i] = debug_counter_names[i]; + names_ptr[i] = rb_debug_counter_names[i]; } } if (counters_ptr != NULL) { @@ -107,7 +97,7 @@ rb_debug_counter_show_results(const char *msg) fprintf(stderr, "[RUBY_DEBUG_COUNTER]\t%d %s\n", getpid(), msg); for (i=0; i<RB_DEBUG_COUNTER_MAX; i++) { fprintf(stderr, "[RUBY_DEBUG_COUNTER]\t%-30s\t%'14"PRIuSIZE"\n", - debug_counter_names[i], + rb_debug_counter_names[i], rb_debug_counter[i]); } } diff --git a/debug_counter.h b/debug_counter.h index c6f4176e97..6e0b8dee60 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -130,7 +130,6 @@ RB_DEBUG_COUNTER(frame_C2R) /* instance variable counts * * * ivar_get_ic_hit/miss: ivar_get inline cache (ic) hit/miss counts (VM insn) - * * ivar_get_ic_miss_serial: ivar_get ic miss reason by serial (VM insn) * * ivar_get_ic_miss_unset: ... by unset (VM insn) * * ivar_get_ic_miss_noobject: ... by "not T_OBJECT" (VM insn) * * ivar_set_...: same counts with ivar_set (VM insn) @@ -140,17 +139,17 @@ RB_DEBUG_COUNTER(frame_C2R) */ RB_DEBUG_COUNTER(ivar_get_ic_hit) RB_DEBUG_COUNTER(ivar_get_ic_miss) -RB_DEBUG_COUNTER(ivar_get_ic_miss_serial) -RB_DEBUG_COUNTER(ivar_get_ic_miss_unset) RB_DEBUG_COUNTER(ivar_get_ic_miss_noobject) RB_DEBUG_COUNTER(ivar_set_ic_hit) RB_DEBUG_COUNTER(ivar_set_ic_miss) -RB_DEBUG_COUNTER(ivar_set_ic_miss_serial) -RB_DEBUG_COUNTER(ivar_set_ic_miss_unset) RB_DEBUG_COUNTER(ivar_set_ic_miss_iv_hit) RB_DEBUG_COUNTER(ivar_set_ic_miss_noobject) RB_DEBUG_COUNTER(ivar_get_base) RB_DEBUG_COUNTER(ivar_set_base) +RB_DEBUG_COUNTER(ivar_get_ic_miss_set) +RB_DEBUG_COUNTER(ivar_get_cc_miss_set) +RB_DEBUG_COUNTER(ivar_get_ic_miss_unset) +RB_DEBUG_COUNTER(ivar_get_cc_miss_unset) /* local variable counts * @@ -244,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) @@ -347,41 +347,6 @@ RB_DEBUG_COUNTER(vm_sync_lock_enter_nb) RB_DEBUG_COUNTER(vm_sync_lock_enter_cr) RB_DEBUG_COUNTER(vm_sync_barrier) -/* jit_exec() counts */ -RB_DEBUG_COUNTER(jit_exec) -RB_DEBUG_COUNTER(mjit_exec_not_added) -RB_DEBUG_COUNTER(mjit_exec_not_ready) -RB_DEBUG_COUNTER(mjit_exec_not_compiled) -RB_DEBUG_COUNTER(mjit_exec_call_func) - -/* MJIT enqueue / unload */ -RB_DEBUG_COUNTER(mjit_add_iseq_to_process) -RB_DEBUG_COUNTER(mjit_unload_units) - -/* MJIT <-> VM frame push counts */ -RB_DEBUG_COUNTER(mjit_frame_VM2VM) -RB_DEBUG_COUNTER(mjit_frame_VM2JT) -RB_DEBUG_COUNTER(mjit_frame_JT2JT) -RB_DEBUG_COUNTER(mjit_frame_JT2VM) - -/* MJIT cancel counters */ -RB_DEBUG_COUNTER(mjit_cancel) -RB_DEBUG_COUNTER(mjit_cancel_ivar_inline) -RB_DEBUG_COUNTER(mjit_cancel_exivar_inline) -RB_DEBUG_COUNTER(mjit_cancel_send_inline) -RB_DEBUG_COUNTER(mjit_cancel_opt_insn) /* CALL_SIMPLE_METHOD */ -RB_DEBUG_COUNTER(mjit_cancel_invalidate_all) -RB_DEBUG_COUNTER(mjit_cancel_leave) - -/* rb_mjit_unit_list length */ -RB_DEBUG_COUNTER(mjit_length_unit_queue) -RB_DEBUG_COUNTER(mjit_length_active_units) -RB_DEBUG_COUNTER(mjit_length_compact_units) -RB_DEBUG_COUNTER(mjit_length_stale_units) - -/* Other MJIT counters */ -RB_DEBUG_COUNTER(mjit_compile_failures) - /* load (not implemented yet) */ /* RB_DEBUG_COUNTER(load_files) diff --git a/defs/gmake.mk b/defs/gmake.mk index cebb181fd1..54fef6685f 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -19,6 +19,7 @@ INSTRUBY_ENV += SDKROOT= endif INSTRUBY_ARGS += --gnumake +ifeq ($(DOT_WAIT),) CHECK_TARGETS := great exam love check test check% test% btest% # expand test targets, and those dependents TEST_TARGETS := $(filter $(CHECK_TARGETS),$(MAKECMDGOALS)) @@ -26,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)) @@ -39,8 +40,10 @@ 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 in-srcdir := $(if $(filter-out .,$(srcdir)),$(CHDIR) $(srcdir) &&) @@ -71,6 +74,7 @@ $(foreach arch,$(arch_flags),\ $(eval $(call archcmd,$(patsubst -arch=%,%,$(value arch)),$(patsubst -arch=%,-arch %,$(value arch))))) endif +ifeq ($(DOT_WAIT),) .PHONY: $(addprefix yes-,$(TEST_TARGETS)) ifneq ($(filter-out btest%,$(TEST_TARGETS)),) @@ -80,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 \ ) @@ -88,6 +93,7 @@ prev_test := $(if $(filter test-spec,$(ORDERED_TEST_TARGETS)),test-spec-precheck $(foreach test,$(ORDERED_TEST_TARGETS), \ $(eval yes-$(value test) no-$(value test): $(value prev_test)); \ $(eval prev_test := $(value test))) +endif ifneq ($(if $(filter install,$(MAKECMDGOALS)),$(filter uninstall,$(MAKECMDGOALS))),) install-targets := $(filter install uninstall,$(MAKECMDGOALS)) @@ -264,8 +270,6 @@ HELP_EXTRA_TASKS = \ " update-github: merge master branch and push it to Pull Request [PR=1234]" \ "" -extract-gems: $(HAVE_BASERUBY:yes=update-gems) - # 1. squeeze spaces # 2. strip and skip comment/empty lines # 3. "gem x.y.z URL xxxxxx" -> "gem|x.y.z|xxxxxx|URL" @@ -282,8 +286,18 @@ bundled-gems := $(shell sed \ bundled-gems-rev := $(filter-out $(subst |,,$(bundled-gems)),$(bundled-gems)) bundled-gems := $(filter-out $(bundled-gems-rev),$(bundled-gems)) +# calls $(1) with name, version, revision, URL +foreach-bundled-gems-rev = \ + $(foreach g,$(bundled-gems-rev),$(call foreach-bundled-gems-rev-0,$(1),$(subst |, ,$(value g)))) +foreach-bundled-gems-rev-0 = \ + $(call $(1),$(word 1,$(2)),$(word 2,$(2)),$(word 3,$(2)),$(word 4,$(2))) +bundled-gem-gemfile = $(srcdir)/gems/$(1)-$(2).gem +bundled-gem-srcdir = $(srcdir)/gems/src/$(1) +bundled-gem-extracted = $(srcdir)/.bundle/gems/$(1)-$(2) + update-gems: | $(patsubst %,$(srcdir)/gems/%.gem,$(bundled-gems)) -update-gems: | $(foreach g,$(bundled-gems-rev),$(srcdir)/gems/src/$(word 1,$(subst |, ,$(value g)))) +update-gems: | $(call foreach-bundled-gems-rev,bundled-gem-gemfile) +update-gems: | $(call foreach-bundled-gems-rev,bundled-gem-srcdir) test-bundler-precheck: | $(srcdir)/.bundle/cache @@ -303,8 +317,7 @@ $(srcdir)/gems/%.gem: -e 'FileUtils.rm_rf(old.map{'"|n|"'n.chomp(".gem")})' extract-gems: | $(patsubst %,$(srcdir)/.bundle/gems/%,$(bundled-gems)) -extract-gems: | $(foreach g,$(bundled-gems-rev), \ - $(srcdir)/.bundle/gems/$(word 1,$(subst |, ,$(value g)))-$(word 2,$(subst |, ,$(value g)))) +extract-gems: | $(call foreach-bundled-gems-rev,bundled-gem-extracted) $(srcdir)/.bundle/gems/%: $(srcdir)/gems/%.gem | .bundle/gems $(ECHO) Extracting bundle gem $*... @@ -329,10 +342,10 @@ $(srcdir)/.bundle/gems/$(1)-$(2): | $(srcdir)/gems/src/$(1) .bundle/gems endef define copy-gem-0 -$(call copy-gem,$(word 1,$(1)),$(word 2,$(1)),$(word 3,$(1)),$(word 4,$(1))) +$(eval $(call copy-gem,$(1),$(2),$(3),$(4))) endef -$(foreach g,$(bundled-gems-rev),$(eval $(call copy-gem-0,$(subst |, ,$(value g))))) +$(call foreach-bundled-gems-rev,copy-gem-0) $(srcdir)/gems/src: $(MAKEDIRS) $@ @@ -372,15 +385,22 @@ $(MJIT_MIN_HEADER): $(mjit_min_headers) $(PREP) endif -ifeq ($(if $(wildcard $(filter-out .,$(UNICODE_FILES) $(UNICODE_PROPERTY_FILES))),,\ - $(wildcard $(srcdir)/lib/unicode_normalize/tables.rb)),) -# Needs the dependency when any Unicode data file exists, or -# normalization tables script doesn't. Otherwise, when the target -# only exists, use it as-is. -.PHONY: $(UNICODE_SRC_DATA_DIR)/.unicode-tables.time -UNICODE_TABLES_TIMESTAMP = -$(UNICODE_SRC_DATA_DIR)/.unicode-tables.time: \ - $(UNICODE_FILES) $(UNICODE_PROPERTY_FILES) +.SECONDARY: update-unicode-files +.SECONDARY: update-unicode-auxiliary-files +.SECONDARY: update-unicode-ucd-emoji-files +.SECONDARY: update-unicode-emoji-files + +ifeq ($(HAVE_GIT),yes) +REVISION_LATEST := $(shell $(CHDIR) $(srcdir) && $(GIT) log -1 --format=%H 2>/dev/null) +else +REVISION_LATEST := update +endif +REVISION_IN_HEADER := $(shell sed -n 's/^\#define RUBY_FULL_REVISION "\(.*\)"/\1/p' $(wildcard $(srcdir)/revision.h revision.h) /dev/null 2>/dev/null) +ifeq ($(REVISION_IN_HEADER),) +REVISION_IN_HEADER := none +endif +ifneq ($(REVISION_IN_HEADER),$(REVISION_LATEST)) +$(REVISION_H): PHONY endif include $(top_srcdir)/yjit/yjit.mk diff --git a/defs/id.def b/defs/id.def index 94af02b12f..ebf00506ea 100644 --- a/defs/id.def +++ b/defs/id.def @@ -58,6 +58,7 @@ firstline, predefined = __LINE__+1, %[\ quo name nil + path _ UScore @@ -75,6 +76,7 @@ firstline, predefined = __LINE__+1, %[\ "/*NULL*/" NULL empty? eql? + default respond_to? Respond_to respond_to_missing? Respond_to_missing <IFUNC> @@ -2305,7 +2305,7 @@ glob_helper( #endif break; case BRACE: - if (!recursive) { + if (!recursive || strchr(p->str, '/')) { brace = 1; } break; @@ -2932,7 +2932,7 @@ dir_globs(VALUE args, VALUE base, int flags) static VALUE dir_glob_option_base(VALUE base) { - if (base == Qundef || NIL_P(base)) { + if (NIL_OR_UNDEF_P(base)) { return Qnil; } #if USE_OPENDIR_AT @@ -3343,7 +3343,7 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname) result = (VALUE)rb_thread_call_without_gvl(nogvl_dir_empty_p, (void *)path, RUBY_UBF_IO, 0); - if (result == Qundef) { + if (UNDEF_P(result)) { rb_sys_fail_path(orig); } return result; @@ -41,6 +41,10 @@ static void dln_loaderror(const char *format, ...); # include <strings.h> #endif +#if defined __APPLE__ +# include <AvailabilityMacros.h> +#endif + #ifndef xmalloc void *xmalloc(); void *xcalloc(); @@ -58,7 +62,7 @@ void *xrealloc(); #include <sys/stat.h> #ifndef S_ISDIR -# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #ifdef HAVE_SYS_PARAM_H @@ -298,15 +302,15 @@ COMPILER_WARNING_POP /* assume others than old Mac OS X have no problem */ # define dln_disable_dlclose() false -#elif MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 -/* targeting newer versions only */ -# define dln_disable_dlclose() false - #elif !defined(MAC_OS_X_VERSION_10_11) || \ (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11) /* targeting older versions only */ # define dln_disable_dlclose() true +#elif MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 +/* targeting newer versions only */ +# define dln_disable_dlclose() false + #else /* support both versions, and check at runtime */ # include <sys/sysctl.h> 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/ChangeLog-2.3.0 b/doc/ChangeLog-2.3.0 index 629fd9c4ec..94996cffd0 100644 --- a/doc/ChangeLog-2.3.0 +++ b/doc/ChangeLog-2.3.0 @@ -170,7 +170,7 @@ Tue Dec 22 14:31:28 2015 Toru Iwase <tietew@tietew.net> should return unfrozen new string. [ruby-core:72426] [Bug #11858] -Tue Dec 22 05:39:58 2015 Takashi Kokubun <takashikkbn@gmail.com> +Tue Dec 22 05:39:58 2015 Takashi Kokubun <k0kubun@ruby-lang.org> * ext/cgi/escape/escape.c (preserve_original_state): Preserve original state for tainted and frozen. [Fix GH-1166] @@ -208,7 +208,7 @@ Mon Dec 21 09:33:17 2015 Karol Bucek <kares@users.noreply.github.com> * ext/openssl/lib/openssl/ssl.rb (OpenSSL::SSL::SSLSocket): fix NotImplementedError typo. [Fix GH-1165] -Sun Dec 20 20:54:51 2015 Takashi Kokubun <takashikkbn@gmail.com> +Sun Dec 20 20:54:51 2015 Takashi Kokubun <k0kubun@ruby-lang.org> * cgi/escape/escape.c: Optimize CGI.escapeHTML for ASCII-compatible encodings. [Fix GH-1164] @@ -476,7 +476,7 @@ Tue Dec 15 17:57:57 2015 Martin Duerst <duerst@it.aoyama.ac.jp> to the correct one in the IANA registry (IBM037) and added an alias (ebcdic-cp-us) -Tue Dec 15 16:19:26 2015 Takashi Kokubun <takashikkbn@gmail.com> +Tue Dec 15 16:19:26 2015 Takashi Kokubun <k0kubun@ruby-lang.org> * lib/erb.rb: Render erb with array buffer for function call optimization. [fix GH-1143] @@ -488,7 +488,7 @@ Tue Dec 15 13:50:05 2015 Nobuyoshi Nakada <nobu@ruby-lang.org> * string.c (rb_str_oct): [DOC] mention radix indicators. [ruby-core:71310] [Bug #11648] -Tue Dec 15 12:20:30 2015 Takashi Kokubun <takashikkbn@gmail.com> +Tue Dec 15 12:20:30 2015 Takashi Kokubun <k0kubun@ruby-lang.org> * lib/erb.rb: Simplify regexp to optimize erb scanner. [fix GH-1144] @@ -2670,7 +2670,7 @@ Sat Nov 7 09:51:38 2015 Koichi Sasada <ko1@atdot.net> * vm_trace.c (rb_threadptr_exec_event_hooks_orig): maintain trace_running counter on internal events. - This patch is made by Takashi Kokubun <takashikkbn@gmail.com>. + This patch is made by Takashi Kokubun <k0kubun@ruby-lang.org>. [Bug #11603] https://github.com/ruby/ruby/pull/1059 Sat Nov 7 03:32:27 2015 Koichi Sasada <ko1@atdot.net> diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 52d6042ec3..469c9d8361 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -6,7 +6,7 @@ * C compiler * autoconf - 2.67 or later - * bison - 2.0 or later + * bison - 3.0 or later * gperf - 3.0.3 or later * ruby - 2.7 or later @@ -18,6 +18,7 @@ * libffi * libyaml * libexecinfo (FreeBSD) + * rustc - 1.58.0 or later (if you wish to build [YJIT](/doc/yjit/yjit.md)) 3. Checkout the CRuby source code: @@ -25,23 +26,54 @@ git clone https://github.com/ruby/ruby.git ``` -4. Generate the configuration files and build. It's generally advisable to use a build directory: +4. Generate the configure file: ``` ./autogen.sh - mkdir build && cd build # it's good practice to build outside of source dir - mkdir ~/.rubies # we will install to .rubies/ruby-master in our home dir + ``` + +5. Create a `build` directory outside of the source directory: + + ``` + mkdir build && cd build + ``` + + While it's not necessary to build in a separate directory, it's good practice to do so. + +6. We'll install Ruby in `~/.rubies/ruby-master`, so create the directory: + + ``` + mkdir ~/.rubies + ``` + +7. Run configure: + + ``` ../configure --prefix="${HOME}/.rubies/ruby-master" - make install ``` -5. Optional: If you are frequently building Ruby, disabling documentation will reduce the time it takes to `make`: + - If you are frequently building Ruby, add the `--disable-install-doc` flag to not build documentation which will speed up the build process. - ``` shell - ../configure --disable-install-doc +8. Build Ruby: + + ``` + make install ``` -6. [Run tests](testing_ruby.md) to confirm your build succeeded + - If you're on macOS and installed \OpenSSL through Homebrew, you may encounter failure to build \OpenSSL that look like this: + + ``` + openssl: + Could not be configured. It will not be installed. + ruby/ext/openssl/extconf.rb: OpenSSL library could not be found. You might want to use --with-openssl-dir=<dir> option to specify the prefix where OpenSSL is installed. + Check ext/openssl/mkmf.log for more details. + ``` + + Adding `--with-openssl-dir=$(brew --prefix openssl)` to the list of options passed to configure may solve the issue. + + Remember to delete your `build` directory and start again from the configure step. + +9. [Run tests](testing_ruby.md) to confirm your build succeeded. ### Unexplainable Build Errors diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index df67747710..9cfd59d629 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -137,6 +137,19 @@ or [list](rdoc-ref:RDoc::Markup@Simple+Lists) should be preceded by and followed by a blank line. This is unnecessary for the HTML output, but helps in the `ri` output. +### \Method Names + +For a method name in text: + +- For a method in the current class or module, + use a double-colon for a singleton method, + or a hash mark for an instance method: + <tt>::bar</tt>, <tt>#baz</tt>. +- Otherwise, include the class or module name + and use a dot for a singleton method, + or a hash mark for an instance method: + <tt>Foo.bar</tt>, <tt>Foo#baz</tt>. + ### Auto-Linking In general, \RDoc's auto-linking should not be suppressed. @@ -151,6 +164,28 @@ We might consider whether to suppress when: - The same reference is repeated many times (e.g., _RDoc_ on this page). +### HTML Tags + +In general, avoid using HTML tags (even in formats where it's allowed) +because `ri` (the Ruby Interactive reference tool) +may not render them properly. + +### Tables + +In particular, avoid building tables with HTML tags +(<tt><table></tt>, etc.). + +Alternatives are: + +- The GFM (GitHub Flavored Markdown) table extension, + which is enabled by default. See + {GFM tables extension}[https://github.github.com/gfm/#tables-extension-]. + +- A {verbatim text block}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks], + using spaces and punctuation to format the text. + Note that {text markup}[rdoc-ref:RDoc::MarkupReference@Text+Markup] + will not be honored. + ## Documenting Classes and Modules The general structure of the class or module documentation should be: 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/io_streams.rdoc b/doc/io_streams.rdoc deleted file mode 100644 index 96285e4f36..0000000000 --- a/doc/io_streams.rdoc +++ /dev/null @@ -1,509 +0,0 @@ -== \IO Streams - -This page describes: - -- {Stream classes}[rdoc-ref:io_streams.rdoc@Stream+Classes]. -- {Pre-existing streams}[rdoc-ref:io_streams.rdoc@Pre-Existing+Streams]. -- {User-created streams}[rdoc-ref:io_streams.rdoc@User-Created+Streams]. -- {Basic \IO}[rdoc-ref:io_streams.rdoc@Basic+IO], including: - - - {Position}[rdoc-ref:io_streams.rdoc@Position]. - - {Open and closed streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. - - {End-of-stream}[rdoc-ref:io_streams.rdoc@End-of-Stream]. - -- {Line \IO}[rdoc-ref:io_streams.rdoc@Line+IO], including: - - - {Line separator}[rdoc-ref:io_streams.rdoc@Line+Separator]. - - {Line limit}[rdoc-ref:io_streams.rdoc@Line+Limit]. - - {Line number}[rdoc-ref:io_streams.rdoc@Line+Number]. - - {Line options}[rdoc-ref:io_streams.rdoc@Line+Options]. - -- {Character \IO}[rdoc-ref:io_streams.rdoc@Character+IO]. -- {Byte \IO}[rdoc-ref:io_streams.rdoc@Byte+IO]. -- {Codepoint \IO}[rdoc-ref:io_streams.rdoc@Codepoint+IO]. - -=== Stream Classes - -Ruby supports processing data as \IO streams; -that is, as data that may be read, re-read, written, re-written, -and traversed via iteration. - -Core classes with such support include: - -- IO, and its derived class File. -- {StringIO}[rdoc-ref:StringIO]: for processing a string. -- {ARGF}[rdoc-ref:ARGF]: for processing files cited on the command line. - -Except as noted, the instance methods described on this page -are available in classes \ARGF, \File, \IO, and \StringIO. -A few, also noted, are available in class \Kernel. - -=== Pre-Existing Streams - -Pre-existing streams that are referenced by constants include: - -- $stdin: read-only instance of \IO. -- $stdout: write-only instance of \IO. -- $stderr: read-only instance of \IO. -- \ARGF: read-only instance of \ARGF. - -=== User-Created Streams - -You can create streams: - -- \File: - - - File.new: returns a new \File object; - the file should be closed when no longer needed. - - File.open: passes a new \File object to given the block; - the file is automatically closed on block exit. - -- \IO: - - - IO.new: returns a new \IO object for the given integer file descriptor; - the \IO object should be closed when no longer needed. - - IO.open: passes a new \IO object to the given block; - the \IO object is automatically closed on block exit. - - IO.popen: returns a new \IO object that is connected to the $stdin - and $stdout of a newly-launched subprocess. - - Kernel#open: returns a new \IO object connected to a given source: - stream, file, or subprocess; - the \IO object should be closed when no longer needed. - -- \StringIO: - - - StringIO.new: returns a new \StringIO object; - the \StringIO object should be closed when no longer needed. - - StringIO.open: passes a new \StringIO object to the given block; - the \StringIO object is automatically closed on block exit. - -(You cannot create an \ARGF object, but one already exists.) - -=== About the Examples - -Many examples here use these variables: - - :include: doc/examples/files.rdoc - -=== Basic \IO - -You can perform basic stream \IO with these methods: - -- IO#read: Returns all remaining or the next _n_ bytes read from the stream, - for a given _n_: - - f = File.new('t.txt') - f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n" - f.rewind - f.read(30) # => "First line\r\nSecond line\r\n\r\nFou" - f.read(30) # => "rth line\r\nFifth line\r\n" - f.read(30) # => nil - f.close - -- IO#write: Writes one or more given strings to the stream: - - $stdout.write('Hello', ', ', 'World!', "\n") # => 14 - $stdout.write('foo', :bar, 2, "\n") - - Output: - - Hello, World! - foobar2 - -==== Position - -An \IO stream has a nonnegative integer _position_, -which is the byte offset at which the next read or write is to occur; -the relevant methods: - -- IO#tell (aliased as +#pos+): - Returns the current position (in bytes) in the stream: - - f = File.new('t.txt') - f.tell # => 0 - f.gets # => "First line\n" - f.tell # => 12 - f.close - -- IO#pos=: Sets the position of the stream (in bytes): - - f = File.new('t.txt') - f.tell # => 0 - f.pos = 20 # => 20 - f.tell # => 20 - f.close - -- IO#seek: Sets the position of the stream to a given integer +offset+ - (in bytes), with respect to a given constant +whence+, which is one of: - - - +:CUR+ or <tt>IO::SEEK_CUR</tt>: - Repositions the stream to its current position plus the given +offset+: - - f = File.new('t.txt') - f.tell # => 0 - f.seek(20, :CUR) # => 0 - f.tell # => 20 - f.seek(-10, :CUR) # => 0 - f.tell # => 10 - f.close - - - +:END+ or <tt>IO::SEEK_END</tt>: - Repositions the stream to its end plus the given +offset+: - - f = File.new('t.txt') - f.tell # => 0 - f.seek(0, :END) # => 0 # Repositions to stream end. - f.tell # => 52 - f.seek(-20, :END) # => 0 - f.tell # => 32 - f.seek(-40, :END) # => 0 - f.tell # => 12 - f.close - - - +:SET+ or <tt>IO:SEEK_SET</tt>: - Repositions the stream to the given +offset+: - - f = File.new('t.txt') - f.tell # => 0 - f.seek(20, :SET) # => 0 - f.tell # => 20 - f.seek(40, :SET) # => 0 - f.tell # => 40 - f.close - -- IO#rewind: Positions the stream to the beginning: - - f = File.new('t.txt') - f.tell # => 0 - f.gets # => "First line\n" - f.tell # => 12 - f.rewind # => 0 - f.tell # => 0 - f.close - -==== Open and Closed Streams - -A new \IO stream may be open for reading, open for writing, or both. - -You can close a stream using these methods: - -- IO#close: Closes the stream for both reading and writing. -- IO#close_read (not in \ARGF): Closes the stream for reading. -- IO#close_write (not in \ARGF): Closes the stream for writing. - -You can query whether a stream is closed using this method: - -- IO#closed?: Returns whether the stream is closed. - -==== End-of-Stream - -You can query whether a stream is positioned at its end using -method IO#eof? (also aliased as +#eof+). - -You can reposition to end-of-stream by reading all stream content: - - f = File.new('t.txt') - f.eof? # => false - f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n" - f.eof? # => true - -Or by using method IO#seek: - - f = File.new('t.txt') - f.eof? # => false - f.seek(0, :END) - f.eof? # => true - -=== Line \IO - -You can read an \IO stream line-by-line using these methods: - -- IO#each_line: Passes each line to the block: - - f = File.new('t.txt') - f.each_line {|line| p line } - - Output: - - "First line\n" - "Second line\n" - "\n" - "Fourth line\n" - "Fifth line\n" - - The reading may begin mid-line: - - f = File.new('t.txt') - f.pos = 27 - f.each_line {|line| p line } - - Output: - - "rth line\n" - "Fifth line\n" - -- IO#gets (also in Kernel): Returns the next line (which may begin mid-line): - - f = File.new('t.txt') - f.gets # => "First line\n" - f.gets # => "Second line\n" - f.pos = 27 - f.gets # => "rth line\n" - f.readlines # => ["Fifth line\n"] - f.gets # => nil - -- IO#readline (also in Kernel; not in StringIO): - Like #gets, but raises an exception at end-of-stream. - -- IO#readlines (also in Kernel): Returns all remaining lines in an array; - may begin mid-line: - - f = File.new('t.txt') - f.pos = 19 - f.readlines # => ["ine\n", "\n", "Fourth line\n", "Fifth line\n"] - f.readlines # => [] - -Each of these reader methods may be called with: - -- An optional line separator, +sep+. -- An optional line-size limit, +limit+. -- Both +sep+ and +limit+. - -You can write to an \IO stream line-by-line using this method: - -- IO#puts (also in Kernel; not in \StringIO): Writes objects to the stream: - - f = File.new('t.tmp', 'w') - f.puts('foo', :bar, 1, 2.0, Complex(3, 0)) - f.flush - File.read('t.tmp') # => "foo\nbar\n1\n2.0\n3+0i\n" - -==== Line Separator - -The default line separator is the given by the global variable <tt>$/</tt>, -whose value is by default <tt>"\n"</tt>. -The line to be read next is all data from the current position -to the next line separator: - - f = File.new('t.txt') - f.gets # => "First line\n" - f.gets # => "Second line\n" - f.gets # => "\n" - f.gets # => "Fourth line\n" - f.gets # => "Fifth line\n" - f.close - -You can specify a different line separator: - - f = File.new('t.txt') - f.gets('l') # => "First l" - f.gets('li') # => "ine\nSecond li" - f.gets('lin') # => "ne\n\nFourth lin" - f.gets # => "e\n" - f.close - -There are two special line separators: - -- +nil+: The entire stream is read into a single string: - - f = File.new('t.txt') - f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" - f.close - -- <tt>''</tt> (the empty string): The next "paragraph" is read - (paragraphs being separated by two consecutive line separators): - - f = File.new('t.txt') - f.gets('') # => "First line\nSecond line\n\n" - f.gets('') # => "Fourth line\nFifth line\n" - f.close - -==== Line Limit - -The line to be read may be further defined by an optional integer argument +limit+, -which specifies that the number of bytes returned may not be (much) longer -than the given +limit+; -a multi-byte character will not be split, and so a line may be slightly longer -than the given limit. - -If +limit+ is not given, the line is determined only by +sep+. - - # Text with 1-byte characters. - File.new('t.txt') {|f| f.gets(1) } # => "F" - File.new('t.txt') {|f| f.gets(2) } # => "Fi" - File.new('t.txt') {|f| f.gets(3) } # => "Fir" - File.new('t.txt') {|f| f.gets(4) } # => "Firs" - # No more than one line. - File.new('t.txt') {|f| f.gets(10) } # => "First line" - File.new('t.txt') {|f| f.gets(11) } # => "First line\n" - File.new('t.txt') {|f| f.gets(12) } # => "First line\n" - - # Text with 2-byte characters, which will not be split. - File.new('r.rus') {|f| f.gets(1).size } # => 1 - File.new('r.rus') {|f| f.gets(2).size } # => 1 - File.new('r.rus') {|f| f.gets(3).size } # => 2 - File.new('r.rus') {|f| f.gets(4).size } # => 2 - -==== Line Separator and Line Limit - -With arguments +sep+ and +limit+ given, -combines the two behaviors: - -- Returns the next line as determined by line separator +sep+. -- But returns no more bytes than are allowed by the limit. - -Example: - - File.new('t.txt') {|f| f.gets('li', 20) } # => "First li" - File.new('t.txt') {|f| f.gets('li', 2) } # => "Fi" - -==== Line Number - -A readable \IO stream has a _line_ _number_, -which is the non-negative integer line number -in the stream where the next read will occur. - -A new stream is initially has line number +0+. - -\Method IO#lineno returns the line number. - -Reading lines from a stream usually changes its line number: - - f = File.new('t.txt', 'r') - f.lineno # => 0 - f.readline # => "This is line one.\n" - f.lineno # => 1 - f.readline # => "This is the second line.\n" - f.lineno # => 2 - f.readline # => "Here's the third line.\n" - f.lineno # => 3 - f.eof? # => true - f.close - -Iterating over lines in a stream usually changes its line number: - - File.open('t.txt') do |f| - f.each_line do |line| - p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}" - end - end - -Output: - - "position=11 eof?=false lineno=1" - "position=23 eof?=false lineno=2" - "position=24 eof?=false lineno=3" - "position=36 eof?=false lineno=4" - "position=47 eof?=true lineno=5" - -==== Line Options - -A number of \IO methods accept optional keyword arguments -that determine how lines in a stream are to be treated: - -- +:chomp+: If +true+, line separators are omitted; default is +false+. - -=== Character \IO - -You can process an \IO stream character-by-character using these methods: - -- IO#getc: Reads and returns the next character from the stream: - - f = File.new('t.rus') - f.getc # => "т" - f.getc # => "е" - f.getc # => "с" - f.getc # => "т" - f.getc # => nil - -- IO#readchar (not in \StringIO): - Like #getc, but raises an exception at end-of-stream: - - f.readchar # Raises EOFError. - -- IO#ungetc (not in \ARGF): - Pushes back ("unshifts") a character or integer onto the stream: - - path = 't.tmp' - File.write(path, 'foo') - File.open(path) do |f| - f.ungetc('т') - f.read # => "тfoo" - end - -- IO#putc (also in Kernel): Writes a character to the stream: - - File.open('t.tmp', 'w') do |f| - f.putc('т') - f.putc('е') - f.putc('с') - f.putc('т') - end - File.read('t.tmp') # => "тест" - -- IO#each_char: Reads each remaining character in the stream, - passing the character to the given block: - - File.open('t.rus') do |f| - f.pos = 4 - f.each_char {|c| p c } - end - - Output: - - "с" - "т" - -=== Byte \IO - -You can process an \IO stream byte-by-byte using these methods: - -- IO#getbyte: Returns the next 8-bit byte as an integer in range 0..255: - - File.read('t.dat') - # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94" - File.read('t.dat') - # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94" - f = File.new('t.dat') - f.getbyte # => 254 - f.getbyte # => 255 - f.seek(-2, :END) - f.getbyte # => 153 - f.getbyte # => 148 - f.getbyte # => nil - -- IO#readbyte (not in \StringIO): - Like #getbyte, but raises an exception if at end-of-stream: - - f.readbyte # Raises EOFError. - -- IO#ungetbyte (not in \ARGF): - Pushes back ("unshifts") a byte back onto the stream: - - f.ungetbyte(0) - f.ungetbyte(01) - f.read # => "\u0001\u0000" - -- IO#each_byte: Reads each remaining byte in the stream, - passing the byte to the given block: - - f.seek(-4, :END) - f.each_byte {|b| p b } - - Output: - - 153 - 147 - 153 - 148 - -=== Codepoint \IO - -You can process an \IO stream codepoint-by-codepoint using method -+#each_codepoint+: - - a = [] - File.open('t.rus') do |f| - f.each_codepoint {|c| a << c } - end - a # => [1090, 1077, 1089, 1090] 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 new file mode 100644 index 0000000000..c1366e7ad1 --- /dev/null +++ b/doc/net-http/examples.rdoc @@ -0,0 +1,31 @@ +Examples here assume that <tt>net/http</tt> has been required +(which also requires +uri+): + + require 'net/http' + +Many code examples here use these example websites: + +- https://jsonplaceholder.typicode.com. +- http://example.com. + +Some examples also assume these variables: + + 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: + + Net::HTTP.get(uri) + Net::HTTP.get(hostname, '/index.html') + Net::HTTP.start(hostname) do |http| + http.get('/todos/1') + http.get('/todos/2') + end + +An example that needs a modified URI first duplicates +uri+, then modifies the duplicate: + + _uri = uri.dup + _uri.path = '/todos/1' 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 new file mode 100644 index 0000000000..ec13b24c69 --- /dev/null +++ b/doc/packed_data.rdoc @@ -0,0 +1,590 @@ +== Packed \Data + +Certain Ruby core methods deal with packing and unpacking data: + +- \Method Array#pack: + Formats each element in array +self+ into a binary string; + returns that string. +- \Method String#unpack: + Extracts data from string +self+, + forming objects that become the elements of a new array; + returns that array. +- \Method String#unpack1: + Does the same, but unpacks and returns only the first extracted object. + +Each of these methods accepts a string +template+, +consisting of zero or more _directive_ characters, +each followed by zero or more _modifier_ characters. + +Examples (directive <tt>'C'</tt> specifies 'unsigned character'): + + [65].pack('C') # => "A" # One element, one directive. + [65, 66].pack('CC') # => "AB" # Two elements, two directives. + [65, 66].pack('C') # => "A" # Extra element is ignored. + [65].pack('') # => "" # No directives. + [65].pack('CC') # Extra directive raises ArgumentError. + + 'A'.unpack('C') # => [65] # One character, one directive. + 'AB'.unpack('CC') # => [65, 66] # Two characters, two directives. + 'AB'.unpack('C') # => [65] # Extra character is ignored. + 'A'.unpack('CC') # => [65, nil] # Extra directive generates nil. + 'AB'.unpack('') # => [] # No directives. + +The string +template+ may contain any mixture of valid directives +(directive <tt>'c'</tt> specifies 'signed character'): + + [65, -1].pack('cC') # => "A\xFF" + "A\xFF".unpack('cC') # => [65, 255] + +The string +template+ may contain whitespace (which is ignored) +and comments, each of which begins with character <tt>'#'</tt> +and continues up to and including the next following newline: + + [0,1].pack(" C #foo \n C ") # => "\x00\x01" + "\0\1".unpack(" C #foo \n C ") # => [0, 1] + +Any directive may be followed by either of these modifiers: + +- <tt>'*'</tt> - The directive is to be applied as many times as needed: + + [65, 66].pack('C*') # => "AB" + 'AB'.unpack('C*') # => [65, 66] + +- Integer +count+ - The directive is to be applied +count+ times: + + [65, 66].pack('C2') # => "AB" + [65, 66].pack('C3') # Raises ArgumentError. + 'AB'.unpack('C2') # => [65, 66] + 'AB'.unpack('C3') # => [65, 66, nil] + + 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 ++buffer+ that specifies the target string (instead of a new string): + + [65, 66].pack('C*', buffer: 'foo') # => "fooAB" + +The method can accept a block: + + # Packed string is passed to the block. + [65, 66].pack('C*') {|s| p s } # => "AB" + +=== Unpacking Methods + +Methods String#unpack and String#unpack1 each accept +an optional keyword argument +offset+ that specifies an offset +into the string: + + 'ABC'.unpack('C*', offset: 1) # => [66, 67] + 'ABC'.unpack1('C*', offset: 1) # => 66 + +Both methods can accept a block: + + # Each unpacked object is passed to the block. + ret = [] + "ABCD".unpack("C*") {|c| ret << c } + ret # => [65, 66, 67, 68] + + # The single unpacked object is passed to the block. + 'AB'.unpack1('C*') {|ele| p ele } # => 65 + +=== \Integer Directives + +Each integer directive specifies the packing or unpacking +for one element in the input or output array. + +==== 8-Bit \Integer Directives + +- <tt>'c'</tt> - 8-bit signed integer + (like C <tt>signed char</tt>): + + [0, 1, 255].pack('c*') # => "\x00\x01\xFF" + s = [0, 1, -1].pack('c*') # => "\x00\x01\xFF" + s.unpack('c*') # => [0, 1, -1] + +- <tt>'C'</tt> - 8-bit signed integer + (like C <tt>unsigned char</tt>): + + [0, 1, 255].pack('C*') # => "\x00\x01\xFF" + s = [0, 1, -1].pack('C*') # => "\x00\x01\xFF" + s.unpack('C*') # => [0, 1, 255] + +==== 16-Bit \Integer Directives + +- <tt>'s'</tt> - 16-bit signed integer, native-endian + (like C <tt>int16_t</tt>): + + [513, -514].pack('s*') # => "\x01\x02\xFE\xFD" + s = [513, 65022].pack('s*') # => "\x01\x02\xFE\xFD" + s.unpack('s*') # => [513, -514] + +- <tt>'S'</tt> - 16-bit unsigned integer, native-endian + (like C <tt>uint16_t</tt>): + + [513, -514].pack('S*') # => "\x01\x02\xFE\xFD" + s = [513, 65022].pack('S*') # => "\x01\x02\xFE\xFD" + s.unpack('S*') # => [513, 65022] + +- <tt>'n'</tt> - 16-bit network integer, big-endian: + + s = [0, 1, -1, 32767, -32768, 65535].pack('n*') + # => "\x00\x00\x00\x01\xFF\xFF\x7F\xFF\x80\x00\xFF\xFF" + s.unpack('n*') + # => [0, 1, 65535, 32767, 32768, 65535] + +- <tt>'v'</tt> - 16-bit VAX integer, little-endian: + + s = [0, 1, -1, 32767, -32768, 65535].pack('v*') + # => "\x00\x00\x01\x00\xFF\xFF\xFF\x7F\x00\x80\xFF\xFF" + s.unpack('v*') + # => [0, 1, 65535, 32767, 32768, 65535] + +==== 32-Bit \Integer Directives + +- <tt>'l'</tt> - 32-bit signed integer, native-endian + (like C <tt>int32_t</tt>): + + s = [67305985, -50462977].pack('l*') + # => "\x01\x02\x03\x04\xFF\xFE\xFD\xFC" + s.unpack('l*') + # => [67305985, -50462977] + +- <tt>'L'</tt> - 32-bit unsigned integer, native-endian + (like C <tt>uint32_t</tt>): + + s = [67305985, 4244504319].pack('L*') + # => "\x01\x02\x03\x04\xFF\xFE\xFD\xFC" + s.unpack('L*') + # => [67305985, 4244504319] + +- <tt>'N'</tt> - 32-bit network integer, big-endian: + + s = [0,1,-1].pack('N*') + # => "\x00\x00\x00\x00\x00\x00\x00\x01\xFF\xFF\xFF\xFF" + s.unpack('N*') + # => [0, 1, 4294967295] + +- <tt>'V'</tt> - 32-bit VAX integer, little-endian: + + s = [0,1,-1].pack('V*') + # => "\x00\x00\x00\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF" + s.unpack('v*') + # => [0, 0, 1, 0, 65535, 65535] + +==== 64-Bit \Integer Directives + +- <tt>'q'</tt> - 64-bit signed integer, native-endian + (like C <tt>int64_t</tt>): + + s = [578437695752307201, -506097522914230529].pack('q*') + # => "\x01\x02\x03\x04\x05\x06\a\b\xFF\xFE\xFD\xFC\xFB\xFA\xF9\xF8" + s.unpack('q*') + # => [578437695752307201, -506097522914230529] + +- <tt>'Q'</tt> - 64-bit unsigned integer, native-endian + (like C <tt>uint64_t</tt>): + + s = [578437695752307201, 17940646550795321087].pack('Q*') + # => "\x01\x02\x03\x04\x05\x06\a\b\xFF\xFE\xFD\xFC\xFB\xFA\xF9\xF8" + s.unpack('Q*') + # => [578437695752307201, 17940646550795321087] + +==== Platform-Dependent \Integer Directives + +- <tt>'i'</tt> - Platform-dependent width signed integer, + native-endian (like C <tt>int</tt>): + + s = [67305985, -50462977].pack('i*') + # => "\x01\x02\x03\x04\xFF\xFE\xFD\xFC" + s.unpack('i*') + # => [67305985, -50462977] + +- <tt>'I'</tt> - Platform-dependent width unsigned integer, + native-endian (like C <tt>unsigned int</tt>): + + s = [67305985, -50462977].pack('I*') + # => "\x01\x02\x03\x04\xFF\xFE\xFD\xFC" + s.unpack('I*') + # => [67305985, 4244504319] + +==== Pointer Directives + +- <tt>'j'</tt> - 64-bit pointer-width signed integer, + native-endian (like C <tt>intptr_t</tt>): + + s = [67305985, -50462977].pack('j*') + # => "\x01\x02\x03\x04\x00\x00\x00\x00\xFF\xFE\xFD\xFC\xFF\xFF\xFF\xFF" + s.unpack('j*') + # => [67305985, -50462977] + +- <tt>'j'</tt> - 64-bit pointer-width unsigned integer, + native-endian (like C <tt>uintptr_t</tt>): + + s = [67305985, 4244504319].pack('J*') + # => "\x01\x02\x03\x04\x00\x00\x00\x00\xFF\xFE\xFD\xFC\x00\x00\x00\x00" + s.unpack('J*') + # => [67305985, 4244504319] + +==== Other \Integer Directives +: +- <tt>'U'</tt> - UTF-8 character: + + s = [4194304].pack('U*') + # => "\xF8\x90\x80\x80\x80" + s.unpack('U*') + # => [4194304] + +- <tt>'w'</tt> - BER-encoded integer + (see {BER enocding}[https://en.wikipedia.org/wiki/X.690#BER_encoding]): + + s = [1073741823].pack('w*') + # => "\x83\xFF\xFF\xFF\x7F" + s.unpack('w*') + # => [1073741823] + +==== Modifiers for \Integer Directives + +For directives in +<tt>'i'</tt>, +<tt>'I'</tt>, +<tt>'s'</tt>, +<tt>'S'</tt>, +<tt>'l'</tt>, +<tt>'L'</tt>, +<tt>'q'</tt>, +<tt>'Q'</tt>, +<tt>'j'</tt>, and +<tt>'J'</tt>, +these modifiers may be suffixed: + +- <tt>'!'</tt> or <tt>'_'</tt> - Underlying platform’s native size. +- <tt>'>'</tt> - Big-endian. +- <tt>'<'</tt> - Little-endian. + +=== \Float Directives + +Each float directive specifies the packing or unpacking +for one element in the input or output array. + +==== Single-Precision \Float Directives + +- <tt>'F'</tt> or <tt>'f'</tt> - Native format: + + s = [3.0].pack('F') # => "\x00\x00@@" + s.unpack('F') # => [3.0] + +- <tt>'e'</tt> - Little-endian: + + s = [3.0].pack('e') # => "\x00\x00@@" + s.unpack('e') # => [3.0] + +- <tt>'g'</tt> - Big-endian: + + s = [3.0].pack('g') # => "@@\x00\x00" + s.unpack('g') # => [3.0] + +==== Double-Precision \Float Directives + +- <tt>'D'</tt> or <tt>'d'</tt> - Native format: + + s = [3.0].pack('D') # => "\x00\x00\x00\x00\x00\x00\b@" + s.unpack('D') # => [3.0] + +- <tt>'E'</tt> - Little-endian: + + s = [3.0].pack('E') # => "\x00\x00\x00\x00\x00\x00\b@" + s.unpack('E') # => [3.0] + +- <tt>'G'</tt> - Big-endian: + + s = [3.0].pack('G') # => "@\b\x00\x00\x00\x00\x00\x00" + s.unpack('G') # => [3.0] + +A float directive may be infinity or not-a-number: + + inf = 1.0/0.0 # => Infinity + [inf].pack('f') # => "\x00\x00\x80\x7F" + "\x00\x00\x80\x7F".unpack('f') # => [Infinity] + + nan = inf/inf # => NaN + [nan].pack('f') # => "\x00\x00\xC0\x7F" + "\x00\x00\xC0\x7F".unpack('f') # => [NaN] + +=== \String Directives + +Each string directive specifies the packing or unpacking +for one byte in the input or output string. + +==== Binary \String Directives + +- <tt>'A'</tt> - Arbitrary binary string (space padded; count is width); + +nil+ is treated as the empty string: + + ['foo'].pack('A') # => "f" + ['foo'].pack('A*') # => "foo" + ['foo'].pack('A2') # => "fo" + ['foo'].pack('A4') # => "foo " + [nil].pack('A') # => " " + [nil].pack('A*') # => "" + [nil].pack('A2') # => " " + [nil].pack('A4') # => " " + + "foo\0".unpack('A') # => ["f"] + "foo\0".unpack('A4') # => ["foo"] + "foo\0bar".unpack('A10') # => ["foo\x00bar"] # Reads past "\0". + "foo ".unpack('A') # => ["f"] + "foo ".unpack('A4') # => ["foo"] + "foo".unpack('A4') # => ["foo"] + + russian = "\u{442 435 441 442}" # => "тест" + russian.size # => 4 + russian.bytesize # => 8 + [russian].pack('A') # => "\xD1" + [russian].pack('A*') # => "\xD1\x82\xD0\xB5\xD1\x81\xD1\x82" + russian.unpack('A') # => ["\xD1"] + russian.unpack('A2') # => ["\xD1\x82"] + russian.unpack('A4') # => ["\xD1\x82\xD0\xB5"] + russian.unpack('A*') # => ["\xD1\x82\xD0\xB5\xD1\x81\xD1\x82"] + +- <tt>'a'</tt> - Arbitrary binary string (null padded; count is width): + + ["foo"].pack('a') # => "f" + ["foo"].pack('a*') # => "foo" + ["foo"].pack('a2') # => "fo" + ["foo\0"].pack('a4') # => "foo\x00" + [nil].pack('a') # => "\x00" + [nil].pack('a*') # => "" + [nil].pack('a2') # => "\x00\x00" + [nil].pack('a4') # => "\x00\x00\x00\x00" + + "foo\0".unpack('a') # => ["f"] + "foo\0".unpack('a4') # => ["foo\x00"] + "foo ".unpack('a4') # => ["foo "] + "foo".unpack('a4') # => ["foo"] + "foo\0bar".unpack('a4') # => ["foo\x00"] # Reads past "\0". + +- <tt>'Z'</tt> - Same as <tt>'a'</tt>, + except that null is added or ignored with <tt>'*'</tt>: + + ["foo"].pack('Z*') # => "foo\x00" + [nil].pack('Z*') # => "\x00" + + "foo\0".unpack('Z*') # => ["foo"] + "foo".unpack('Z*') # => ["foo"] + "foo\0bar".unpack('Z*') # => ["foo"] # Does not read past "\0". + +==== Bit \String Directives + +- <tt>'B'</tt> - Bit string (high byte first): + + ['11111111' + '00000000'].pack('B*') # => "\xFF\x00" + ['10000000' + '01000000'].pack('B*') # => "\x80@" + + ['1'].pack('B0') # => "" + ['1'].pack('B1') # => "\x80" + ['1'].pack('B2') # => "\x80\x00" + ['1'].pack('B3') # => "\x80\x00" + ['1'].pack('B4') # => "\x80\x00\x00" + ['1'].pack('B5') # => "\x80\x00\x00" + ['1'].pack('B6') # => "\x80\x00\x00\x00" + + "\xff\x00".unpack("B*") # => ["1111111100000000"] + "\x01\x02".unpack("B*") # => ["0000000100000010"] + + "".unpack("B0") # => [""] + "\x80".unpack("B1") # => ["1"] + "\x80".unpack("B2") # => ["10"] + "\x80".unpack("B3") # => ["100"] + +- <tt>'b'</tt> - Bit string (low byte first): + + ['11111111' + '00000000'].pack('b*') # => "\xFF\x00" + ['10000000' + '01000000'].pack('b*') # => "\x01\x02" + + ['1'].pack('b0') # => "" + ['1'].pack('b1') # => "\x01" + ['1'].pack('b2') # => "\x01\x00" + ['1'].pack('b3') # => "\x01\x00" + ['1'].pack('b4') # => "\x01\x00\x00" + ['1'].pack('b5') # => "\x01\x00\x00" + ['1'].pack('b6') # => "\x01\x00\x00\x00" + + "\xff\x00".unpack("b*") # => ["1111111100000000"] + "\x01\x02".unpack("b*") # => ["1000000001000000"] + + "".unpack("b0") # => [""] + "\x01".unpack("b1") # => ["1"] + "\x01".unpack("b2") # => ["10"] + "\x01".unpack("b3") # => ["100"] + +==== Hex \String Directives + +- <tt>'H'</tt> - Hex string (high nibble first): + + ['10ef'].pack('H*') # => "\x10\xEF" + ['10ef'].pack('H0') # => "" + ['10ef'].pack('H3') # => "\x10\xE0" + ['10ef'].pack('H5') # => "\x10\xEF\x00" + + ['fff'].pack('H3') # => "\xFF\xF0" + ['fff'].pack('H4') # => "\xFF\xF0" + ['fff'].pack('H5') # => "\xFF\xF0\x00" + ['fff'].pack('H6') # => "\xFF\xF0\x00" + ['fff'].pack('H7') # => "\xFF\xF0\x00\x00" + ['fff'].pack('H8') # => "\xFF\xF0\x00\x00" + + "\x10\xef".unpack('H*') # => ["10ef"] + "\x10\xef".unpack('H0') # => [""] + "\x10\xef".unpack('H1') # => ["1"] + "\x10\xef".unpack('H2') # => ["10"] + "\x10\xef".unpack('H3') # => ["10e"] + "\x10\xef".unpack('H4') # => ["10ef"] + "\x10\xef".unpack('H5') # => ["10ef"] + +- <tt>'h'</tt> - Hex string (low nibble first): + + ['10ef'].pack('h*') # => "\x01\xFE" + ['10ef'].pack('h0') # => "" + ['10ef'].pack('h3') # => "\x01\x0E" + ['10ef'].pack('h5') # => "\x01\xFE\x00" + + ['fff'].pack('h3') # => "\xFF\x0F" + ['fff'].pack('h4') # => "\xFF\x0F" + ['fff'].pack('h5') # => "\xFF\x0F\x00" + ['fff'].pack('h6') # => "\xFF\x0F\x00" + ['fff'].pack('h7') # => "\xFF\x0F\x00\x00" + ['fff'].pack('h8') # => "\xFF\x0F\x00\x00" + + "\x01\xfe".unpack('h*') # => ["10ef"] + "\x01\xfe".unpack('h0') # => [""] + "\x01\xfe".unpack('h1') # => ["1"] + "\x01\xfe".unpack('h2') # => ["10"] + "\x01\xfe".unpack('h3') # => ["10e"] + "\x01\xfe".unpack('h4') # => ["10ef"] + "\x01\xfe".unpack('h5') # => ["10ef"] + +==== Pointer \String Directives + +- <tt>'P'</tt> - Pointer to a structure (fixed-length string): + + s = ['abc'].pack('P') # => "\xE0O\x7F\xE5\xA1\x01\x00\x00" + s.unpack('P*') # => ["abc"] + ".".unpack("P") # => [] + ("\0" * 8).unpack("P") # => [nil] + [nil].pack("P") # => "\x00\x00\x00\x00\x00\x00\x00\x00" + +- <tt>'p'</tt> - Pointer to a null-terminated string: + + s = ['abc'].pack('p') # => "(\xE4u\xE5\xA1\x01\x00\x00" + s.unpack('p*') # => ["abc"] + ".".unpack("p") # => [] + ("\0" * 8).unpack("p") # => [nil] + [nil].pack("p") # => "\x00\x00\x00\x00\x00\x00\x00\x00" + +==== Other \String Directives + +- <tt>'M'</tt> - Quoted printable, MIME encoding; + text mode, but input must use LF and output LF; + (see {RFC 2045}[https://www.ietf.org/rfc/rfc2045.txt]): + + ["a b c\td \ne"].pack('M') # => "a b c\td =\n\ne=\n" + ["\0"].pack('M') # => "=00=\n" + + ["a"*1023].pack('M') == ("a"*73+"=\n")*14+"a=\n" # => true + ("a"*73+"=\na=\n").unpack('M') == ["a"*74] # => true + (("a"*73+"=\n")*14+"a=\n").unpack('M') == ["a"*1023] # => true + + "a b c\td =\n\ne=\n".unpack('M') # => ["a b c\td \ne"] + "=00=\n".unpack('M') # => ["\x00"] + + "pre=31=32=33after".unpack('M') # => ["pre123after"] + "pre=\nafter".unpack('M') # => ["preafter"] + "pre=\r\nafter".unpack('M') # => ["preafter"] + "pre=".unpack('M') # => ["pre="] + "pre=\r".unpack('M') # => ["pre=\r"] + "pre=hoge".unpack('M') # => ["pre=hoge"] + "pre==31after".unpack('M') # => ["pre==31after"] + "pre===31after".unpack('M') # => ["pre===31after"] + +- <tt>'m'</tt> - Base64 encoded string; + count specifies input bytes between each newline, + rounded down to nearest multiple of 3; + if count is zero, no newlines are added; + (see {RFC 4648}[https://www.ietf.org/rfc/rfc4648.txt]): + + [""].pack('m') # => "" + ["\0"].pack('m') # => "AA==\n" + ["\0\0"].pack('m') # => "AAA=\n" + ["\0\0\0"].pack('m') # => "AAAA\n" + ["\377"].pack('m') # => "/w==\n" + ["\377\377"].pack('m') # => "//8=\n" + ["\377\377\377"].pack('m') # => "////\n" + + "".unpack('m') # => [""] + "AA==\n".unpack('m') # => ["\x00"] + "AAA=\n".unpack('m') # => ["\x00\x00"] + "AAAA\n".unpack('m') # => ["\x00\x00\x00"] + "/w==\n".unpack('m') # => ["\xFF"] + "//8=\n".unpack('m') # => ["\xFF\xFF"] + "////\n".unpack('m') # => ["\xFF\xFF\xFF"] + "A\n".unpack('m') # => [""] + "AA\n".unpack('m') # => ["\x00"] + "AA=\n".unpack('m') # => ["\x00"] + "AAA\n".unpack('m') # => ["\x00\x00"] + + [""].pack('m0') # => "" + ["\0"].pack('m0') # => "AA==" + ["\0\0"].pack('m0') # => "AAA=" + ["\0\0\0"].pack('m0') # => "AAAA" + ["\377"].pack('m0') # => "/w==" + ["\377\377"].pack('m0') # => "//8=" + ["\377\377\377"].pack('m0') # => "////" + + "".unpack('m0') # => [""] + "AA==".unpack('m0') # => ["\x00"] + "AAA=".unpack('m0') # => ["\x00\x00"] + "AAAA".unpack('m0') # => ["\x00\x00\x00"] + "/w==".unpack('m0') # => ["\xFF"] + "//8=".unpack('m0') # => ["\xFF\xFF"] + "////".unpack('m0') # => ["\xFF\xFF\xFF"] + +- <tt>'u'</tt> - UU-encoded string: + + [0].pack("U") # => "\u0000" + [0x3fffffff].pack("U") # => "\xFC\xBF\xBF\xBF\xBF\xBF" + [0x40000000].pack("U") # => "\xFD\x80\x80\x80\x80\x80" + [0x7fffffff].pack("U") # => "\xFD\xBF\xBF\xBF\xBF\xBF" + +=== Offset Directives + +- <tt>'@'</tt> - Begin packing at the given byte offset; + for packing, null fill if necessary: + + [1, 2].pack("C@0C") # => "\x02" + [1, 2].pack("C@1C") # => "\x01\x02" + [1, 2].pack("C@5C") # => "\x01\x00\x00\x00\x00\x02" + + "\x01\x00\x00\x02".unpack("C@3C") # => [1, 2] + "\x00".unpack("@1C") # => [nil] + +- <tt>'X'</tt> - Back up a byte: + + [0, 1, 2].pack("CCXC") # => "\x00\x02" + [0, 1, 2].pack("CCX2C") # => "\x02" + "\x00\x02".unpack("CCXC") # => [0, 2, 2] + +=== Null Byte Direcive + +- <tt>'x'</tt> - Null byte: + + [].pack("x0") # => "" + [].pack("x") # => "\x00" + [].pack("x8") # => "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x02".unpack("CxC") # => [0, 2] diff --git a/doc/regexp.rdoc b/doc/regexp.rdoc index a7e2a0786e..92c7ecf66e 100644 --- a/doc/regexp.rdoc +++ b/doc/regexp.rdoc @@ -28,7 +28,7 @@ Specifically, <tt>/st/</tt> requires that the string contains the letter _s_ followed by the letter _t_, so it matches _haystack_, also. Note that any Regexp matching will raise a RuntimeError if timeout is set and -exceeded. See "Timeout" section in detail. +exceeded. See {"Timeout"}[#label-Timeout] section in detail. == \Regexp Interpolation @@ -781,7 +781,7 @@ with <i>a{0,29}</i>: == Timeout -There are two APIs to set timeout. One is Timeout.timeout=, which is +There are two APIs to set timeout. One is Regexp.timeout=, which is process-global configuration of timeout for Regexp matching. Regexp.timeout = 3 @@ -796,6 +796,6 @@ The other is timeout keyword of Regexp.new. When using Regexps to process untrusted input, you should use the timeout feature to avoid excessive backtracking. Otherwise, a malicious user can -provide input to Regexp causing Denail-of-Service attack. +provide input to Regexp causing Denial-of-Service attack. Note that the timeout is not set by default because an appropriate limit highly depends on an application requirement and context. 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 c2521eba42..67b2ffa5f0 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -8,16 +8,20 @@ YJIT - Yet Another Ruby JIT =========================== -**DISCLAIMER: Please note that this project is experimental. It is very much a work in progress, it may cause your software to crash, and current performance results will vary widely, especially on larger applications.** - 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. -To simplify development, we currently support only macOS and Linux on x86-64, but an ARM64 backend -is part of future plans. +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> + If you're using YJIT in production, please + <a href="mailto:maxime.chevalierboisvert@shopify.com">share your success stories with us!</a> + </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) @@ -27,28 +31,31 @@ If you wish to learn more about the approach taken, here are some conference tal - ECOOP 2015 talk: [Simple and Effective Type Check Removal through Lazy Basic Block Versioning](https://www.youtube.com/watch?v=S-aHBuoiYE0) - ECOOP 2015 paper: [Simple and Effective Type Check Removal through Lazy Basic Block Versioning](https://arxiv.org/pdf/1411.0352.pdf) -To cite this repository in your publications, please use this bibtex snippet: - -``` -@misc{yjit_ruby_jit, - author = {Chevalier-Boisvert, Maxime and Wu, Alan and Patterson, Aaron}, - title = {YJIT - Yet Another Ruby JIT}, - year = {2021}, - publisher = {GitHub}, - journal = {GitHub repository}, - howpublished = {\url{https://github.com/Shopify/yjit}}, +To cite YJIT in your publications, please cite the VMIL 2021 paper: + +``` +@inproceedings{yjit_vmil2021, +author = {Chevalier-Boisvert, Maxime and Gibbs, Noah and Boussier, Jean and Wu, Si Xing (Alan) and Patterson, Aaron and Newton, Kevin and Hawthorn, John}, +title = {YJIT: A Basic Block Versioning JIT Compiler for CRuby}, +year = {2021}, +isbn = {9781450391092}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +url = {https://doi.org/10.1145/3486606.3486781}, +doi = {10.1145/3486606.3486781}, +booktitle = {Proceedings of the 13th ACM SIGPLAN International Workshop on Virtual Machines and Intermediate Languages}, +pages = {25–32}, +numpages = {8}, +keywords = {ruby, dynamically typed, compiler, optimization, just-in-time, bytecode}, +location = {Chicago, IL, USA}, +series = {VMIL 2021} } ``` ## Current Limitations -YJIT is a work in progress and as such may not yet be mature enough for mission-critical software. Below is a list of known limitations, all of which we plan to eventually address: - -- No garbage collection for generated code. -- Currently supports only macOS and Linux. -- Supports x86-64 and arm64/aarch64 CPUs only. - -Because there is no GC for generated code yet, your software could run out of executable memory if it is large enough. You can change how much executable memory is allocated using [YJIT's command-line options](#command-line-options). +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 @@ -58,7 +65,7 @@ You will need to install: - A C compiler such as GCC or Clang - GNU Make and Autoconf - The Rust compiler `rustc` and Cargo (if you want to build in dev/debug mode) - - The Rust version must be [>= 1.58.1](../../yjit/Cargo.toml). + - The Rust version must be [>= 1.58.0](../../yjit/Cargo.toml). To install the Rust build toolchain, we suggest following the [recommended installation method][rust-install]. Rust also provides first class [support][editor-tools] for many source code editors. @@ -69,51 +76,62 @@ 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 ``` + before running `./configure`. You can test that YJIT works correctly by running: -``` +```sh # Quick tests found in /bootstraptest make btest @@ -128,71 +146,111 @@ 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 ``` The machine code generated for a given method can be printed by adding `puts RubyVM::YJIT.disasm(method(:method_name))` to a Ruby script. Note that no code will be generated if the method is not compiled. - ### Command-Line Options YJIT supports all command-line options supported by upstream CRuby, but also adds a few YJIT-specific options: - `--yjit`: enable YJIT (disabled by default) -- `--yjit-call-threshold=N`: number of calls after which YJIT begins to compile a function (default 2) -- `--yjit-exec-mem-size=N`: size of the executable memory block to allocate, in MiB (default 256 MiB) -- `--yjit-stats`: produce statistics after the execution of a program (must compile with `cppflags=-DRUBY_DEBUG` 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-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 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. ### Benchmarking 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 -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. +YJIT allocates memory for JIT code and metadata. Enabling YJIT generally results in more memory usage. -- Use exceptions for error recovery only, not as part of normal control-flow +This section goes over tips on minimizing YJIT memory usage in case it uses more than your capacity. + +### 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 compile YJIT in debug mode and 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. - -### Memory Statistics - -YJIT, including in production configuration, keeps track of the size of generated code. If you check YJIT.runtime_stats you can see them: - -``` -$ RUBYOPT="--yjit" irb -irb(main):001:0> RubyVM::YJIT.runtime_stats -=> {:inline_code_size=>331945, :outlined_code_size=>272980} -``` - -These are the size in bytes of generated inlined code and generated outlined code. If the combined sizes for generated code are very close to the total YJIT exec-mem-size (see above), YJIT will stop generating code once the limit is reached. Try to make sure you have enough exec-mem-size for the program you're running. By default YJIT will allocate 268,435,456 bytes (256 MiB) of space for generated inlined and outlined code. +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> YJIT.runtime_stats +irb(main):001:0> RubyVM::YJIT.runtime_stats => {:inline_code_size=>340745, :outlined_code_size=>297664, @@ -206,16 +264,24 @@ irb(main):001:0> 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. @@ -243,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`) @@ -275,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 ``` @@ -317,7 +381,7 @@ instructions below, but there are a few caveats listed further down. First, install Rosetta: -``` +```sh $ softwareupdate --install-rosetta ``` @@ -325,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 @@ -339,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/enc/make_encmake.rb b/enc/make_encmake.rb index bc0597e3f4..fcfc2c9267 100755 --- a/enc/make_encmake.rb +++ b/enc/make_encmake.rb @@ -134,7 +134,7 @@ else end mkin = File.read(File.join($srcdir, "Makefile.in")) mkin.gsub!(/@(#{CONFIG.keys.join('|')})@/) {CONFIG[$1]} -open(ARGV[0], 'wb') {|f| +File.open(ARGV[0], 'wb') {|f| f.puts mkin, dep } if MODULE_TYPE == :static diff --git a/enc/unicode/14.0.0/casefold.h b/enc/unicode/15.0.0/casefold.h index d387cff628..51120d867d 100644 --- a/enc/unicode/14.0.0/casefold.h +++ b/enc/unicode/15.0.0/casefold.h @@ -1,15 +1,15 @@ /* DO NOT EDIT THIS FILE. */ -/* Generated by enc/unicode/case-folding.rb */ +/* Generated by enc-case-folding.rb */ #if defined ONIG_UNICODE_VERSION_STRING && !( \ - ONIG_UNICODE_VERSION_MAJOR == 14 && \ + ONIG_UNICODE_VERSION_MAJOR == 15 && \ ONIG_UNICODE_VERSION_MINOR == 0 && \ ONIG_UNICODE_VERSION_TEENY == 0 && \ 1) # error ONIG_UNICODE_VERSION_STRING mismatch #endif -#define ONIG_UNICODE_VERSION_STRING "14.0.0" -#define ONIG_UNICODE_VERSION_MAJOR 14 +#define ONIG_UNICODE_VERSION_STRING "15.0.0" +#define ONIG_UNICODE_VERSION_MAJOR 15 #define ONIG_UNICODE_VERSION_MINOR 0 #define ONIG_UNICODE_VERSION_TEENY 0 diff --git a/enc/unicode/14.0.0/name2ctype.h b/enc/unicode/15.0.0/name2ctype.h index 61c16bafc2..a2c996423d 100644 --- a/enc/unicode/14.0.0/name2ctype.h +++ b/enc/unicode/15.0.0/name2ctype.h @@ -43,7 +43,7 @@ static const OnigCodePoint CR_NEWLINE[] = { /* 'Alpha': [[:Alpha:]] */ static const OnigCodePoint CR_Alpha[] = { - 722, + 732, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -178,8 +178,7 @@ static const OnigCodePoint CR_Alpha[] = { 0x0bca, 0x0bcc, 0x0bd0, 0x0bd0, 0x0bd7, 0x0bd7, - 0x0c00, 0x0c03, - 0x0c05, 0x0c0c, + 0x0c00, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c28, 0x0c2a, 0x0c39, @@ -202,7 +201,7 @@ static const OnigCodePoint CR_Alpha[] = { 0x0cd5, 0x0cd6, 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d3a, @@ -240,7 +239,7 @@ static const OnigCodePoint CR_Alpha[] = { 0x0f00, 0x0f00, 0x0f40, 0x0f47, 0x0f49, 0x0f6c, - 0x0f71, 0x0f81, + 0x0f71, 0x0f83, 0x0f88, 0x0f97, 0x0f99, 0x0fbc, 0x1000, 0x1036, @@ -542,7 +541,7 @@ static const OnigCodePoint CR_Alpha[] = { 0x10fe0, 0x10ff6, 0x11000, 0x11045, 0x11071, 0x11075, - 0x11082, 0x110b8, + 0x11080, 0x110b8, 0x110c2, 0x110c2, 0x110d0, 0x110e8, 0x11100, 0x11132, @@ -557,7 +556,7 @@ static const OnigCodePoint CR_Alpha[] = { 0x11200, 0x11211, 0x11213, 0x11234, 0x11237, 0x11237, - 0x1123e, 0x1123e, + 0x1123e, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -637,12 +636,16 @@ static const OnigCodePoint CR_Alpha[] = { 0x11d93, 0x11d96, 0x11d98, 0x11d98, 0x11ee0, 0x11ef6, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f40, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -666,7 +669,9 @@ static const OnigCodePoint CR_Alpha[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -705,16 +710,20 @@ static const OnigCodePoint CR_Alpha[] = { 0x1d7aa, 0x1d7c2, 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, + 0x1e4d0, 0x1e4eb, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -760,12 +769,13 @@ static const OnigCodePoint CR_Alpha[] = { 0x1f150, 0x1f169, 0x1f170, 0x1f189, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Alpha */ /* 'Blank': [[:Blank:]] */ @@ -790,7 +800,7 @@ static const OnigCodePoint CR_Cntrl[] = { /* 'Digit': [[:Digit:]] */ static const OnigCodePoint CR_Digit[] = { - 62, + 64, 0x0030, 0x0039, 0x0660, 0x0669, 0x06f0, 0x06f9, @@ -845,19 +855,21 @@ static const OnigCodePoint CR_Digit[] = { 0x11c50, 0x11c59, 0x11d50, 0x11d59, 0x11da0, 0x11da9, + 0x11f50, 0x11f59, 0x16a60, 0x16a69, 0x16ac0, 0x16ac9, 0x16b50, 0x16b59, 0x1d7ce, 0x1d7ff, 0x1e140, 0x1e149, 0x1e2f0, 0x1e2f9, + 0x1e4f0, 0x1e4f9, 0x1e950, 0x1e959, 0x1fbf0, 0x1fbf9, }; /* CR_Digit */ /* 'Graph': [[:Graph:]] */ static const OnigCodePoint CR_Graph[] = { - 703, + 712, 0x0021, 0x007e, 0x00a1, 0x0377, 0x037a, 0x037f, @@ -980,7 +992,7 @@ static const OnigCodePoint CR_Graph[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -1010,7 +1022,7 @@ static const OnigCodePoint CR_Graph[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f47, @@ -1285,7 +1297,7 @@ static const OnigCodePoint CR_Graph[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10ead, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f27, + 0x10efd, 0x10f27, 0x10f30, 0x10f59, 0x10f70, 0x10f89, 0x10fb0, 0x10fcb, @@ -1302,7 +1314,7 @@ static const OnigCodePoint CR_Graph[] = { 0x11180, 0x111df, 0x111e1, 0x111f4, 0x11200, 0x11211, - 0x11213, 0x1123e, + 0x11213, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -1355,6 +1367,7 @@ static const OnigCodePoint CR_Graph[] = { 0x11a00, 0x11a47, 0x11a50, 0x11aa2, 0x11ab0, 0x11af8, + 0x11b00, 0x11b09, 0x11c00, 0x11c08, 0x11c0a, 0x11c36, 0x11c38, 0x11c45, @@ -1376,6 +1389,9 @@ static const OnigCodePoint CR_Graph[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef8, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f59, 0x11fb0, 0x11fb0, 0x11fc0, 0x11ff1, 0x11fff, 0x12399, @@ -1383,8 +1399,7 @@ static const OnigCodePoint CR_Graph[] = { 0x12470, 0x12474, 0x12480, 0x12543, 0x12f90, 0x12ff2, - 0x13000, 0x1342e, - 0x13430, 0x13438, + 0x13000, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -1411,7 +1426,9 @@ static const OnigCodePoint CR_Graph[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -1426,6 +1443,7 @@ static const OnigCodePoint CR_Graph[] = { 0x1d100, 0x1d126, 0x1d129, 0x1d1ea, 0x1d200, 0x1d245, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d300, 0x1d356, 0x1d360, 0x1d378, @@ -1453,11 +1471,14 @@ static const OnigCodePoint CR_Graph[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, @@ -1465,6 +1486,7 @@ static const OnigCodePoint CR_Graph[] = { 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, 0x1e2ff, 0x1e2ff, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -1523,10 +1545,10 @@ static const OnigCodePoint CR_Graph[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -1537,25 +1559,24 @@ static const OnigCodePoint CR_Graph[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0001, 0xe0001, 0xe0020, 0xe007f, 0xe0100, 0xe01ef, @@ -1565,7 +1586,7 @@ static const OnigCodePoint CR_Graph[] = { /* 'Lower': [[:Lower:]] */ static const OnigCodePoint CR_Lower[] = { - 664, + 671, 0x0061, 0x007a, 0x00aa, 0x00aa, 0x00b5, 0x00b5, @@ -1842,7 +1863,7 @@ static const OnigCodePoint CR_Lower[] = { 0x052f, 0x052f, 0x0560, 0x0588, 0x10d0, 0x10fa, - 0x10fd, 0x10ff, + 0x10fc, 0x10ff, 0x13f8, 0x13fd, 0x1c80, 0x1c88, 0x1d00, 0x1dbf, @@ -2182,10 +2203,11 @@ static const OnigCodePoint CR_Lower[] = { 0xa7d5, 0xa7d5, 0xa7d7, 0xa7d7, 0xa7d9, 0xa7d9, + 0xa7f2, 0xa7f4, 0xa7f6, 0xa7f6, 0xa7f8, 0xa7fa, 0xab30, 0xab5a, - 0xab5c, 0xab68, + 0xab5c, 0xab69, 0xab70, 0xabbf, 0xfb00, 0xfb06, 0xfb13, 0xfb17, @@ -2196,6 +2218,10 @@ static const OnigCodePoint CR_Lower[] = { 0x105a3, 0x105b1, 0x105b3, 0x105b9, 0x105bb, 0x105bc, + 0x10780, 0x10780, + 0x10783, 0x10785, + 0x10787, 0x107b0, + 0x107b2, 0x107ba, 0x10cc0, 0x10cf2, 0x118c0, 0x118df, 0x16e60, 0x16e7f, @@ -2229,12 +2255,14 @@ static const OnigCodePoint CR_Lower[] = { 0x1d7cb, 0x1d7cb, 0x1df00, 0x1df09, 0x1df0b, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e922, 0x1e943, }; /* CR_Lower */ /* 'Print': [[:Print:]] */ static const OnigCodePoint CR_Print[] = { - 700, + 709, 0x0020, 0x007e, 0x00a0, 0x0377, 0x037a, 0x037f, @@ -2357,7 +2385,7 @@ static const OnigCodePoint CR_Print[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -2387,7 +2415,7 @@ static const OnigCodePoint CR_Print[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f47, @@ -2659,7 +2687,7 @@ static const OnigCodePoint CR_Print[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10ead, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f27, + 0x10efd, 0x10f27, 0x10f30, 0x10f59, 0x10f70, 0x10f89, 0x10fb0, 0x10fcb, @@ -2676,7 +2704,7 @@ static const OnigCodePoint CR_Print[] = { 0x11180, 0x111df, 0x111e1, 0x111f4, 0x11200, 0x11211, - 0x11213, 0x1123e, + 0x11213, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -2729,6 +2757,7 @@ static const OnigCodePoint CR_Print[] = { 0x11a00, 0x11a47, 0x11a50, 0x11aa2, 0x11ab0, 0x11af8, + 0x11b00, 0x11b09, 0x11c00, 0x11c08, 0x11c0a, 0x11c36, 0x11c38, 0x11c45, @@ -2750,6 +2779,9 @@ static const OnigCodePoint CR_Print[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef8, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f59, 0x11fb0, 0x11fb0, 0x11fc0, 0x11ff1, 0x11fff, 0x12399, @@ -2757,8 +2789,7 @@ static const OnigCodePoint CR_Print[] = { 0x12470, 0x12474, 0x12480, 0x12543, 0x12f90, 0x12ff2, - 0x13000, 0x1342e, - 0x13430, 0x13438, + 0x13000, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -2785,7 +2816,9 @@ static const OnigCodePoint CR_Print[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -2800,6 +2833,7 @@ static const OnigCodePoint CR_Print[] = { 0x1d100, 0x1d126, 0x1d129, 0x1d1ea, 0x1d200, 0x1d245, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d300, 0x1d356, 0x1d360, 0x1d378, @@ -2827,11 +2861,14 @@ static const OnigCodePoint CR_Print[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, @@ -2839,6 +2876,7 @@ static const OnigCodePoint CR_Print[] = { 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, 0x1e2ff, 0x1e2ff, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -2897,10 +2935,10 @@ static const OnigCodePoint CR_Print[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -2911,25 +2949,24 @@ static const OnigCodePoint CR_Print[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0001, 0xe0001, 0xe0020, 0xe007f, 0xe0100, 0xe01ef, @@ -2939,7 +2976,7 @@ static const OnigCodePoint CR_Print[] = { /* 'XPosixPunct': [[:Punct:]] */ static const OnigCodePoint CR_XPosixPunct[] = { - 184, + 186, 0x0021, 0x002f, 0x003a, 0x0040, 0x005b, 0x0060, @@ -3109,9 +3146,11 @@ static const OnigCodePoint CR_XPosixPunct[] = { 0x11a3f, 0x11a46, 0x11a9a, 0x11a9c, 0x11a9e, 0x11aa2, + 0x11b00, 0x11b09, 0x11c41, 0x11c45, 0x11c70, 0x11c71, 0x11ef7, 0x11ef8, + 0x11f43, 0x11f4f, 0x11fff, 0x11fff, 0x12470, 0x12474, 0x12ff1, 0x12ff2, @@ -3807,7 +3846,7 @@ static const OnigCodePoint CR_XDigit[] = { /* 'Word': [[:Word:]] */ static const OnigCodePoint CR_Word[] = { - 758, + 770, 0x0030, 0x0039, 0x0041, 0x005a, 0x005f, 0x005f, @@ -3965,7 +4004,7 @@ static const OnigCodePoint CR_Word[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -3998,7 +4037,7 @@ static const OnigCodePoint CR_Word[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f00, @@ -4311,7 +4350,7 @@ static const OnigCodePoint CR_Word[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10eac, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f1c, + 0x10efd, 0x10f1c, 0x10f27, 0x10f27, 0x10f30, 0x10f50, 0x10f70, 0x10f85, @@ -4334,7 +4373,7 @@ static const OnigCodePoint CR_Word[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x11237, - 0x1123e, 0x1123e, + 0x1123e, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -4415,12 +4454,17 @@ static const OnigCodePoint CR_Word[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef6, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f42, + 0x11f50, 0x11f59, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13440, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -4448,7 +4492,9 @@ static const OnigCodePoint CR_Word[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -4502,17 +4548,21 @@ static const OnigCodePoint CR_Word[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -4559,18 +4609,19 @@ static const OnigCodePoint CR_Word[] = { 0x1f170, 0x1f189, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0100, 0xe01ef, }; /* CR_Word */ /* 'Alnum': [[:Alnum:]] */ static const OnigCodePoint CR_Alnum[] = { - 760, + 772, 0x0030, 0x0039, 0x0041, 0x005a, 0x0061, 0x007a, @@ -4709,8 +4760,7 @@ static const OnigCodePoint CR_Alnum[] = { 0x0bd0, 0x0bd0, 0x0bd7, 0x0bd7, 0x0be6, 0x0bef, - 0x0c00, 0x0c03, - 0x0c05, 0x0c0c, + 0x0c00, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c28, 0x0c2a, 0x0c39, @@ -4735,7 +4785,7 @@ static const OnigCodePoint CR_Alnum[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d3a, @@ -4778,7 +4828,7 @@ static const OnigCodePoint CR_Alnum[] = { 0x0f20, 0x0f29, 0x0f40, 0x0f47, 0x0f49, 0x0f6c, - 0x0f71, 0x0f81, + 0x0f71, 0x0f83, 0x0f88, 0x0f97, 0x0f99, 0x0fbc, 0x1000, 0x1036, @@ -5088,7 +5138,7 @@ static const OnigCodePoint CR_Alnum[] = { 0x11000, 0x11045, 0x11066, 0x1106f, 0x11071, 0x11075, - 0x11082, 0x110b8, + 0x11080, 0x110b8, 0x110c2, 0x110c2, 0x110d0, 0x110e8, 0x110f0, 0x110f9, @@ -5104,7 +5154,7 @@ static const OnigCodePoint CR_Alnum[] = { 0x11200, 0x11211, 0x11213, 0x11234, 0x11237, 0x11237, - 0x1123e, 0x1123e, + 0x1123e, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -5194,12 +5244,17 @@ static const OnigCodePoint CR_Alnum[] = { 0x11d98, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef6, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f40, + 0x11f50, 0x11f59, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -5226,7 +5281,9 @@ static const OnigCodePoint CR_Alnum[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -5266,11 +5323,14 @@ static const OnigCodePoint CR_Alnum[] = { 0x1d7c4, 0x1d7cb, 0x1d7ce, 0x1d7ff, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e140, 0x1e149, @@ -5278,6 +5338,8 @@ static const OnigCodePoint CR_Alnum[] = { 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, 0x1e2f0, 0x1e2f9, + 0x1e4d0, 0x1e4eb, + 0x1e4f0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -5325,12 +5387,13 @@ static const OnigCodePoint CR_Alnum[] = { 0x1f170, 0x1f189, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Alnum */ /* 'ASCII': [[:ASCII:]] */ @@ -5341,7 +5404,7 @@ static const OnigCodePoint CR_ASCII[] = { /* 'Punct' */ static const OnigCodePoint CR_Punct[] = { - 189, + 191, 0x0021, 0x0023, 0x0025, 0x002a, 0x002c, 0x002f, @@ -5516,9 +5579,11 @@ static const OnigCodePoint CR_Punct[] = { 0x11a3f, 0x11a46, 0x11a9a, 0x11a9c, 0x11a9e, 0x11aa2, + 0x11b00, 0x11b09, 0x11c41, 0x11c45, 0x11c70, 0x11c71, 0x11ef7, 0x11ef8, + 0x11f43, 0x11f4f, 0x11fff, 0x11fff, 0x12470, 0x12474, 0x12ff1, 0x12ff2, @@ -5542,7 +5607,7 @@ static const OnigCodePoint CR_Any[] = { /* 'Assigned': - */ static const OnigCodePoint CR_Assigned[] = { - 698, + 707, 0x0000, 0x0377, 0x037a, 0x037f, 0x0384, 0x038a, @@ -5664,7 +5729,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -5694,7 +5759,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f47, @@ -5965,7 +6030,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10ead, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f27, + 0x10efd, 0x10f27, 0x10f30, 0x10f59, 0x10f70, 0x10f89, 0x10fb0, 0x10fcb, @@ -5982,7 +6047,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x11180, 0x111df, 0x111e1, 0x111f4, 0x11200, 0x11211, - 0x11213, 0x1123e, + 0x11213, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -6035,6 +6100,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x11a00, 0x11a47, 0x11a50, 0x11aa2, 0x11ab0, 0x11af8, + 0x11b00, 0x11b09, 0x11c00, 0x11c08, 0x11c0a, 0x11c36, 0x11c38, 0x11c45, @@ -6056,6 +6122,9 @@ static const OnigCodePoint CR_Assigned[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef8, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f59, 0x11fb0, 0x11fb0, 0x11fc0, 0x11ff1, 0x11fff, 0x12399, @@ -6063,8 +6132,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x12470, 0x12474, 0x12480, 0x12543, 0x12f90, 0x12ff2, - 0x13000, 0x1342e, - 0x13430, 0x13438, + 0x13000, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -6091,7 +6159,9 @@ static const OnigCodePoint CR_Assigned[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -6106,6 +6176,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x1d100, 0x1d126, 0x1d129, 0x1d1ea, 0x1d200, 0x1d245, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d300, 0x1d356, 0x1d360, 0x1d378, @@ -6133,11 +6204,14 @@ static const OnigCodePoint CR_Assigned[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, @@ -6145,6 +6219,7 @@ static const OnigCodePoint CR_Assigned[] = { 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, 0x1e2ff, 0x1e2ff, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -6203,10 +6278,10 @@ static const OnigCodePoint CR_Assigned[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -6217,25 +6292,24 @@ static const OnigCodePoint CR_Assigned[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0001, 0xe0001, 0xe0020, 0xe007f, 0xe0100, 0xe01ef, @@ -6245,7 +6319,7 @@ static const OnigCodePoint CR_Assigned[] = { /* 'C': Major Category */ static const OnigCodePoint CR_C[] = { - 701, + 712, 0x0000, 0x001f, 0x007f, 0x009f, 0x00ad, 0x00ad, @@ -6372,7 +6446,7 @@ static const OnigCodePoint CR_C[] = { 0x0cdf, 0x0cdf, 0x0ce4, 0x0ce5, 0x0cf0, 0x0cf0, - 0x0cf3, 0x0cff, + 0x0cf4, 0x0cff, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d45, 0x0d45, @@ -6402,7 +6476,7 @@ static const OnigCodePoint CR_C[] = { 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, - 0x0ece, 0x0ecf, + 0x0ecf, 0x0ecf, 0x0eda, 0x0edb, 0x0ee0, 0x0eff, 0x0f48, 0x0f48, @@ -6674,7 +6748,7 @@ static const OnigCodePoint CR_C[] = { 0x10e7f, 0x10e7f, 0x10eaa, 0x10eaa, 0x10eae, 0x10eaf, - 0x10eb2, 0x10eff, + 0x10eb2, 0x10efc, 0x10f28, 0x10f2f, 0x10f5a, 0x10f6f, 0x10f8a, 0x10faf, @@ -6692,7 +6766,7 @@ static const OnigCodePoint CR_C[] = { 0x111e0, 0x111e0, 0x111f5, 0x111ff, 0x11212, 0x11212, - 0x1123f, 0x1127f, + 0x11242, 0x1127f, 0x11287, 0x11287, 0x11289, 0x11289, 0x1128e, 0x1128e, @@ -6744,7 +6818,8 @@ static const OnigCodePoint CR_C[] = { 0x119e5, 0x119ff, 0x11a48, 0x11a4f, 0x11aa3, 0x11aaf, - 0x11af9, 0x11bff, + 0x11af9, 0x11aff, + 0x11b0a, 0x11bff, 0x11c09, 0x11c09, 0x11c37, 0x11c37, 0x11c46, 0x11c4f, @@ -6765,7 +6840,10 @@ static const OnigCodePoint CR_C[] = { 0x11d92, 0x11d92, 0x11d99, 0x11d9f, 0x11daa, 0x11edf, - 0x11ef9, 0x11faf, + 0x11ef9, 0x11eff, + 0x11f11, 0x11f11, + 0x11f3b, 0x11f3d, + 0x11f5a, 0x11faf, 0x11fb1, 0x11fbf, 0x11ff2, 0x11ffe, 0x1239a, 0x123ff, @@ -6773,7 +6851,8 @@ static const OnigCodePoint CR_C[] = { 0x12475, 0x1247f, 0x12544, 0x12f8f, 0x12ff3, 0x12fff, - 0x1342f, 0x143ff, + 0x13430, 0x1343f, + 0x13456, 0x143ff, 0x14647, 0x167ff, 0x16a39, 0x16a3f, 0x16a5f, 0x16a5f, @@ -6799,8 +6878,10 @@ static const OnigCodePoint CR_C[] = { 0x1aff4, 0x1aff4, 0x1affc, 0x1affc, 0x1afff, 0x1afff, - 0x1b123, 0x1b14f, - 0x1b153, 0x1b163, + 0x1b123, 0x1b131, + 0x1b133, 0x1b14f, + 0x1b153, 0x1b154, + 0x1b156, 0x1b163, 0x1b168, 0x1b16f, 0x1b2fc, 0x1bbff, 0x1bc6b, 0x1bc6f, @@ -6815,7 +6896,8 @@ static const OnigCodePoint CR_C[] = { 0x1d127, 0x1d128, 0x1d173, 0x1d17a, 0x1d1eb, 0x1d1ff, - 0x1d246, 0x1d2df, + 0x1d246, 0x1d2bf, + 0x1d2d4, 0x1d2df, 0x1d2f4, 0x1d2ff, 0x1d357, 0x1d35f, 0x1d379, 0x1d3ff, @@ -6842,19 +6924,23 @@ static const OnigCodePoint CR_C[] = { 0x1da8c, 0x1da9a, 0x1daa0, 0x1daa0, 0x1dab0, 0x1deff, - 0x1df1f, 0x1dfff, + 0x1df1f, 0x1df24, + 0x1df2b, 0x1dfff, 0x1e007, 0x1e007, 0x1e019, 0x1e01a, 0x1e022, 0x1e022, 0x1e025, 0x1e025, - 0x1e02b, 0x1e0ff, + 0x1e02b, 0x1e02f, + 0x1e06e, 0x1e08e, + 0x1e090, 0x1e0ff, 0x1e12d, 0x1e12f, 0x1e13e, 0x1e13f, 0x1e14a, 0x1e14d, 0x1e150, 0x1e28f, 0x1e2af, 0x1e2bf, 0x1e2fa, 0x1e2fe, - 0x1e300, 0x1e7df, + 0x1e300, 0x1e4cf, + 0x1e4fa, 0x1e7df, 0x1e7e7, 0x1e7e7, 0x1e7ec, 0x1e7ec, 0x1e7ef, 0x1e7ef, @@ -6912,11 +6998,11 @@ static const OnigCodePoint CR_C[] = { 0x1f249, 0x1f24f, 0x1f252, 0x1f25f, 0x1f266, 0x1f2ff, - 0x1f6d8, 0x1f6dc, + 0x1f6d8, 0x1f6db, 0x1f6ed, 0x1f6ef, 0x1f6fd, 0x1f6ff, - 0x1f774, 0x1f77f, - 0x1f7d9, 0x1f7df, + 0x1f777, 0x1f77a, + 0x1f7da, 0x1f7df, 0x1f7ec, 0x1f7ef, 0x1f7f1, 0x1f7ff, 0x1f80c, 0x1f80f, @@ -6927,25 +7013,24 @@ static const OnigCodePoint CR_C[] = { 0x1f8b2, 0x1f8ff, 0x1fa54, 0x1fa5f, 0x1fa6e, 0x1fa6f, - 0x1fa75, 0x1fa77, 0x1fa7d, 0x1fa7f, - 0x1fa87, 0x1fa8f, - 0x1faad, 0x1faaf, - 0x1fabb, 0x1fabf, - 0x1fac6, 0x1facf, - 0x1fada, 0x1fadf, - 0x1fae8, 0x1faef, - 0x1faf7, 0x1faff, + 0x1fa89, 0x1fa8f, + 0x1fabe, 0x1fabe, + 0x1fac6, 0x1facd, + 0x1fadc, 0x1fadf, + 0x1fae9, 0x1faef, + 0x1faf9, 0x1faff, 0x1fb93, 0x1fb93, 0x1fbcb, 0x1fbef, 0x1fbfa, 0x1ffff, 0x2a6e0, 0x2a6ff, - 0x2b739, 0x2b73f, + 0x2b73a, 0x2b73f, 0x2b81e, 0x2b81f, 0x2cea2, 0x2ceaf, 0x2ebe1, 0x2f7ff, 0x2fa1e, 0x2ffff, - 0x3134b, 0xe00ff, + 0x3134b, 0x3134f, + 0x323b0, 0xe00ff, 0xe01f0, 0x10ffff, }; /* CR_C */ @@ -6971,7 +7056,7 @@ static const OnigCodePoint CR_Cf[] = { 0xfff9, 0xfffb, 0x110bd, 0x110bd, 0x110cd, 0x110cd, - 0x13430, 0x13438, + 0x13430, 0x1343f, 0x1bca0, 0x1bca3, 0x1d173, 0x1d17a, 0xe0001, 0xe0001, @@ -6980,7 +7065,7 @@ static const OnigCodePoint CR_Cf[] = { /* 'Cn': General Category */ static const OnigCodePoint CR_Cn[] = { - 698, + 707, 0x0378, 0x0379, 0x0380, 0x0383, 0x038b, 0x038b, @@ -7102,7 +7187,7 @@ static const OnigCodePoint CR_Cn[] = { 0x0cdf, 0x0cdf, 0x0ce4, 0x0ce5, 0x0cf0, 0x0cf0, - 0x0cf3, 0x0cff, + 0x0cf4, 0x0cff, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d45, 0x0d45, @@ -7132,7 +7217,7 @@ static const OnigCodePoint CR_Cn[] = { 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, - 0x0ece, 0x0ecf, + 0x0ecf, 0x0ecf, 0x0eda, 0x0edb, 0x0ee0, 0x0eff, 0x0f48, 0x0f48, @@ -7402,7 +7487,7 @@ static const OnigCodePoint CR_Cn[] = { 0x10e7f, 0x10e7f, 0x10eaa, 0x10eaa, 0x10eae, 0x10eaf, - 0x10eb2, 0x10eff, + 0x10eb2, 0x10efc, 0x10f28, 0x10f2f, 0x10f5a, 0x10f6f, 0x10f8a, 0x10faf, @@ -7420,7 +7505,7 @@ static const OnigCodePoint CR_Cn[] = { 0x111e0, 0x111e0, 0x111f5, 0x111ff, 0x11212, 0x11212, - 0x1123f, 0x1127f, + 0x11242, 0x1127f, 0x11287, 0x11287, 0x11289, 0x11289, 0x1128e, 0x1128e, @@ -7472,7 +7557,8 @@ static const OnigCodePoint CR_Cn[] = { 0x119e5, 0x119ff, 0x11a48, 0x11a4f, 0x11aa3, 0x11aaf, - 0x11af9, 0x11bff, + 0x11af9, 0x11aff, + 0x11b0a, 0x11bff, 0x11c09, 0x11c09, 0x11c37, 0x11c37, 0x11c46, 0x11c4f, @@ -7493,7 +7579,10 @@ static const OnigCodePoint CR_Cn[] = { 0x11d92, 0x11d92, 0x11d99, 0x11d9f, 0x11daa, 0x11edf, - 0x11ef9, 0x11faf, + 0x11ef9, 0x11eff, + 0x11f11, 0x11f11, + 0x11f3b, 0x11f3d, + 0x11f5a, 0x11faf, 0x11fb1, 0x11fbf, 0x11ff2, 0x11ffe, 0x1239a, 0x123ff, @@ -7501,8 +7590,7 @@ static const OnigCodePoint CR_Cn[] = { 0x12475, 0x1247f, 0x12544, 0x12f8f, 0x12ff3, 0x12fff, - 0x1342f, 0x1342f, - 0x13439, 0x143ff, + 0x13456, 0x143ff, 0x14647, 0x167ff, 0x16a39, 0x16a3f, 0x16a5f, 0x16a5f, @@ -7528,8 +7616,10 @@ static const OnigCodePoint CR_Cn[] = { 0x1aff4, 0x1aff4, 0x1affc, 0x1affc, 0x1afff, 0x1afff, - 0x1b123, 0x1b14f, - 0x1b153, 0x1b163, + 0x1b123, 0x1b131, + 0x1b133, 0x1b14f, + 0x1b153, 0x1b154, + 0x1b156, 0x1b163, 0x1b168, 0x1b16f, 0x1b2fc, 0x1bbff, 0x1bc6b, 0x1bc6f, @@ -7543,7 +7633,8 @@ static const OnigCodePoint CR_Cn[] = { 0x1d0f6, 0x1d0ff, 0x1d127, 0x1d128, 0x1d1eb, 0x1d1ff, - 0x1d246, 0x1d2df, + 0x1d246, 0x1d2bf, + 0x1d2d4, 0x1d2df, 0x1d2f4, 0x1d2ff, 0x1d357, 0x1d35f, 0x1d379, 0x1d3ff, @@ -7570,19 +7661,23 @@ static const OnigCodePoint CR_Cn[] = { 0x1da8c, 0x1da9a, 0x1daa0, 0x1daa0, 0x1dab0, 0x1deff, - 0x1df1f, 0x1dfff, + 0x1df1f, 0x1df24, + 0x1df2b, 0x1dfff, 0x1e007, 0x1e007, 0x1e019, 0x1e01a, 0x1e022, 0x1e022, 0x1e025, 0x1e025, - 0x1e02b, 0x1e0ff, + 0x1e02b, 0x1e02f, + 0x1e06e, 0x1e08e, + 0x1e090, 0x1e0ff, 0x1e12d, 0x1e12f, 0x1e13e, 0x1e13f, 0x1e14a, 0x1e14d, 0x1e150, 0x1e28f, 0x1e2af, 0x1e2bf, 0x1e2fa, 0x1e2fe, - 0x1e300, 0x1e7df, + 0x1e300, 0x1e4cf, + 0x1e4fa, 0x1e7df, 0x1e7e7, 0x1e7e7, 0x1e7ec, 0x1e7ec, 0x1e7ef, 0x1e7ef, @@ -7640,11 +7735,11 @@ static const OnigCodePoint CR_Cn[] = { 0x1f249, 0x1f24f, 0x1f252, 0x1f25f, 0x1f266, 0x1f2ff, - 0x1f6d8, 0x1f6dc, + 0x1f6d8, 0x1f6db, 0x1f6ed, 0x1f6ef, 0x1f6fd, 0x1f6ff, - 0x1f774, 0x1f77f, - 0x1f7d9, 0x1f7df, + 0x1f777, 0x1f77a, + 0x1f7da, 0x1f7df, 0x1f7ec, 0x1f7ef, 0x1f7f1, 0x1f7ff, 0x1f80c, 0x1f80f, @@ -7655,25 +7750,24 @@ static const OnigCodePoint CR_Cn[] = { 0x1f8b2, 0x1f8ff, 0x1fa54, 0x1fa5f, 0x1fa6e, 0x1fa6f, - 0x1fa75, 0x1fa77, 0x1fa7d, 0x1fa7f, - 0x1fa87, 0x1fa8f, - 0x1faad, 0x1faaf, - 0x1fabb, 0x1fabf, - 0x1fac6, 0x1facf, - 0x1fada, 0x1fadf, - 0x1fae8, 0x1faef, - 0x1faf7, 0x1faff, + 0x1fa89, 0x1fa8f, + 0x1fabe, 0x1fabe, + 0x1fac6, 0x1facd, + 0x1fadc, 0x1fadf, + 0x1fae9, 0x1faef, + 0x1faf9, 0x1faff, 0x1fb93, 0x1fb93, 0x1fbcb, 0x1fbef, 0x1fbfa, 0x1ffff, 0x2a6e0, 0x2a6ff, - 0x2b739, 0x2b73f, + 0x2b73a, 0x2b73f, 0x2b81e, 0x2b81f, 0x2cea2, 0x2ceaf, 0x2ebe1, 0x2f7ff, 0x2fa1e, 0x2ffff, - 0x3134b, 0xe0000, + 0x3134b, 0x3134f, + 0x323b0, 0xe0000, 0xe0002, 0xe001f, 0xe0080, 0xe00ff, 0xe01f0, 0xeffff, @@ -7697,7 +7791,7 @@ static const OnigCodePoint CR_Cs[] = { /* 'L': Major Category */ static const OnigCodePoint CR_L[] = { - 648, + 659, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -8167,6 +8261,7 @@ static const OnigCodePoint CR_L[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x1122b, + 0x1123f, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -8229,11 +8324,15 @@ static const OnigCodePoint CR_L[] = { 0x11d6a, 0x11d89, 0x11d98, 0x11d98, 0x11ee0, 0x11ef2, + 0x11f02, 0x11f02, + 0x11f04, 0x11f10, + 0x11f12, 0x11f33, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -8256,7 +8355,9 @@ static const OnigCodePoint CR_L[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -8294,11 +8395,14 @@ static const OnigCodePoint CR_L[] = { 0x1d7aa, 0x1d7c2, 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, + 0x1e4d0, 0x1e4eb, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -8340,17 +8444,18 @@ static const OnigCodePoint CR_L[] = { 0x1eea5, 0x1eea9, 0x1eeab, 0x1eebb, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_L */ /* 'LC': General Category */ static const OnigCodePoint CR_LC[] = { - 142, + 143, 0x0041, 0x005a, 0x0061, 0x007a, 0x00b5, 0x00b5, @@ -8492,12 +8597,13 @@ static const OnigCodePoint CR_LC[] = { 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df09, 0x1df0b, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e900, 0x1e943, }; /* CR_LC */ /* 'Ll': General Category */ static const OnigCodePoint CR_Ll[] = { - 657, + 658, 0x0061, 0x007a, 0x00b5, 0x00b5, 0x00df, 0x00f6, @@ -9154,12 +9260,13 @@ static const OnigCodePoint CR_Ll[] = { 0x1d7cb, 0x1d7cb, 0x1df00, 0x1df09, 0x1df0b, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e922, 0x1e943, }; /* CR_Ll */ /* 'Lm': General Category */ static const OnigCodePoint CR_Lm[] = { - 69, + 71, 0x02b0, 0x02c1, 0x02c6, 0x02d1, 0x02e0, 0x02e4, @@ -9227,13 +9334,15 @@ static const OnigCodePoint CR_Lm[] = { 0x1aff0, 0x1aff3, 0x1aff5, 0x1affb, 0x1affd, 0x1affe, + 0x1e030, 0x1e06d, 0x1e137, 0x1e13d, + 0x1e4eb, 0x1e4eb, 0x1e94b, 0x1e94b, }; /* CR_Lm */ /* 'Lo': General Category */ static const OnigCodePoint CR_Lo[] = { - 501, + 510, 0x00aa, 0x00aa, 0x00ba, 0x00ba, 0x01bb, 0x01bb, @@ -9598,6 +9707,7 @@ static const OnigCodePoint CR_Lo[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x1122b, + 0x1123f, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -9659,11 +9769,15 @@ static const OnigCodePoint CR_Lo[] = { 0x11d6a, 0x11d89, 0x11d98, 0x11d98, 0x11ee0, 0x11ef2, + 0x11f02, 0x11f02, + 0x11f04, 0x11f10, + 0x11f12, 0x11f33, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -9678,7 +9792,9 @@ static const OnigCodePoint CR_Lo[] = { 0x18800, 0x18cd5, 0x18d00, 0x18d08, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -9690,6 +9806,7 @@ static const OnigCodePoint CR_Lo[] = { 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, + 0x1e4d0, 0x1e4ea, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -9729,12 +9846,13 @@ static const OnigCodePoint CR_Lo[] = { 0x1eea5, 0x1eea9, 0x1eeab, 0x1eebb, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Lo */ /* 'Lt': General Category */ @@ -10405,7 +10523,7 @@ static const OnigCodePoint CR_Lu[] = { /* 'M': Major Category */ static const OnigCodePoint CR_M[] = { - 299, + 310, 0x0300, 0x036f, 0x0483, 0x0489, 0x0591, 0x05bd, @@ -10486,6 +10604,7 @@ static const OnigCodePoint CR_M[] = { 0x0cca, 0x0ccd, 0x0cd5, 0x0cd6, 0x0ce2, 0x0ce3, + 0x0cf3, 0x0cf3, 0x0d00, 0x0d03, 0x0d3b, 0x0d3c, 0x0d3e, 0x0d44, @@ -10504,7 +10623,7 @@ static const OnigCodePoint CR_M[] = { 0x0e47, 0x0e4e, 0x0eb1, 0x0eb1, 0x0eb4, 0x0ebc, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0f18, 0x0f19, 0x0f35, 0x0f35, 0x0f37, 0x0f37, @@ -10606,6 +10725,7 @@ static const OnigCodePoint CR_M[] = { 0x10ae5, 0x10ae6, 0x10d24, 0x10d27, 0x10eab, 0x10eac, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11000, 0x11002, @@ -10625,6 +10745,7 @@ static const OnigCodePoint CR_M[] = { 0x111ce, 0x111cf, 0x1122c, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112ea, 0x11300, 0x11303, 0x1133b, 0x1133c, @@ -10672,6 +10793,12 @@ static const OnigCodePoint CR_M[] = { 0x11d90, 0x11d91, 0x11d93, 0x11d97, 0x11ef3, 0x11ef6, + 0x11f00, 0x11f01, + 0x11f03, 0x11f03, + 0x11f34, 0x11f3a, + 0x11f3e, 0x11f42, + 0x13440, 0x13440, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16f4f, 0x16f4f, @@ -10699,9 +10826,11 @@ static const OnigCodePoint CR_M[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e08f, 0x1e08f, 0x1e130, 0x1e136, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, + 0x1e4ec, 0x1e4ef, 0x1e8d0, 0x1e8d6, 0x1e944, 0x1e94a, 0xe0100, 0xe01ef, @@ -10709,7 +10838,7 @@ static const OnigCodePoint CR_M[] = { /* 'Mc': General Category */ static const OnigCodePoint CR_Mc[] = { - 177, + 182, 0x0903, 0x0903, 0x093b, 0x093b, 0x093e, 0x0940, @@ -10745,6 +10874,7 @@ static const OnigCodePoint CR_Mc[] = { 0x0cc7, 0x0cc8, 0x0cca, 0x0ccb, 0x0cd5, 0x0cd6, + 0x0cf3, 0x0cf3, 0x0d02, 0x0d03, 0x0d3e, 0x0d40, 0x0d46, 0x0d48, @@ -10883,6 +11013,10 @@ static const OnigCodePoint CR_Mc[] = { 0x11d93, 0x11d94, 0x11d96, 0x11d96, 0x11ef5, 0x11ef6, + 0x11f03, 0x11f03, + 0x11f34, 0x11f35, + 0x11f3e, 0x11f3f, + 0x11f41, 0x11f41, 0x16f51, 0x16f87, 0x16ff0, 0x16ff1, 0x1d165, 0x1d166, @@ -10901,7 +11035,7 @@ static const OnigCodePoint CR_Me[] = { /* 'Mn': General Category */ static const OnigCodePoint CR_Mn[] = { - 336, + 346, 0x0300, 0x036f, 0x0483, 0x0487, 0x0591, 0x05bd, @@ -10994,7 +11128,7 @@ static const OnigCodePoint CR_Mn[] = { 0x0e47, 0x0e4e, 0x0eb1, 0x0eb1, 0x0eb4, 0x0ebc, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0f18, 0x0f19, 0x0f35, 0x0f35, 0x0f37, 0x0f37, @@ -11125,6 +11259,7 @@ static const OnigCodePoint CR_Mn[] = { 0x10ae5, 0x10ae6, 0x10d24, 0x10d27, 0x10eab, 0x10eac, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11001, 0x11001, @@ -11147,6 +11282,7 @@ static const OnigCodePoint CR_Mn[] = { 0x11234, 0x11234, 0x11236, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112df, 0x112e3, 0x112ea, 0x11300, 0x11301, @@ -11208,6 +11344,12 @@ static const OnigCodePoint CR_Mn[] = { 0x11d95, 0x11d95, 0x11d97, 0x11d97, 0x11ef3, 0x11ef4, + 0x11f00, 0x11f01, + 0x11f36, 0x11f3a, + 0x11f40, 0x11f40, + 0x11f42, 0x11f42, + 0x13440, 0x13440, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16f4f, 0x16f4f, @@ -11232,9 +11374,11 @@ static const OnigCodePoint CR_Mn[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e08f, 0x1e08f, 0x1e130, 0x1e136, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, + 0x1e4ec, 0x1e4ef, 0x1e8d0, 0x1e8d6, 0x1e944, 0x1e94a, 0xe0100, 0xe01ef, @@ -11242,7 +11386,7 @@ static const OnigCodePoint CR_Mn[] = { /* 'N': Major Category */ static const OnigCodePoint CR_N[] = { - 134, + 137, 0x0030, 0x0039, 0x00b2, 0x00b3, 0x00b9, 0x00b9, @@ -11356,6 +11500,7 @@ static const OnigCodePoint CR_N[] = { 0x11c50, 0x11c6c, 0x11d50, 0x11d59, 0x11da0, 0x11da9, + 0x11f50, 0x11f59, 0x11fc0, 0x11fd4, 0x12400, 0x1246e, 0x16a60, 0x16a69, @@ -11363,11 +11508,13 @@ static const OnigCodePoint CR_N[] = { 0x16b50, 0x16b59, 0x16b5b, 0x16b61, 0x16e80, 0x16e96, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d360, 0x1d378, 0x1d7ce, 0x1d7ff, 0x1e140, 0x1e149, 0x1e2f0, 0x1e2f9, + 0x1e4f0, 0x1e4f9, 0x1e8c7, 0x1e8cf, 0x1e950, 0x1e959, 0x1ec71, 0x1ecab, @@ -11401,7 +11548,7 @@ static const OnigCodePoint CR_Nl[] = { /* 'No': General Category */ static const OnigCodePoint CR_No[] = { - 71, + 72, 0x00b2, 0x00b3, 0x00b9, 0x00b9, 0x00bc, 0x00be, @@ -11464,6 +11611,7 @@ static const OnigCodePoint CR_No[] = { 0x11fc0, 0x11fd4, 0x16b5b, 0x16b61, 0x16e80, 0x16e96, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d360, 0x1d378, 0x1e8c7, 0x1e8cf, @@ -11627,7 +11775,7 @@ static const OnigCodePoint CR_Pi[] = { /* 'Po': General Category */ static const OnigCodePoint CR_Po[] = { - 185, + 187, 0x0021, 0x0023, 0x0025, 0x0027, 0x002a, 0x002a, @@ -11798,9 +11946,11 @@ static const OnigCodePoint CR_Po[] = { 0x11a3f, 0x11a46, 0x11a9a, 0x11a9c, 0x11a9e, 0x11aa2, + 0x11b00, 0x11b09, 0x11c41, 0x11c45, 0x11c70, 0x11c71, 0x11ef7, 0x11ef8, + 0x11f43, 0x11f4f, 0x11fff, 0x11fff, 0x12470, 0x12474, 0x12ff1, 0x12ff2, @@ -11901,7 +12051,7 @@ static const OnigCodePoint CR_Ps[] = { /* 'S': Major Category */ static const OnigCodePoint CR_S[] = { - 234, + 232, 0x0024, 0x0024, 0x002b, 0x002b, 0x003c, 0x003e, @@ -12111,10 +12261,10 @@ static const OnigCodePoint CR_S[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -12125,15 +12275,13 @@ static const OnigCodePoint CR_S[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, }; /* CR_S */ @@ -12271,7 +12419,7 @@ static const OnigCodePoint CR_Sm[] = { /* 'So': General Category */ static const OnigCodePoint CR_So[] = { - 186, + 184, 0x00a6, 0x00a6, 0x00a9, 0x00a9, 0x00ae, 0x00ae, @@ -12433,10 +12581,10 @@ static const OnigCodePoint CR_So[] = { 0x1f260, 0x1f265, 0x1f300, 0x1f3fa, 0x1f400, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -12447,15 +12595,13 @@ static const OnigCodePoint CR_So[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, }; /* CR_So */ @@ -12651,7 +12797,7 @@ static const OnigCodePoint CR_Math[] = { /* 'Cased': Derived Property */ static const OnigCodePoint CR_Cased[] = { - 151, + 157, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -12683,7 +12829,7 @@ static const OnigCodePoint CR_Cased[] = { 0x10c7, 0x10c7, 0x10cd, 0x10cd, 0x10d0, 0x10fa, - 0x10fd, 0x10ff, + 0x10fc, 0x10ff, 0x13a0, 0x13f5, 0x13f8, 0x13fd, 0x1c80, 0x1c88, @@ -12743,10 +12889,10 @@ static const OnigCodePoint CR_Cased[] = { 0xa7d0, 0xa7d1, 0xa7d3, 0xa7d3, 0xa7d5, 0xa7d9, - 0xa7f5, 0xa7f6, + 0xa7f2, 0xa7f6, 0xa7f8, 0xa7fa, 0xab30, 0xab5a, - 0xab5c, 0xab68, + 0xab5c, 0xab69, 0xab70, 0xabbf, 0xfb00, 0xfb06, 0xfb13, 0xfb17, @@ -12763,6 +12909,10 @@ static const OnigCodePoint CR_Cased[] = { 0x105a3, 0x105b1, 0x105b3, 0x105b9, 0x105bb, 0x105bc, + 0x10780, 0x10780, + 0x10783, 0x10785, + 0x10787, 0x107b0, + 0x107b2, 0x107ba, 0x10c80, 0x10cb2, 0x10cc0, 0x10cf2, 0x118a0, 0x118df, @@ -12799,6 +12949,8 @@ static const OnigCodePoint CR_Cased[] = { 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df09, 0x1df0b, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e900, 0x1e943, 0x1f130, 0x1f149, 0x1f150, 0x1f169, @@ -12807,7 +12959,7 @@ static const OnigCodePoint CR_Cased[] = { /* 'Case_Ignorable': Derived Property */ static const OnigCodePoint CR_Case_Ignorable[] = { - 427, + 437, 0x0027, 0x0027, 0x002e, 0x002e, 0x003a, 0x003a, @@ -12921,7 +13073,7 @@ static const OnigCodePoint CR_Case_Ignorable[] = { 0x0eb1, 0x0eb1, 0x0eb4, 0x0ebc, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0f18, 0x0f19, 0x0f35, 0x0f35, 0x0f37, 0x0f37, @@ -13110,6 +13262,7 @@ static const OnigCodePoint CR_Case_Ignorable[] = { 0x10ae5, 0x10ae6, 0x10d24, 0x10d27, 0x10eab, 0x10eac, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11001, 0x11001, @@ -13134,6 +13287,7 @@ static const OnigCodePoint CR_Case_Ignorable[] = { 0x11234, 0x11234, 0x11236, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112df, 0x112e3, 0x112ea, 0x11300, 0x11301, @@ -13195,7 +13349,12 @@ static const OnigCodePoint CR_Case_Ignorable[] = { 0x11d95, 0x11d95, 0x11d97, 0x11d97, 0x11ef3, 0x11ef4, - 0x13430, 0x13438, + 0x11f00, 0x11f01, + 0x11f36, 0x11f3a, + 0x11f40, 0x11f40, + 0x11f42, 0x11f42, + 0x13430, 0x13440, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16b40, 0x16b43, @@ -13226,9 +13385,12 @@ static const OnigCodePoint CR_Case_Ignorable[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e130, 0x1e13d, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, + 0x1e4eb, 0x1e4ef, 0x1e8d0, 0x1e8d6, 0x1e944, 0x1e94b, 0x1f3fb, 0x1f3ff, @@ -15879,7 +16041,7 @@ static const OnigCodePoint CR_Changes_When_Casemapped[] = { /* 'ID_Start': Derived Property */ static const OnigCodePoint CR_ID_Start[] = { - 648, + 659, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -16348,6 +16510,7 @@ static const OnigCodePoint CR_ID_Start[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x1122b, + 0x1123f, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -16410,12 +16573,16 @@ static const OnigCodePoint CR_ID_Start[] = { 0x11d6a, 0x11d89, 0x11d98, 0x11d98, 0x11ee0, 0x11ef2, + 0x11f02, 0x11f02, + 0x11f04, 0x11f10, + 0x11f12, 0x11f33, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -16438,7 +16605,9 @@ static const OnigCodePoint CR_ID_Start[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -16476,11 +16645,14 @@ static const OnigCodePoint CR_ID_Start[] = { 0x1d7aa, 0x1d7c2, 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, + 0x1e4d0, 0x1e4eb, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -16522,17 +16694,18 @@ static const OnigCodePoint CR_ID_Start[] = { 0x1eea5, 0x1eea9, 0x1eeab, 0x1eebb, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_ID_Start */ /* 'ID_Continue': Derived Property */ static const OnigCodePoint CR_ID_Continue[] = { - 756, + 768, 0x0030, 0x0039, 0x0041, 0x005a, 0x005f, 0x005f, @@ -16691,7 +16864,7 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -16724,7 +16897,7 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f00, @@ -17037,7 +17210,7 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10eac, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f1c, + 0x10efd, 0x10f1c, 0x10f27, 0x10f27, 0x10f30, 0x10f50, 0x10f70, 0x10f85, @@ -17060,7 +17233,7 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x11237, - 0x1123e, 0x1123e, + 0x1123e, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -17141,12 +17314,17 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef6, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f42, + 0x11f50, 0x11f59, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13440, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -17174,7 +17352,9 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -17228,17 +17408,21 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -17282,18 +17466,19 @@ static const OnigCodePoint CR_ID_Continue[] = { 0x1eeab, 0x1eebb, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0100, 0xe01ef, }; /* CR_ID_Continue */ /* 'XID_Start': Derived Property */ static const OnigCodePoint CR_XID_Start[] = { - 655, + 666, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -17769,6 +17954,7 @@ static const OnigCodePoint CR_XID_Start[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x1122b, + 0x1123f, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -17831,12 +18017,16 @@ static const OnigCodePoint CR_XID_Start[] = { 0x11d6a, 0x11d89, 0x11d98, 0x11d98, 0x11ee0, 0x11ef2, + 0x11f02, 0x11f02, + 0x11f04, 0x11f10, + 0x11f12, 0x11f33, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -17859,7 +18049,9 @@ static const OnigCodePoint CR_XID_Start[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -17897,11 +18089,14 @@ static const OnigCodePoint CR_XID_Start[] = { 0x1d7aa, 0x1d7c2, 0x1d7c4, 0x1d7cb, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ad, 0x1e2c0, 0x1e2eb, + 0x1e4d0, 0x1e4eb, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -17943,17 +18138,18 @@ static const OnigCodePoint CR_XID_Start[] = { 0x1eea5, 0x1eea9, 0x1eeab, 0x1eebb, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_XID_Start */ /* 'XID_Continue': Derived Property */ static const OnigCodePoint CR_XID_Continue[] = { - 763, + 775, 0x0030, 0x0039, 0x0041, 0x005a, 0x005f, 0x005f, @@ -18112,7 +18308,7 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d00, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d44, @@ -18145,7 +18341,7 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, 0x0f00, 0x0f00, @@ -18465,7 +18661,7 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x10e80, 0x10ea9, 0x10eab, 0x10eac, 0x10eb0, 0x10eb1, - 0x10f00, 0x10f1c, + 0x10efd, 0x10f1c, 0x10f27, 0x10f27, 0x10f30, 0x10f50, 0x10f70, 0x10f85, @@ -18488,7 +18684,7 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x111dc, 0x111dc, 0x11200, 0x11211, 0x11213, 0x11237, - 0x1123e, 0x1123e, + 0x1123e, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -18569,12 +18765,17 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x11d93, 0x11d98, 0x11da0, 0x11da9, 0x11ee0, 0x11ef6, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f42, + 0x11f50, 0x11f59, 0x11fb0, 0x11fb0, 0x12000, 0x12399, 0x12400, 0x1246e, 0x12480, 0x12543, 0x12f90, 0x12ff0, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13440, 0x13455, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -18602,7 +18803,9 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -18656,17 +18859,21 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x1da9b, 0x1da9f, 0x1daa1, 0x1daaf, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, 0x1e000, 0x1e006, 0x1e008, 0x1e018, 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, 0x1e100, 0x1e12c, 0x1e130, 0x1e13d, 0x1e140, 0x1e149, 0x1e14e, 0x1e14e, 0x1e290, 0x1e2ae, 0x1e2c0, 0x1e2f9, + 0x1e4d0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -18710,12 +18917,13 @@ static const OnigCodePoint CR_XID_Continue[] = { 0x1eeab, 0x1eebb, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, 0xe0100, 0xe01ef, }; /* CR_XID_Continue */ @@ -18743,7 +18951,7 @@ static const OnigCodePoint CR_Default_Ignorable_Code_Point[] = { /* 'Grapheme_Extend': Derived Property */ static const OnigCodePoint CR_Grapheme_Extend[] = { - 353, + 363, 0x0300, 0x036f, 0x0483, 0x0489, 0x0591, 0x05bd, @@ -18846,7 +19054,7 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { 0x0e47, 0x0e4e, 0x0eb1, 0x0eb1, 0x0eb4, 0x0ebc, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0f18, 0x0f19, 0x0f35, 0x0f35, 0x0f37, 0x0f37, @@ -18975,6 +19183,7 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { 0x10ae5, 0x10ae6, 0x10d24, 0x10d27, 0x10eab, 0x10eac, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11001, 0x11001, @@ -18997,6 +19206,7 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { 0x11234, 0x11234, 0x11236, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112df, 0x112e3, 0x112ea, 0x11300, 0x11301, @@ -19064,6 +19274,12 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { 0x11d95, 0x11d95, 0x11d97, 0x11d97, 0x11ef3, 0x11ef4, + 0x11f00, 0x11f01, + 0x11f36, 0x11f3a, + 0x11f40, 0x11f40, + 0x11f42, 0x11f42, + 0x13440, 0x13440, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16f4f, 0x16f4f, @@ -19090,9 +19306,11 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e08f, 0x1e08f, 0x1e130, 0x1e136, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, + 0x1e4ec, 0x1e4ef, 0x1e8d0, 0x1e8d6, 0x1e944, 0x1e94a, 0xe0020, 0xe007f, @@ -19101,7 +19319,7 @@ static const OnigCodePoint CR_Grapheme_Extend[] = { /* 'Grapheme_Base': Derived Property */ static const OnigCodePoint CR_Grapheme_Base[] = { - 861, + 875, 0x0020, 0x007e, 0x00a0, 0x00ac, 0x00ae, 0x02ff, @@ -19251,7 +19469,7 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce1, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, 0x0d02, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d3a, @@ -19670,6 +19888,7 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x11232, 0x11233, 0x11235, 0x11235, 0x11238, 0x1123d, + 0x1123f, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128a, 0x1128d, @@ -19756,6 +19975,7 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x11a97, 0x11a97, 0x11a9a, 0x11aa2, 0x11ab0, 0x11af8, + 0x11b00, 0x11b09, 0x11c00, 0x11c08, 0x11c0a, 0x11c2f, 0x11c3e, 0x11c3e, @@ -19779,6 +19999,11 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x11da0, 0x11da9, 0x11ee0, 0x11ef2, 0x11ef5, 0x11ef8, + 0x11f02, 0x11f10, + 0x11f12, 0x11f35, + 0x11f3e, 0x11f3f, + 0x11f41, 0x11f41, + 0x11f43, 0x11f59, 0x11fb0, 0x11fb0, 0x11fc0, 0x11ff1, 0x11fff, 0x12399, @@ -19786,7 +20011,8 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x12470, 0x12474, 0x12480, 0x12543, 0x12f90, 0x12ff2, - 0x13000, 0x1342e, + 0x13000, 0x1342f, + 0x13441, 0x13446, 0x14400, 0x14646, 0x16800, 0x16a38, 0x16a40, 0x16a5e, @@ -19814,7 +20040,9 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1aff5, 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1bc00, 0x1bc6a, @@ -19834,6 +20062,7 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1d1ae, 0x1d1ea, 0x1d200, 0x1d241, 0x1d245, 0x1d245, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d300, 0x1d356, 0x1d360, 0x1d378, @@ -19863,6 +20092,8 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1da76, 0x1da83, 0x1da85, 0x1da8b, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e030, 0x1e06d, 0x1e100, 0x1e12c, 0x1e137, 0x1e13d, 0x1e140, 0x1e149, @@ -19871,6 +20102,8 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1e2c0, 0x1e2eb, 0x1e2f0, 0x1e2f9, 0x1e2ff, 0x1e2ff, + 0x1e4d0, 0x1e4eb, + 0x1e4f0, 0x1e4f9, 0x1e7e0, 0x1e7e6, 0x1e7e8, 0x1e7eb, 0x1e7ed, 0x1e7ee, @@ -19930,10 +20163,10 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -19944,30 +20177,29 @@ static const OnigCodePoint CR_Grapheme_Base[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, 0x1fbf0, 0x1fbf9, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Grapheme_Base */ /* 'Grapheme_Link': Derived Property */ static const OnigCodePoint CR_Grapheme_Link[] = { - 55, + 56, 0x094d, 0x094d, 0x09cd, 0x09cd, 0x0a4d, 0x0a4d, @@ -20023,11 +20255,12 @@ static const OnigCodePoint CR_Grapheme_Link[] = { 0x11c3f, 0x11c3f, 0x11d44, 0x11d45, 0x11d97, 0x11d97, + 0x11f41, 0x11f42, }; /* CR_Grapheme_Link */ /* 'Common': Script */ static const OnigCodePoint CR_Common[] = { - 174, + 173, 0x0000, 0x0040, 0x005b, 0x0060, 0x007b, 0x00a9, @@ -20134,6 +20367,7 @@ static const OnigCodePoint CR_Common[] = { 0x1d183, 0x1d184, 0x1d18c, 0x1d1a9, 0x1d1ae, 0x1d1ea, + 0x1d2c0, 0x1d2d3, 0x1d2e0, 0x1d2f3, 0x1d300, 0x1d356, 0x1d360, 0x1d378, @@ -20174,10 +20408,10 @@ static const OnigCodePoint CR_Common[] = { 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f6d7, - 0x1f6dd, 0x1f6ec, + 0x1f6dc, 0x1f6ec, 0x1f6f0, 0x1f6fc, - 0x1f700, 0x1f773, - 0x1f780, 0x1f7d8, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, 0x1f7e0, 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f800, 0x1f80b, @@ -20188,15 +20422,13 @@ static const OnigCodePoint CR_Common[] = { 0x1f8b0, 0x1f8b1, 0x1f900, 0x1fa53, 0x1fa60, 0x1fa6d, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, 0x1fb00, 0x1fb92, 0x1fb94, 0x1fbca, 0x1fbf0, 0x1fbf9, @@ -20206,7 +20438,7 @@ static const OnigCodePoint CR_Common[] = { /* 'Latin': Script */ static const OnigCodePoint CR_Latin[] = { - 38, + 39, 0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, @@ -20245,6 +20477,7 @@ static const OnigCodePoint CR_Latin[] = { 0x10787, 0x107b0, 0x107b2, 0x107ba, 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, }; /* CR_Latin */ /* 'Greek': Script */ @@ -20290,7 +20523,7 @@ static const OnigCodePoint CR_Greek[] = { /* 'Cyrillic': Script */ static const OnigCodePoint CR_Cyrillic[] = { - 8, + 10, 0x0400, 0x0484, 0x0487, 0x052f, 0x1c80, 0x1c88, @@ -20299,6 +20532,8 @@ static const OnigCodePoint CR_Cyrillic[] = { 0x2de0, 0x2dff, 0xa640, 0xa69f, 0xfe2e, 0xfe2f, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, }; /* CR_Cyrillic */ /* 'Armenian': Script */ @@ -20326,7 +20561,7 @@ static const OnigCodePoint CR_Hebrew[] = { /* 'Arabic': Script */ static const OnigCodePoint CR_Arabic[] = { - 57, + 58, 0x0600, 0x0604, 0x0606, 0x060b, 0x060d, 0x061a, @@ -20350,6 +20585,7 @@ static const OnigCodePoint CR_Arabic[] = { 0xfe70, 0xfe74, 0xfe76, 0xfefc, 0x10e60, 0x10e7e, + 0x10efd, 0x10eff, 0x1ee00, 0x1ee03, 0x1ee05, 0x1ee1f, 0x1ee21, 0x1ee22, @@ -20403,11 +20639,12 @@ static const OnigCodePoint CR_Thaana[] = { /* 'Devanagari': Script */ static const OnigCodePoint CR_Devanagari[] = { - 4, + 5, 0x0900, 0x0950, 0x0955, 0x0963, 0x0966, 0x097f, 0xa8e0, 0xa8ff, + 0x11b00, 0x11b09, }; /* CR_Devanagari */ /* 'Bengali': Script */ @@ -20544,7 +20781,7 @@ static const OnigCodePoint CR_Kannada[] = { 0x0cdd, 0x0cde, 0x0ce0, 0x0ce3, 0x0ce6, 0x0cef, - 0x0cf1, 0x0cf2, + 0x0cf1, 0x0cf3, }; /* CR_Kannada */ /* 'Malayalam': Script */ @@ -20595,7 +20832,7 @@ static const OnigCodePoint CR_Lao[] = { 0x0ea7, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0ed0, 0x0ed9, 0x0edc, 0x0edf, }; /* CR_Lao */ @@ -20746,17 +20983,18 @@ static const OnigCodePoint CR_Mongolian[] = { /* 'Hiragana': Script */ static const OnigCodePoint CR_Hiragana[] = { - 5, + 6, 0x3041, 0x3096, 0x309d, 0x309f, 0x1b001, 0x1b11f, + 0x1b132, 0x1b132, 0x1b150, 0x1b152, 0x1f200, 0x1f200, }; /* CR_Hiragana */ /* 'Katakana': Script */ static const OnigCodePoint CR_Katakana[] = { - 13, + 14, 0x30a1, 0x30fa, 0x30fd, 0x30ff, 0x31f0, 0x31ff, @@ -20769,6 +21007,7 @@ static const OnigCodePoint CR_Katakana[] = { 0x1affd, 0x1affe, 0x1b000, 0x1b000, 0x1b120, 0x1b122, + 0x1b155, 0x1b155, 0x1b164, 0x1b167, }; /* CR_Katakana */ @@ -20782,7 +21021,7 @@ static const OnigCodePoint CR_Bopomofo[] = { /* 'Han': Script */ static const OnigCodePoint CR_Han[] = { - 20, + 21, 0x2e80, 0x2e99, 0x2e9b, 0x2ef3, 0x2f00, 0x2fd5, @@ -20797,12 +21036,13 @@ static const OnigCodePoint CR_Han[] = { 0x16fe2, 0x16fe3, 0x16ff0, 0x16ff1, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Han */ /* 'Yi': Script */ @@ -21165,9 +21405,8 @@ static const OnigCodePoint CR_Avestan[] = { /* 'Egyptian_Hieroglyphs': Script */ static const OnigCodePoint CR_Egyptian_Hieroglyphs[] = { - 2, - 0x13000, 0x1342e, - 0x13430, 0x13438, + 1, + 0x13000, 0x13455, }; /* CR_Egyptian_Hieroglyphs */ /* 'Samaritan': Script */ @@ -21382,7 +21621,7 @@ static const OnigCodePoint CR_Pahawh_Hmong[] = { static const OnigCodePoint CR_Khojki[] = { 2, 0x11200, 0x11211, - 0x11213, 0x1123e, + 0x11213, 0x11241, }; /* CR_Khojki */ /* 'Linear_A': Script */ @@ -21772,6 +22011,20 @@ static const OnigCodePoint CR_Vithkuqi[] = { 0x105bb, 0x105bc, }; /* CR_Vithkuqi */ +/* 'Kawi': Script */ +static const OnigCodePoint CR_Kawi[] = { + 3, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f59, +}; /* CR_Kawi */ + +/* 'Nag_Mundari': Script */ +static const OnigCodePoint CR_Nag_Mundari[] = { + 1, + 0x1e4d0, 0x1e4f9, +}; /* CR_Nag_Mundari */ + /* 'White_Space': Binary Property */ #define CR_White_Space CR_Space @@ -21853,7 +22106,7 @@ static const OnigCodePoint CR_Quotation_Mark[] = { /* 'Terminal_Punctuation': Binary Property */ static const OnigCodePoint CR_Terminal_Punctuation[] = { - 107, + 108, 0x0021, 0x0021, 0x002c, 0x002c, 0x002e, 0x002e, @@ -21953,6 +22206,7 @@ static const OnigCodePoint CR_Terminal_Punctuation[] = { 0x11c41, 0x11c43, 0x11c71, 0x11c71, 0x11ef7, 0x11ef8, + 0x11f43, 0x11f44, 0x12470, 0x12474, 0x16a6e, 0x16a6f, 0x16af5, 0x16af5, @@ -22118,7 +22372,7 @@ static const OnigCodePoint CR_Hex_Digit[] = { /* 'Other_Alphabetic': Binary Property */ static const OnigCodePoint CR_Other_Alphabetic[] = { - 233, + 240, 0x0345, 0x0345, 0x05b0, 0x05bd, 0x05bf, 0x05bf, @@ -22178,7 +22432,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x0bc6, 0x0bc8, 0x0bca, 0x0bcc, 0x0bd7, 0x0bd7, - 0x0c00, 0x0c03, + 0x0c00, 0x0c04, 0x0c3e, 0x0c44, 0x0c46, 0x0c48, 0x0c4a, 0x0c4c, @@ -22190,6 +22444,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x0cca, 0x0ccc, 0x0cd5, 0x0cd6, 0x0ce2, 0x0ce3, + 0x0cf3, 0x0cf3, 0x0d00, 0x0d03, 0x0d3e, 0x0d44, 0x0d46, 0x0d48, @@ -22208,7 +22463,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x0eb4, 0x0eb9, 0x0ebb, 0x0ebc, 0x0ecd, 0x0ecd, - 0x0f71, 0x0f81, + 0x0f71, 0x0f83, 0x0f8d, 0x0f97, 0x0f99, 0x0fbc, 0x102b, 0x1036, @@ -22281,7 +22536,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x11000, 0x11002, 0x11038, 0x11045, 0x11073, 0x11074, - 0x11082, 0x11082, + 0x11080, 0x11082, 0x110b0, 0x110b8, 0x110c2, 0x110c2, 0x11100, 0x11102, @@ -22293,6 +22548,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x1122c, 0x11234, 0x11237, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112e8, 0x11300, 0x11303, 0x1133e, 0x11344, @@ -22338,6 +22594,10 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x11d90, 0x11d91, 0x11d93, 0x11d96, 0x11ef3, 0x11ef6, + 0x11f00, 0x11f01, + 0x11f03, 0x11f03, + 0x11f34, 0x11f3a, + 0x11f3e, 0x11f40, 0x16f4f, 0x16f4f, 0x16f51, 0x16f87, 0x16f8f, 0x16f92, @@ -22348,6 +22608,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e08f, 0x1e08f, 0x1e947, 0x1e947, 0x1f130, 0x1f149, 0x1f150, 0x1f169, @@ -22356,7 +22617,7 @@ static const OnigCodePoint CR_Other_Alphabetic[] = { /* 'Ideographic': Binary Property */ static const OnigCodePoint CR_Ideographic[] = { - 19, + 20, 0x3006, 0x3007, 0x3021, 0x3029, 0x3038, 0x303a, @@ -22370,17 +22631,18 @@ static const OnigCodePoint CR_Ideographic[] = { 0x18d00, 0x18d08, 0x1b170, 0x1b2fb, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x2f800, 0x2fa1d, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Ideographic */ /* 'Diacritic': Binary Property */ static const OnigCodePoint CR_Diacritic[] = { - 192, + 195, 0x005e, 0x005e, 0x0060, 0x0060, 0x00a8, 0x00a8, @@ -22520,6 +22782,7 @@ static const OnigCodePoint CR_Diacritic[] = { 0x107b2, 0x107ba, 0x10ae5, 0x10ae6, 0x10d22, 0x10d27, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11046, 0x11046, @@ -22553,6 +22816,7 @@ static const OnigCodePoint CR_Diacritic[] = { 0x11d42, 0x11d42, 0x11d44, 0x11d45, 0x11d97, 0x11d97, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16f8f, 0x16f9f, @@ -22567,6 +22831,7 @@ static const OnigCodePoint CR_Diacritic[] = { 0x1d17b, 0x1d182, 0x1d185, 0x1d18b, 0x1d1aa, 0x1d1ad, + 0x1e030, 0x1e06d, 0x1e130, 0x1e136, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, @@ -22615,7 +22880,7 @@ static const OnigCodePoint CR_Extender[] = { /* 'Other_Lowercase': Binary Property */ static const OnigCodePoint CR_Other_Lowercase[] = { - 20, + 28, 0x00aa, 0x00aa, 0x00ba, 0x00ba, 0x02b0, 0x02b8, @@ -22623,6 +22888,7 @@ static const OnigCodePoint CR_Other_Lowercase[] = { 0x02e0, 0x02e4, 0x0345, 0x0345, 0x037a, 0x037a, + 0x10fc, 0x10fc, 0x1d2c, 0x1d6a, 0x1d78, 0x1d78, 0x1d9b, 0x1dbf, @@ -22634,8 +22900,15 @@ static const OnigCodePoint CR_Other_Lowercase[] = { 0x2c7c, 0x2c7d, 0xa69c, 0xa69d, 0xa770, 0xa770, + 0xa7f2, 0xa7f4, 0xa7f8, 0xa7f9, 0xab5c, 0xab5f, + 0xab69, 0xab69, + 0x10780, 0x10780, + 0x10783, 0x10785, + 0x10787, 0x107b0, + 0x107b2, 0x107ba, + 0x1e030, 0x1e06d, }; /* CR_Other_Lowercase */ /* 'Other_Uppercase': Binary Property */ @@ -22724,7 +22997,7 @@ static const OnigCodePoint CR_Radical[] = { /* 'Unified_Ideograph': Binary Property */ static const OnigCodePoint CR_Unified_Ideograph[] = { - 15, + 16, 0x3400, 0x4dbf, 0x4e00, 0x9fff, 0xfa0e, 0xfa0f, @@ -22735,11 +23008,12 @@ static const OnigCodePoint CR_Unified_Ideograph[] = { 0xfa23, 0xfa24, 0xfa27, 0xfa29, 0x20000, 0x2a6df, - 0x2a700, 0x2b738, + 0x2a700, 0x2b739, 0x2b740, 0x2b81d, 0x2b820, 0x2cea1, 0x2ceb0, 0x2ebe0, 0x30000, 0x3134a, + 0x31350, 0x323af, }; /* CR_Unified_Ideograph */ /* 'Other_Default_Ignorable_Code_Point': Binary Property */ @@ -22773,7 +23047,7 @@ static const OnigCodePoint CR_Deprecated[] = { /* 'Soft_Dotted': Binary Property */ static const OnigCodePoint CR_Soft_Dotted[] = { - 32, + 34, 0x0069, 0x006a, 0x012f, 0x012f, 0x0249, 0x0249, @@ -22806,6 +23080,8 @@ static const OnigCodePoint CR_Soft_Dotted[] = { 0x1d65e, 0x1d65f, 0x1d692, 0x1d693, 0x1df1a, 0x1df1a, + 0x1e04c, 0x1e04d, + 0x1e068, 0x1e068, }; /* CR_Soft_Dotted */ /* 'Logical_Order_Exception': Binary Property */ @@ -22840,7 +23116,7 @@ static const OnigCodePoint CR_Other_ID_Continue[] = { /* 'Sentence_Terminal': Binary Property */ static const OnigCodePoint CR_Sentence_Terminal[] = { - 79, + 80, 0x0021, 0x0021, 0x002e, 0x002e, 0x003f, 0x003f, @@ -22913,6 +23189,7 @@ static const OnigCodePoint CR_Sentence_Terminal[] = { 0x11a9b, 0x11a9c, 0x11c41, 0x11c42, 0x11ef7, 0x11ef8, + 0x11f43, 0x11f44, 0x16a6e, 0x16a6f, 0x16af5, 0x16af5, 0x16b37, 0x16b38, @@ -22994,7 +23271,7 @@ static const OnigCodePoint CR_Regional_Indicator[] = { /* 'Emoji': Emoji */ static const OnigCodePoint CR_Emoji[] = { - 153, + 151, 0x0023, 0x0023, 0x002a, 0x002a, 0x0030, 0x0039, @@ -23129,7 +23406,7 @@ static const OnigCodePoint CR_Emoji[] = { 0x1f680, 0x1f6c5, 0x1f6cb, 0x1f6d2, 0x1f6d5, 0x1f6d7, - 0x1f6dd, 0x1f6e5, + 0x1f6dc, 0x1f6e5, 0x1f6e9, 0x1f6e9, 0x1f6eb, 0x1f6ec, 0x1f6f0, 0x1f6f0, @@ -23139,20 +23416,18 @@ static const OnigCodePoint CR_Emoji[] = { 0x1f90c, 0x1f93a, 0x1f93c, 0x1f945, 0x1f947, 0x1f9ff, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, }; /* CR_Emoji */ /* 'Emoji_Presentation': Emoji */ static const OnigCodePoint CR_Emoji_Presentation[] = { - 83, + 81, 0x231a, 0x231b, 0x23e9, 0x23ec, 0x23f0, 0x23f0, @@ -23219,7 +23494,7 @@ static const OnigCodePoint CR_Emoji_Presentation[] = { 0x1f6cc, 0x1f6cc, 0x1f6d0, 0x1f6d2, 0x1f6d5, 0x1f6d7, - 0x1f6dd, 0x1f6df, + 0x1f6dc, 0x1f6df, 0x1f6eb, 0x1f6ec, 0x1f6f4, 0x1f6fc, 0x1f7e0, 0x1f7eb, @@ -23227,15 +23502,13 @@ static const OnigCodePoint CR_Emoji_Presentation[] = { 0x1f90c, 0x1f93a, 0x1f93c, 0x1f945, 0x1f947, 0x1f9ff, - 0x1fa70, 0x1fa74, - 0x1fa78, 0x1fa7c, - 0x1fa80, 0x1fa86, - 0x1fa90, 0x1faac, - 0x1fab0, 0x1faba, - 0x1fac0, 0x1fac5, - 0x1fad0, 0x1fad9, - 0x1fae0, 0x1fae7, - 0x1faf0, 0x1faf6, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, }; /* CR_Emoji_Presentation */ /* 'Emoji_Modifier': Emoji */ @@ -23286,7 +23559,7 @@ static const OnigCodePoint CR_Emoji_Modifier_Base[] = { 0x1f9cd, 0x1f9cf, 0x1f9d1, 0x1f9dd, 0x1fac3, 0x1fac5, - 0x1faf0, 0x1faf6, + 0x1faf0, 0x1faf8, }; /* CR_Emoji_Modifier_Base */ /* 'Emoji_Component': Emoji */ @@ -23389,7 +23662,7 @@ static const OnigCodePoint CR_Extended_Pictographic[] = { /* 'Unknown': Script */ static const OnigCodePoint CR_Unknown[] = { - 696, + 705, 0x0378, 0x0379, 0x0380, 0x0383, 0x038b, 0x038b, @@ -23511,7 +23784,7 @@ static const OnigCodePoint CR_Unknown[] = { 0x0cdf, 0x0cdf, 0x0ce4, 0x0ce5, 0x0cf0, 0x0cf0, - 0x0cf3, 0x0cff, + 0x0cf4, 0x0cff, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d45, 0x0d45, @@ -23541,7 +23814,7 @@ static const OnigCodePoint CR_Unknown[] = { 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, - 0x0ece, 0x0ecf, + 0x0ecf, 0x0ecf, 0x0eda, 0x0edb, 0x0ee0, 0x0eff, 0x0f48, 0x0f48, @@ -23811,7 +24084,7 @@ static const OnigCodePoint CR_Unknown[] = { 0x10e7f, 0x10e7f, 0x10eaa, 0x10eaa, 0x10eae, 0x10eaf, - 0x10eb2, 0x10eff, + 0x10eb2, 0x10efc, 0x10f28, 0x10f2f, 0x10f5a, 0x10f6f, 0x10f8a, 0x10faf, @@ -23829,7 +24102,7 @@ static const OnigCodePoint CR_Unknown[] = { 0x111e0, 0x111e0, 0x111f5, 0x111ff, 0x11212, 0x11212, - 0x1123f, 0x1127f, + 0x11242, 0x1127f, 0x11287, 0x11287, 0x11289, 0x11289, 0x1128e, 0x1128e, @@ -23881,7 +24154,8 @@ static const OnigCodePoint CR_Unknown[] = { 0x119e5, 0x119ff, 0x11a48, 0x11a4f, 0x11aa3, 0x11aaf, - 0x11af9, 0x11bff, + 0x11af9, 0x11aff, + 0x11b0a, 0x11bff, 0x11c09, 0x11c09, 0x11c37, 0x11c37, 0x11c46, 0x11c4f, @@ -23902,7 +24176,10 @@ static const OnigCodePoint CR_Unknown[] = { 0x11d92, 0x11d92, 0x11d99, 0x11d9f, 0x11daa, 0x11edf, - 0x11ef9, 0x11faf, + 0x11ef9, 0x11eff, + 0x11f11, 0x11f11, + 0x11f3b, 0x11f3d, + 0x11f5a, 0x11faf, 0x11fb1, 0x11fbf, 0x11ff2, 0x11ffe, 0x1239a, 0x123ff, @@ -23910,8 +24187,7 @@ static const OnigCodePoint CR_Unknown[] = { 0x12475, 0x1247f, 0x12544, 0x12f8f, 0x12ff3, 0x12fff, - 0x1342f, 0x1342f, - 0x13439, 0x143ff, + 0x13456, 0x143ff, 0x14647, 0x167ff, 0x16a39, 0x16a3f, 0x16a5f, 0x16a5f, @@ -23937,8 +24213,10 @@ static const OnigCodePoint CR_Unknown[] = { 0x1aff4, 0x1aff4, 0x1affc, 0x1affc, 0x1afff, 0x1afff, - 0x1b123, 0x1b14f, - 0x1b153, 0x1b163, + 0x1b123, 0x1b131, + 0x1b133, 0x1b14f, + 0x1b153, 0x1b154, + 0x1b156, 0x1b163, 0x1b168, 0x1b16f, 0x1b2fc, 0x1bbff, 0x1bc6b, 0x1bc6f, @@ -23952,7 +24230,8 @@ static const OnigCodePoint CR_Unknown[] = { 0x1d0f6, 0x1d0ff, 0x1d127, 0x1d128, 0x1d1eb, 0x1d1ff, - 0x1d246, 0x1d2df, + 0x1d246, 0x1d2bf, + 0x1d2d4, 0x1d2df, 0x1d2f4, 0x1d2ff, 0x1d357, 0x1d35f, 0x1d379, 0x1d3ff, @@ -23979,19 +24258,23 @@ static const OnigCodePoint CR_Unknown[] = { 0x1da8c, 0x1da9a, 0x1daa0, 0x1daa0, 0x1dab0, 0x1deff, - 0x1df1f, 0x1dfff, + 0x1df1f, 0x1df24, + 0x1df2b, 0x1dfff, 0x1e007, 0x1e007, 0x1e019, 0x1e01a, 0x1e022, 0x1e022, 0x1e025, 0x1e025, - 0x1e02b, 0x1e0ff, + 0x1e02b, 0x1e02f, + 0x1e06e, 0x1e08e, + 0x1e090, 0x1e0ff, 0x1e12d, 0x1e12f, 0x1e13e, 0x1e13f, 0x1e14a, 0x1e14d, 0x1e150, 0x1e28f, 0x1e2af, 0x1e2bf, 0x1e2fa, 0x1e2fe, - 0x1e300, 0x1e7df, + 0x1e300, 0x1e4cf, + 0x1e4fa, 0x1e7df, 0x1e7e7, 0x1e7e7, 0x1e7ec, 0x1e7ec, 0x1e7ef, 0x1e7ef, @@ -24049,11 +24332,11 @@ static const OnigCodePoint CR_Unknown[] = { 0x1f249, 0x1f24f, 0x1f252, 0x1f25f, 0x1f266, 0x1f2ff, - 0x1f6d8, 0x1f6dc, + 0x1f6d8, 0x1f6db, 0x1f6ed, 0x1f6ef, 0x1f6fd, 0x1f6ff, - 0x1f774, 0x1f77f, - 0x1f7d9, 0x1f7df, + 0x1f777, 0x1f77a, + 0x1f7da, 0x1f7df, 0x1f7ec, 0x1f7ef, 0x1f7f1, 0x1f7ff, 0x1f80c, 0x1f80f, @@ -24064,25 +24347,24 @@ static const OnigCodePoint CR_Unknown[] = { 0x1f8b2, 0x1f8ff, 0x1fa54, 0x1fa5f, 0x1fa6e, 0x1fa6f, - 0x1fa75, 0x1fa77, 0x1fa7d, 0x1fa7f, - 0x1fa87, 0x1fa8f, - 0x1faad, 0x1faaf, - 0x1fabb, 0x1fabf, - 0x1fac6, 0x1facf, - 0x1fada, 0x1fadf, - 0x1fae8, 0x1faef, - 0x1faf7, 0x1faff, + 0x1fa89, 0x1fa8f, + 0x1fabe, 0x1fabe, + 0x1fac6, 0x1facd, + 0x1fadc, 0x1fadf, + 0x1fae9, 0x1faef, + 0x1faf9, 0x1faff, 0x1fb93, 0x1fb93, 0x1fbcb, 0x1fbef, 0x1fbfa, 0x1ffff, 0x2a6e0, 0x2a6ff, - 0x2b739, 0x2b73f, + 0x2b73a, 0x2b73f, 0x2b81e, 0x2b81f, 0x2cea2, 0x2ceaf, 0x2ebe1, 0x2f7ff, 0x2fa1e, 0x2ffff, - 0x3134b, 0xe0000, + 0x3134b, 0x3134f, + 0x323b0, 0xe0000, 0xe0002, 0xe001f, 0xe0080, 0xe00ff, 0xe01f0, 0x10ffff, @@ -36632,10 +36914,730 @@ static const OnigCodePoint CR_Age_14_0[] = { 0xefffe, 0x10ffff, }; /* CR_Age_14_0 */ +/* 'Age_15_0': Derived Age 15.0 */ +static const OnigCodePoint CR_Age_15_0[] = { + 715, + 0x0000, 0x0377, + 0x037a, 0x037f, + 0x0384, 0x038a, + 0x038c, 0x038c, + 0x038e, 0x03a1, + 0x03a3, 0x052f, + 0x0531, 0x0556, + 0x0559, 0x058a, + 0x058d, 0x058f, + 0x0591, 0x05c7, + 0x05d0, 0x05ea, + 0x05ef, 0x05f4, + 0x0600, 0x070d, + 0x070f, 0x074a, + 0x074d, 0x07b1, + 0x07c0, 0x07fa, + 0x07fd, 0x082d, + 0x0830, 0x083e, + 0x0840, 0x085b, + 0x085e, 0x085e, + 0x0860, 0x086a, + 0x0870, 0x088e, + 0x0890, 0x0891, + 0x0898, 0x0983, + 0x0985, 0x098c, + 0x098f, 0x0990, + 0x0993, 0x09a8, + 0x09aa, 0x09b0, + 0x09b2, 0x09b2, + 0x09b6, 0x09b9, + 0x09bc, 0x09c4, + 0x09c7, 0x09c8, + 0x09cb, 0x09ce, + 0x09d7, 0x09d7, + 0x09dc, 0x09dd, + 0x09df, 0x09e3, + 0x09e6, 0x09fe, + 0x0a01, 0x0a03, + 0x0a05, 0x0a0a, + 0x0a0f, 0x0a10, + 0x0a13, 0x0a28, + 0x0a2a, 0x0a30, + 0x0a32, 0x0a33, + 0x0a35, 0x0a36, + 0x0a38, 0x0a39, + 0x0a3c, 0x0a3c, + 0x0a3e, 0x0a42, + 0x0a47, 0x0a48, + 0x0a4b, 0x0a4d, + 0x0a51, 0x0a51, + 0x0a59, 0x0a5c, + 0x0a5e, 0x0a5e, + 0x0a66, 0x0a76, + 0x0a81, 0x0a83, + 0x0a85, 0x0a8d, + 0x0a8f, 0x0a91, + 0x0a93, 0x0aa8, + 0x0aaa, 0x0ab0, + 0x0ab2, 0x0ab3, + 0x0ab5, 0x0ab9, + 0x0abc, 0x0ac5, + 0x0ac7, 0x0ac9, + 0x0acb, 0x0acd, + 0x0ad0, 0x0ad0, + 0x0ae0, 0x0ae3, + 0x0ae6, 0x0af1, + 0x0af9, 0x0aff, + 0x0b01, 0x0b03, + 0x0b05, 0x0b0c, + 0x0b0f, 0x0b10, + 0x0b13, 0x0b28, + 0x0b2a, 0x0b30, + 0x0b32, 0x0b33, + 0x0b35, 0x0b39, + 0x0b3c, 0x0b44, + 0x0b47, 0x0b48, + 0x0b4b, 0x0b4d, + 0x0b55, 0x0b57, + 0x0b5c, 0x0b5d, + 0x0b5f, 0x0b63, + 0x0b66, 0x0b77, + 0x0b82, 0x0b83, + 0x0b85, 0x0b8a, + 0x0b8e, 0x0b90, + 0x0b92, 0x0b95, + 0x0b99, 0x0b9a, + 0x0b9c, 0x0b9c, + 0x0b9e, 0x0b9f, + 0x0ba3, 0x0ba4, + 0x0ba8, 0x0baa, + 0x0bae, 0x0bb9, + 0x0bbe, 0x0bc2, + 0x0bc6, 0x0bc8, + 0x0bca, 0x0bcd, + 0x0bd0, 0x0bd0, + 0x0bd7, 0x0bd7, + 0x0be6, 0x0bfa, + 0x0c00, 0x0c0c, + 0x0c0e, 0x0c10, + 0x0c12, 0x0c28, + 0x0c2a, 0x0c39, + 0x0c3c, 0x0c44, + 0x0c46, 0x0c48, + 0x0c4a, 0x0c4d, + 0x0c55, 0x0c56, + 0x0c58, 0x0c5a, + 0x0c5d, 0x0c5d, + 0x0c60, 0x0c63, + 0x0c66, 0x0c6f, + 0x0c77, 0x0c8c, + 0x0c8e, 0x0c90, + 0x0c92, 0x0ca8, + 0x0caa, 0x0cb3, + 0x0cb5, 0x0cb9, + 0x0cbc, 0x0cc4, + 0x0cc6, 0x0cc8, + 0x0cca, 0x0ccd, + 0x0cd5, 0x0cd6, + 0x0cdd, 0x0cde, + 0x0ce0, 0x0ce3, + 0x0ce6, 0x0cef, + 0x0cf1, 0x0cf3, + 0x0d00, 0x0d0c, + 0x0d0e, 0x0d10, + 0x0d12, 0x0d44, + 0x0d46, 0x0d48, + 0x0d4a, 0x0d4f, + 0x0d54, 0x0d63, + 0x0d66, 0x0d7f, + 0x0d81, 0x0d83, + 0x0d85, 0x0d96, + 0x0d9a, 0x0db1, + 0x0db3, 0x0dbb, + 0x0dbd, 0x0dbd, + 0x0dc0, 0x0dc6, + 0x0dca, 0x0dca, + 0x0dcf, 0x0dd4, + 0x0dd6, 0x0dd6, + 0x0dd8, 0x0ddf, + 0x0de6, 0x0def, + 0x0df2, 0x0df4, + 0x0e01, 0x0e3a, + 0x0e3f, 0x0e5b, + 0x0e81, 0x0e82, + 0x0e84, 0x0e84, + 0x0e86, 0x0e8a, + 0x0e8c, 0x0ea3, + 0x0ea5, 0x0ea5, + 0x0ea7, 0x0ebd, + 0x0ec0, 0x0ec4, + 0x0ec6, 0x0ec6, + 0x0ec8, 0x0ece, + 0x0ed0, 0x0ed9, + 0x0edc, 0x0edf, + 0x0f00, 0x0f47, + 0x0f49, 0x0f6c, + 0x0f71, 0x0f97, + 0x0f99, 0x0fbc, + 0x0fbe, 0x0fcc, + 0x0fce, 0x0fda, + 0x1000, 0x10c5, + 0x10c7, 0x10c7, + 0x10cd, 0x10cd, + 0x10d0, 0x1248, + 0x124a, 0x124d, + 0x1250, 0x1256, + 0x1258, 0x1258, + 0x125a, 0x125d, + 0x1260, 0x1288, + 0x128a, 0x128d, + 0x1290, 0x12b0, + 0x12b2, 0x12b5, + 0x12b8, 0x12be, + 0x12c0, 0x12c0, + 0x12c2, 0x12c5, + 0x12c8, 0x12d6, + 0x12d8, 0x1310, + 0x1312, 0x1315, + 0x1318, 0x135a, + 0x135d, 0x137c, + 0x1380, 0x1399, + 0x13a0, 0x13f5, + 0x13f8, 0x13fd, + 0x1400, 0x169c, + 0x16a0, 0x16f8, + 0x1700, 0x1715, + 0x171f, 0x1736, + 0x1740, 0x1753, + 0x1760, 0x176c, + 0x176e, 0x1770, + 0x1772, 0x1773, + 0x1780, 0x17dd, + 0x17e0, 0x17e9, + 0x17f0, 0x17f9, + 0x1800, 0x1819, + 0x1820, 0x1878, + 0x1880, 0x18aa, + 0x18b0, 0x18f5, + 0x1900, 0x191e, + 0x1920, 0x192b, + 0x1930, 0x193b, + 0x1940, 0x1940, + 0x1944, 0x196d, + 0x1970, 0x1974, + 0x1980, 0x19ab, + 0x19b0, 0x19c9, + 0x19d0, 0x19da, + 0x19de, 0x1a1b, + 0x1a1e, 0x1a5e, + 0x1a60, 0x1a7c, + 0x1a7f, 0x1a89, + 0x1a90, 0x1a99, + 0x1aa0, 0x1aad, + 0x1ab0, 0x1ace, + 0x1b00, 0x1b4c, + 0x1b50, 0x1b7e, + 0x1b80, 0x1bf3, + 0x1bfc, 0x1c37, + 0x1c3b, 0x1c49, + 0x1c4d, 0x1c88, + 0x1c90, 0x1cba, + 0x1cbd, 0x1cc7, + 0x1cd0, 0x1cfa, + 0x1d00, 0x1f15, + 0x1f18, 0x1f1d, + 0x1f20, 0x1f45, + 0x1f48, 0x1f4d, + 0x1f50, 0x1f57, + 0x1f59, 0x1f59, + 0x1f5b, 0x1f5b, + 0x1f5d, 0x1f5d, + 0x1f5f, 0x1f7d, + 0x1f80, 0x1fb4, + 0x1fb6, 0x1fc4, + 0x1fc6, 0x1fd3, + 0x1fd6, 0x1fdb, + 0x1fdd, 0x1fef, + 0x1ff2, 0x1ff4, + 0x1ff6, 0x1ffe, + 0x2000, 0x2064, + 0x2066, 0x2071, + 0x2074, 0x208e, + 0x2090, 0x209c, + 0x20a0, 0x20c0, + 0x20d0, 0x20f0, + 0x2100, 0x218b, + 0x2190, 0x2426, + 0x2440, 0x244a, + 0x2460, 0x2b73, + 0x2b76, 0x2b95, + 0x2b97, 0x2cf3, + 0x2cf9, 0x2d25, + 0x2d27, 0x2d27, + 0x2d2d, 0x2d2d, + 0x2d30, 0x2d67, + 0x2d6f, 0x2d70, + 0x2d7f, 0x2d96, + 0x2da0, 0x2da6, + 0x2da8, 0x2dae, + 0x2db0, 0x2db6, + 0x2db8, 0x2dbe, + 0x2dc0, 0x2dc6, + 0x2dc8, 0x2dce, + 0x2dd0, 0x2dd6, + 0x2dd8, 0x2dde, + 0x2de0, 0x2e5d, + 0x2e80, 0x2e99, + 0x2e9b, 0x2ef3, + 0x2f00, 0x2fd5, + 0x2ff0, 0x2ffb, + 0x3000, 0x303f, + 0x3041, 0x3096, + 0x3099, 0x30ff, + 0x3105, 0x312f, + 0x3131, 0x318e, + 0x3190, 0x31e3, + 0x31f0, 0x321e, + 0x3220, 0xa48c, + 0xa490, 0xa4c6, + 0xa4d0, 0xa62b, + 0xa640, 0xa6f7, + 0xa700, 0xa7ca, + 0xa7d0, 0xa7d1, + 0xa7d3, 0xa7d3, + 0xa7d5, 0xa7d9, + 0xa7f2, 0xa82c, + 0xa830, 0xa839, + 0xa840, 0xa877, + 0xa880, 0xa8c5, + 0xa8ce, 0xa8d9, + 0xa8e0, 0xa953, + 0xa95f, 0xa97c, + 0xa980, 0xa9cd, + 0xa9cf, 0xa9d9, + 0xa9de, 0xa9fe, + 0xaa00, 0xaa36, + 0xaa40, 0xaa4d, + 0xaa50, 0xaa59, + 0xaa5c, 0xaac2, + 0xaadb, 0xaaf6, + 0xab01, 0xab06, + 0xab09, 0xab0e, + 0xab11, 0xab16, + 0xab20, 0xab26, + 0xab28, 0xab2e, + 0xab30, 0xab6b, + 0xab70, 0xabed, + 0xabf0, 0xabf9, + 0xac00, 0xd7a3, + 0xd7b0, 0xd7c6, + 0xd7cb, 0xd7fb, + 0xd800, 0xfa6d, + 0xfa70, 0xfad9, + 0xfb00, 0xfb06, + 0xfb13, 0xfb17, + 0xfb1d, 0xfb36, + 0xfb38, 0xfb3c, + 0xfb3e, 0xfb3e, + 0xfb40, 0xfb41, + 0xfb43, 0xfb44, + 0xfb46, 0xfbc2, + 0xfbd3, 0xfd8f, + 0xfd92, 0xfdc7, + 0xfdcf, 0xfe19, + 0xfe20, 0xfe52, + 0xfe54, 0xfe66, + 0xfe68, 0xfe6b, + 0xfe70, 0xfe74, + 0xfe76, 0xfefc, + 0xfeff, 0xfeff, + 0xff01, 0xffbe, + 0xffc2, 0xffc7, + 0xffca, 0xffcf, + 0xffd2, 0xffd7, + 0xffda, 0xffdc, + 0xffe0, 0xffe6, + 0xffe8, 0xffee, + 0xfff9, 0x1000b, + 0x1000d, 0x10026, + 0x10028, 0x1003a, + 0x1003c, 0x1003d, + 0x1003f, 0x1004d, + 0x10050, 0x1005d, + 0x10080, 0x100fa, + 0x10100, 0x10102, + 0x10107, 0x10133, + 0x10137, 0x1018e, + 0x10190, 0x1019c, + 0x101a0, 0x101a0, + 0x101d0, 0x101fd, + 0x10280, 0x1029c, + 0x102a0, 0x102d0, + 0x102e0, 0x102fb, + 0x10300, 0x10323, + 0x1032d, 0x1034a, + 0x10350, 0x1037a, + 0x10380, 0x1039d, + 0x1039f, 0x103c3, + 0x103c8, 0x103d5, + 0x10400, 0x1049d, + 0x104a0, 0x104a9, + 0x104b0, 0x104d3, + 0x104d8, 0x104fb, + 0x10500, 0x10527, + 0x10530, 0x10563, + 0x1056f, 0x1057a, + 0x1057c, 0x1058a, + 0x1058c, 0x10592, + 0x10594, 0x10595, + 0x10597, 0x105a1, + 0x105a3, 0x105b1, + 0x105b3, 0x105b9, + 0x105bb, 0x105bc, + 0x10600, 0x10736, + 0x10740, 0x10755, + 0x10760, 0x10767, + 0x10780, 0x10785, + 0x10787, 0x107b0, + 0x107b2, 0x107ba, + 0x10800, 0x10805, + 0x10808, 0x10808, + 0x1080a, 0x10835, + 0x10837, 0x10838, + 0x1083c, 0x1083c, + 0x1083f, 0x10855, + 0x10857, 0x1089e, + 0x108a7, 0x108af, + 0x108e0, 0x108f2, + 0x108f4, 0x108f5, + 0x108fb, 0x1091b, + 0x1091f, 0x10939, + 0x1093f, 0x1093f, + 0x10980, 0x109b7, + 0x109bc, 0x109cf, + 0x109d2, 0x10a03, + 0x10a05, 0x10a06, + 0x10a0c, 0x10a13, + 0x10a15, 0x10a17, + 0x10a19, 0x10a35, + 0x10a38, 0x10a3a, + 0x10a3f, 0x10a48, + 0x10a50, 0x10a58, + 0x10a60, 0x10a9f, + 0x10ac0, 0x10ae6, + 0x10aeb, 0x10af6, + 0x10b00, 0x10b35, + 0x10b39, 0x10b55, + 0x10b58, 0x10b72, + 0x10b78, 0x10b91, + 0x10b99, 0x10b9c, + 0x10ba9, 0x10baf, + 0x10c00, 0x10c48, + 0x10c80, 0x10cb2, + 0x10cc0, 0x10cf2, + 0x10cfa, 0x10d27, + 0x10d30, 0x10d39, + 0x10e60, 0x10e7e, + 0x10e80, 0x10ea9, + 0x10eab, 0x10ead, + 0x10eb0, 0x10eb1, + 0x10efd, 0x10f27, + 0x10f30, 0x10f59, + 0x10f70, 0x10f89, + 0x10fb0, 0x10fcb, + 0x10fe0, 0x10ff6, + 0x11000, 0x1104d, + 0x11052, 0x11075, + 0x1107f, 0x110c2, + 0x110cd, 0x110cd, + 0x110d0, 0x110e8, + 0x110f0, 0x110f9, + 0x11100, 0x11134, + 0x11136, 0x11147, + 0x11150, 0x11176, + 0x11180, 0x111df, + 0x111e1, 0x111f4, + 0x11200, 0x11211, + 0x11213, 0x11241, + 0x11280, 0x11286, + 0x11288, 0x11288, + 0x1128a, 0x1128d, + 0x1128f, 0x1129d, + 0x1129f, 0x112a9, + 0x112b0, 0x112ea, + 0x112f0, 0x112f9, + 0x11300, 0x11303, + 0x11305, 0x1130c, + 0x1130f, 0x11310, + 0x11313, 0x11328, + 0x1132a, 0x11330, + 0x11332, 0x11333, + 0x11335, 0x11339, + 0x1133b, 0x11344, + 0x11347, 0x11348, + 0x1134b, 0x1134d, + 0x11350, 0x11350, + 0x11357, 0x11357, + 0x1135d, 0x11363, + 0x11366, 0x1136c, + 0x11370, 0x11374, + 0x11400, 0x1145b, + 0x1145d, 0x11461, + 0x11480, 0x114c7, + 0x114d0, 0x114d9, + 0x11580, 0x115b5, + 0x115b8, 0x115dd, + 0x11600, 0x11644, + 0x11650, 0x11659, + 0x11660, 0x1166c, + 0x11680, 0x116b9, + 0x116c0, 0x116c9, + 0x11700, 0x1171a, + 0x1171d, 0x1172b, + 0x11730, 0x11746, + 0x11800, 0x1183b, + 0x118a0, 0x118f2, + 0x118ff, 0x11906, + 0x11909, 0x11909, + 0x1190c, 0x11913, + 0x11915, 0x11916, + 0x11918, 0x11935, + 0x11937, 0x11938, + 0x1193b, 0x11946, + 0x11950, 0x11959, + 0x119a0, 0x119a7, + 0x119aa, 0x119d7, + 0x119da, 0x119e4, + 0x11a00, 0x11a47, + 0x11a50, 0x11aa2, + 0x11ab0, 0x11af8, + 0x11b00, 0x11b09, + 0x11c00, 0x11c08, + 0x11c0a, 0x11c36, + 0x11c38, 0x11c45, + 0x11c50, 0x11c6c, + 0x11c70, 0x11c8f, + 0x11c92, 0x11ca7, + 0x11ca9, 0x11cb6, + 0x11d00, 0x11d06, + 0x11d08, 0x11d09, + 0x11d0b, 0x11d36, + 0x11d3a, 0x11d3a, + 0x11d3c, 0x11d3d, + 0x11d3f, 0x11d47, + 0x11d50, 0x11d59, + 0x11d60, 0x11d65, + 0x11d67, 0x11d68, + 0x11d6a, 0x11d8e, + 0x11d90, 0x11d91, + 0x11d93, 0x11d98, + 0x11da0, 0x11da9, + 0x11ee0, 0x11ef8, + 0x11f00, 0x11f10, + 0x11f12, 0x11f3a, + 0x11f3e, 0x11f59, + 0x11fb0, 0x11fb0, + 0x11fc0, 0x11ff1, + 0x11fff, 0x12399, + 0x12400, 0x1246e, + 0x12470, 0x12474, + 0x12480, 0x12543, + 0x12f90, 0x12ff2, + 0x13000, 0x13455, + 0x14400, 0x14646, + 0x16800, 0x16a38, + 0x16a40, 0x16a5e, + 0x16a60, 0x16a69, + 0x16a6e, 0x16abe, + 0x16ac0, 0x16ac9, + 0x16ad0, 0x16aed, + 0x16af0, 0x16af5, + 0x16b00, 0x16b45, + 0x16b50, 0x16b59, + 0x16b5b, 0x16b61, + 0x16b63, 0x16b77, + 0x16b7d, 0x16b8f, + 0x16e40, 0x16e9a, + 0x16f00, 0x16f4a, + 0x16f4f, 0x16f87, + 0x16f8f, 0x16f9f, + 0x16fe0, 0x16fe4, + 0x16ff0, 0x16ff1, + 0x17000, 0x187f7, + 0x18800, 0x18cd5, + 0x18d00, 0x18d08, + 0x1aff0, 0x1aff3, + 0x1aff5, 0x1affb, + 0x1affd, 0x1affe, + 0x1b000, 0x1b122, + 0x1b132, 0x1b132, + 0x1b150, 0x1b152, + 0x1b155, 0x1b155, + 0x1b164, 0x1b167, + 0x1b170, 0x1b2fb, + 0x1bc00, 0x1bc6a, + 0x1bc70, 0x1bc7c, + 0x1bc80, 0x1bc88, + 0x1bc90, 0x1bc99, + 0x1bc9c, 0x1bca3, + 0x1cf00, 0x1cf2d, + 0x1cf30, 0x1cf46, + 0x1cf50, 0x1cfc3, + 0x1d000, 0x1d0f5, + 0x1d100, 0x1d126, + 0x1d129, 0x1d1ea, + 0x1d200, 0x1d245, + 0x1d2c0, 0x1d2d3, + 0x1d2e0, 0x1d2f3, + 0x1d300, 0x1d356, + 0x1d360, 0x1d378, + 0x1d400, 0x1d454, + 0x1d456, 0x1d49c, + 0x1d49e, 0x1d49f, + 0x1d4a2, 0x1d4a2, + 0x1d4a5, 0x1d4a6, + 0x1d4a9, 0x1d4ac, + 0x1d4ae, 0x1d4b9, + 0x1d4bb, 0x1d4bb, + 0x1d4bd, 0x1d4c3, + 0x1d4c5, 0x1d505, + 0x1d507, 0x1d50a, + 0x1d50d, 0x1d514, + 0x1d516, 0x1d51c, + 0x1d51e, 0x1d539, + 0x1d53b, 0x1d53e, + 0x1d540, 0x1d544, + 0x1d546, 0x1d546, + 0x1d54a, 0x1d550, + 0x1d552, 0x1d6a5, + 0x1d6a8, 0x1d7cb, + 0x1d7ce, 0x1da8b, + 0x1da9b, 0x1da9f, + 0x1daa1, 0x1daaf, + 0x1df00, 0x1df1e, + 0x1df25, 0x1df2a, + 0x1e000, 0x1e006, + 0x1e008, 0x1e018, + 0x1e01b, 0x1e021, + 0x1e023, 0x1e024, + 0x1e026, 0x1e02a, + 0x1e030, 0x1e06d, + 0x1e08f, 0x1e08f, + 0x1e100, 0x1e12c, + 0x1e130, 0x1e13d, + 0x1e140, 0x1e149, + 0x1e14e, 0x1e14f, + 0x1e290, 0x1e2ae, + 0x1e2c0, 0x1e2f9, + 0x1e2ff, 0x1e2ff, + 0x1e4d0, 0x1e4f9, + 0x1e7e0, 0x1e7e6, + 0x1e7e8, 0x1e7eb, + 0x1e7ed, 0x1e7ee, + 0x1e7f0, 0x1e7fe, + 0x1e800, 0x1e8c4, + 0x1e8c7, 0x1e8d6, + 0x1e900, 0x1e94b, + 0x1e950, 0x1e959, + 0x1e95e, 0x1e95f, + 0x1ec71, 0x1ecb4, + 0x1ed01, 0x1ed3d, + 0x1ee00, 0x1ee03, + 0x1ee05, 0x1ee1f, + 0x1ee21, 0x1ee22, + 0x1ee24, 0x1ee24, + 0x1ee27, 0x1ee27, + 0x1ee29, 0x1ee32, + 0x1ee34, 0x1ee37, + 0x1ee39, 0x1ee39, + 0x1ee3b, 0x1ee3b, + 0x1ee42, 0x1ee42, + 0x1ee47, 0x1ee47, + 0x1ee49, 0x1ee49, + 0x1ee4b, 0x1ee4b, + 0x1ee4d, 0x1ee4f, + 0x1ee51, 0x1ee52, + 0x1ee54, 0x1ee54, + 0x1ee57, 0x1ee57, + 0x1ee59, 0x1ee59, + 0x1ee5b, 0x1ee5b, + 0x1ee5d, 0x1ee5d, + 0x1ee5f, 0x1ee5f, + 0x1ee61, 0x1ee62, + 0x1ee64, 0x1ee64, + 0x1ee67, 0x1ee6a, + 0x1ee6c, 0x1ee72, + 0x1ee74, 0x1ee77, + 0x1ee79, 0x1ee7c, + 0x1ee7e, 0x1ee7e, + 0x1ee80, 0x1ee89, + 0x1ee8b, 0x1ee9b, + 0x1eea1, 0x1eea3, + 0x1eea5, 0x1eea9, + 0x1eeab, 0x1eebb, + 0x1eef0, 0x1eef1, + 0x1f000, 0x1f02b, + 0x1f030, 0x1f093, + 0x1f0a0, 0x1f0ae, + 0x1f0b1, 0x1f0bf, + 0x1f0c1, 0x1f0cf, + 0x1f0d1, 0x1f0f5, + 0x1f100, 0x1f1ad, + 0x1f1e6, 0x1f202, + 0x1f210, 0x1f23b, + 0x1f240, 0x1f248, + 0x1f250, 0x1f251, + 0x1f260, 0x1f265, + 0x1f300, 0x1f6d7, + 0x1f6dc, 0x1f6ec, + 0x1f6f0, 0x1f6fc, + 0x1f700, 0x1f776, + 0x1f77b, 0x1f7d9, + 0x1f7e0, 0x1f7eb, + 0x1f7f0, 0x1f7f0, + 0x1f800, 0x1f80b, + 0x1f810, 0x1f847, + 0x1f850, 0x1f859, + 0x1f860, 0x1f887, + 0x1f890, 0x1f8ad, + 0x1f8b0, 0x1f8b1, + 0x1f900, 0x1fa53, + 0x1fa60, 0x1fa6d, + 0x1fa70, 0x1fa7c, + 0x1fa80, 0x1fa88, + 0x1fa90, 0x1fabd, + 0x1fabf, 0x1fac5, + 0x1face, 0x1fadb, + 0x1fae0, 0x1fae8, + 0x1faf0, 0x1faf8, + 0x1fb00, 0x1fb92, + 0x1fb94, 0x1fbca, + 0x1fbf0, 0x1fbf9, + 0x1fffe, 0x2a6df, + 0x2a700, 0x2b739, + 0x2b740, 0x2b81d, + 0x2b820, 0x2cea1, + 0x2ceb0, 0x2ebe0, + 0x2f800, 0x2fa1d, + 0x2fffe, 0x3134a, + 0x31350, 0x323af, + 0x3fffe, 0x3ffff, + 0x4fffe, 0x4ffff, + 0x5fffe, 0x5ffff, + 0x6fffe, 0x6ffff, + 0x7fffe, 0x7ffff, + 0x8fffe, 0x8ffff, + 0x9fffe, 0x9ffff, + 0xafffe, 0xaffff, + 0xbfffe, 0xbffff, + 0xcfffe, 0xcffff, + 0xdfffe, 0xdffff, + 0xe0001, 0xe0001, + 0xe0020, 0xe007f, + 0xe0100, 0xe01ef, + 0xefffe, 0x10ffff, +}; /* CR_Age_15_0 */ + #endif /* USE_UNICODE_AGE_PROPERTIES */ /* 'Grapheme_Cluster_Break_Prepend': Grapheme_Cluster_Break=Prepend */ static const OnigCodePoint CR_Grapheme_Cluster_Break_Prepend[] = { - 14, + 15, 0x0600, 0x0605, 0x06dd, 0x06dd, 0x070f, 0x070f, @@ -36650,6 +37652,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Prepend[] = { 0x11a3a, 0x11a3a, 0x11a84, 0x11a89, 0x11d46, 0x11d46, + 0x11f02, 0x11f02, }; /* CR_Grapheme_Cluster_Break_Prepend */ /* 'Grapheme_Cluster_Break_CR': Grapheme_Cluster_Break=CR */ @@ -36677,7 +37680,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Control[] = { 0x2060, 0x206f, 0xfeff, 0xfeff, 0xfff0, 0xfffb, - 0x13430, 0x13438, + 0x13430, 0x1343f, 0x1bca0, 0x1bca3, 0x1d173, 0x1d17a, 0xe0000, 0xe001f, @@ -36687,7 +37690,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Control[] = { /* 'Grapheme_Cluster_Break_Extend': Grapheme_Cluster_Break=Extend */ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { - 354, + 364, 0x0300, 0x036f, 0x0483, 0x0489, 0x0591, 0x05bd, @@ -36790,7 +37793,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { 0x0e47, 0x0e4e, 0x0eb1, 0x0eb1, 0x0eb4, 0x0ebc, - 0x0ec8, 0x0ecd, + 0x0ec8, 0x0ece, 0x0f18, 0x0f19, 0x0f35, 0x0f35, 0x0f37, 0x0f37, @@ -36919,6 +37922,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { 0x10ae5, 0x10ae6, 0x10d24, 0x10d27, 0x10eab, 0x10eac, + 0x10efd, 0x10eff, 0x10f46, 0x10f50, 0x10f82, 0x10f85, 0x11001, 0x11001, @@ -36941,6 +37945,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { 0x11234, 0x11234, 0x11236, 0x11237, 0x1123e, 0x1123e, + 0x11241, 0x11241, 0x112df, 0x112df, 0x112e3, 0x112ea, 0x11300, 0x11301, @@ -37008,6 +38013,12 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { 0x11d95, 0x11d95, 0x11d97, 0x11d97, 0x11ef3, 0x11ef4, + 0x11f00, 0x11f01, + 0x11f36, 0x11f3a, + 0x11f40, 0x11f40, + 0x11f42, 0x11f42, + 0x13440, 0x13440, + 0x13447, 0x13455, 0x16af0, 0x16af4, 0x16b30, 0x16b36, 0x16f4f, 0x16f4f, @@ -37034,9 +38045,11 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { 0x1e01b, 0x1e021, 0x1e023, 0x1e024, 0x1e026, 0x1e02a, + 0x1e08f, 0x1e08f, 0x1e130, 0x1e136, 0x1e2ae, 0x1e2ae, 0x1e2ec, 0x1e2ef, + 0x1e4ec, 0x1e4ef, 0x1e8d0, 0x1e8d6, 0x1e944, 0x1e94a, 0x1f3fb, 0x1f3ff, @@ -37049,7 +38062,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_Extend[] = { /* 'Grapheme_Cluster_Break_SpacingMark': Grapheme_Cluster_Break=SpacingMark */ static const OnigCodePoint CR_Grapheme_Cluster_Break_SpacingMark[] = { - 161, + 165, 0x0903, 0x0903, 0x093b, 0x093b, 0x093e, 0x0940, @@ -37081,6 +38094,7 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_SpacingMark[] = { 0x0cc3, 0x0cc4, 0x0cc7, 0x0cc8, 0x0cca, 0x0ccb, + 0x0cf3, 0x0cf3, 0x0d02, 0x0d03, 0x0d3f, 0x0d40, 0x0d46, 0x0d48, @@ -37183,7 +38197,6 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_SpacingMark[] = { 0x116ac, 0x116ac, 0x116ae, 0x116af, 0x116b6, 0x116b6, - 0x11720, 0x11721, 0x11726, 0x11726, 0x1182c, 0x1182e, 0x11838, 0x11838, @@ -37207,6 +38220,10 @@ static const OnigCodePoint CR_Grapheme_Cluster_Break_SpacingMark[] = { 0x11d93, 0x11d94, 0x11d96, 0x11d96, 0x11ef5, 0x11ef6, + 0x11f03, 0x11f03, + 0x11f34, 0x11f35, + 0x11f3e, 0x11f3f, + 0x11f41, 0x11f41, 0x16f51, 0x16f87, 0x16ff0, 0x16ff1, 0x1d166, 0x1d166, @@ -39275,6 +40292,12 @@ static const OnigCodePoint CR_In_Yezidi[] = { 0x10e80, 0x10ebf, }; /* CR_In_Yezidi */ +/* 'In_Arabic_Extended_C': Block */ +static const OnigCodePoint CR_In_Arabic_Extended_C[] = { + 1, + 0x10ec0, 0x10eff, +}; /* CR_In_Arabic_Extended_C */ + /* 'In_Old_Sogdian': Block */ static const OnigCodePoint CR_In_Old_Sogdian[] = { 1, @@ -39458,6 +40481,12 @@ static const OnigCodePoint CR_In_Pau_Cin_Hau[] = { 0x11ac0, 0x11aff, }; /* CR_In_Pau_Cin_Hau */ +/* 'In_Devanagari_Extended_A': Block */ +static const OnigCodePoint CR_In_Devanagari_Extended_A[] = { + 1, + 0x11b00, 0x11b5f, +}; /* CR_In_Devanagari_Extended_A */ + /* 'In_Bhaiksuki': Block */ static const OnigCodePoint CR_In_Bhaiksuki[] = { 1, @@ -39488,6 +40517,12 @@ static const OnigCodePoint CR_In_Makasar[] = { 0x11ee0, 0x11eff, }; /* CR_In_Makasar */ +/* 'In_Kawi': Block */ +static const OnigCodePoint CR_In_Kawi[] = { + 1, + 0x11f00, 0x11f5f, +}; /* CR_In_Kawi */ + /* 'In_Lisu_Supplement': Block */ static const OnigCodePoint CR_In_Lisu_Supplement[] = { 1, @@ -39533,7 +40568,7 @@ static const OnigCodePoint CR_In_Egyptian_Hieroglyphs[] = { /* 'In_Egyptian_Hieroglyph_Format_Controls': Block */ static const OnigCodePoint CR_In_Egyptian_Hieroglyph_Format_Controls[] = { 1, - 0x13430, 0x1343f, + 0x13430, 0x1345f, }; /* CR_In_Egyptian_Hieroglyph_Format_Controls */ /* 'In_Anatolian_Hieroglyphs': Block */ @@ -39680,6 +40715,12 @@ static const OnigCodePoint CR_In_Ancient_Greek_Musical_Notation[] = { 0x1d200, 0x1d24f, }; /* CR_In_Ancient_Greek_Musical_Notation */ +/* 'In_Kaktovik_Numerals': Block */ +static const OnigCodePoint CR_In_Kaktovik_Numerals[] = { + 1, + 0x1d2c0, 0x1d2df, +}; /* CR_In_Kaktovik_Numerals */ + /* 'In_Mayan_Numerals': Block */ static const OnigCodePoint CR_In_Mayan_Numerals[] = { 1, @@ -39722,6 +40763,12 @@ static const OnigCodePoint CR_In_Glagolitic_Supplement[] = { 0x1e000, 0x1e02f, }; /* CR_In_Glagolitic_Supplement */ +/* 'In_Cyrillic_Extended_D': Block */ +static const OnigCodePoint CR_In_Cyrillic_Extended_D[] = { + 1, + 0x1e030, 0x1e08f, +}; /* CR_In_Cyrillic_Extended_D */ + /* 'In_Nyiakeng_Puachue_Hmong': Block */ static const OnigCodePoint CR_In_Nyiakeng_Puachue_Hmong[] = { 1, @@ -39740,6 +40787,12 @@ static const OnigCodePoint CR_In_Wancho[] = { 0x1e2c0, 0x1e2ff, }; /* CR_In_Wancho */ +/* 'In_Nag_Mundari': Block */ +static const OnigCodePoint CR_In_Nag_Mundari[] = { + 1, + 0x1e4d0, 0x1e4ff, +}; /* CR_In_Nag_Mundari */ + /* 'In_Ethiopic_Extended_B': Block */ static const OnigCodePoint CR_In_Ethiopic_Extended_B[] = { 1, @@ -39914,6 +40967,12 @@ static const OnigCodePoint CR_In_CJK_Unified_Ideographs_Extension_G[] = { 0x30000, 0x3134f, }; /* CR_In_CJK_Unified_Ideographs_Extension_G */ +/* 'In_CJK_Unified_Ideographs_Extension_H': Block */ +static const OnigCodePoint CR_In_CJK_Unified_Ideographs_Extension_H[] = { + 1, + 0x31350, 0x323af, +}; /* CR_In_CJK_Unified_Ideographs_Extension_H */ + /* 'In_Tags': Block */ static const OnigCodePoint CR_In_Tags[] = { 1, @@ -39952,7 +41011,6 @@ static const OnigCodePoint CR_In_No_Block[] = { 0x10bb0, 0x10bff, 0x10c50, 0x10c7f, 0x10d40, 0x10e5f, - 0x10ec0, 0x10eff, 0x11250, 0x1127f, 0x11380, 0x113ff, 0x114e0, 0x1157f, @@ -39960,12 +41018,12 @@ static const OnigCodePoint CR_In_No_Block[] = { 0x11750, 0x117ff, 0x11850, 0x1189f, 0x11960, 0x1199f, - 0x11b00, 0x11bff, + 0x11b60, 0x11bff, 0x11cc0, 0x11cff, 0x11db0, 0x11edf, - 0x11f00, 0x11faf, + 0x11f60, 0x11faf, 0x12550, 0x12f8f, - 0x13440, 0x143ff, + 0x13460, 0x143ff, 0x14680, 0x167ff, 0x16b90, 0x16e3f, 0x16ea0, 0x16eff, @@ -39974,12 +41032,13 @@ static const OnigCodePoint CR_In_No_Block[] = { 0x1b300, 0x1bbff, 0x1bcb0, 0x1ceff, 0x1cfd0, 0x1cfff, - 0x1d250, 0x1d2df, + 0x1d250, 0x1d2bf, 0x1d380, 0x1d3ff, 0x1dab0, 0x1deff, - 0x1e030, 0x1e0ff, + 0x1e090, 0x1e0ff, 0x1e150, 0x1e28f, - 0x1e300, 0x1e7df, + 0x1e300, 0x1e4cf, + 0x1e500, 0x1e7df, 0x1e8e0, 0x1e8ff, 0x1e960, 0x1ec6f, 0x1ecc0, 0x1ecff, @@ -39989,7 +41048,7 @@ static const OnigCodePoint CR_In_No_Block[] = { 0x2a6e0, 0x2a6ff, 0x2ebf0, 0x2f7ff, 0x2fa20, 0x2ffff, - 0x31350, 0xdffff, + 0x323b0, 0xdffff, 0xe0080, 0xe00ff, 0xe01f0, 0xeffff, }; /* CR_In_No_Block */ @@ -40233,6 +41292,8 @@ static const OnigCodePoint* const CodeRanges[] = { CR_Tangsa, CR_Toto, CR_Vithkuqi, + CR_Kawi, + CR_Nag_Mundari, CR_White_Space, CR_Bidi_Control, CR_Join_Control, @@ -40299,6 +41360,7 @@ static const OnigCodePoint* const CodeRanges[] = { CR_Age_12_1, CR_Age_13_0, CR_Age_14_0, + CR_Age_15_0, #endif /* USE_UNICODE_AGE_PROPERTIES */ CR_Grapheme_Cluster_Break_Prepend, CR_Grapheme_Cluster_Break_CR, @@ -40522,6 +41584,7 @@ static const OnigCodePoint* const CodeRanges[] = { CR_In_Hanifi_Rohingya, CR_In_Rumi_Numeral_Symbols, CR_In_Yezidi, + CR_In_Arabic_Extended_C, CR_In_Old_Sogdian, CR_In_Sogdian, CR_In_Old_Uyghur, @@ -40553,11 +41616,13 @@ static const OnigCodePoint* const CodeRanges[] = { CR_In_Soyombo, CR_In_Unified_Canadian_Aboriginal_Syllabics_Extended_A, CR_In_Pau_Cin_Hau, + CR_In_Devanagari_Extended_A, CR_In_Bhaiksuki, CR_In_Marchen, CR_In_Masaram_Gondi, CR_In_Gunjala_Gondi, CR_In_Makasar, + CR_In_Kawi, CR_In_Lisu_Supplement, CR_In_Tamil_Supplement, CR_In_Cuneiform, @@ -40590,6 +41655,7 @@ static const OnigCodePoint* const CodeRanges[] = { CR_In_Byzantine_Musical_Symbols, CR_In_Musical_Symbols, CR_In_Ancient_Greek_Musical_Notation, + CR_In_Kaktovik_Numerals, CR_In_Mayan_Numerals, CR_In_Tai_Xuan_Jing_Symbols, CR_In_Counting_Rod_Numerals, @@ -40597,9 +41663,11 @@ static const OnigCodePoint* const CodeRanges[] = { CR_In_Sutton_SignWriting, CR_In_Latin_Extended_G, CR_In_Glagolitic_Supplement, + CR_In_Cyrillic_Extended_D, CR_In_Nyiakeng_Puachue_Hmong, CR_In_Toto, CR_In_Wancho, + CR_In_Nag_Mundari, CR_In_Ethiopic_Extended_B, CR_In_Mende_Kikakui, CR_In_Adlam, @@ -40629,6 +41697,7 @@ static const OnigCodePoint* const CodeRanges[] = { CR_In_CJK_Unified_Ideographs_Extension_F, CR_In_CJK_Compatibility_Ideographs_Supplement, CR_In_CJK_Unified_Ideographs_Extension_G, + CR_In_CJK_Unified_Ideographs_Extension_H, CR_In_Tags, CR_In_Variation_Selectors_Supplement, CR_In_Supplementary_Private_Use_Area_A, @@ -40653,9 +41722,9 @@ static const struct uniname2ctype_struct *uniname2ctype_p(register const char *s /* maximum key range = 15, duplicates = 0 */ #else /* USE_UNICODE_PROPERTIES */ #ifndef USE_UNICODE_AGE_PROPERTIES -#define TOTAL_KEYWORDS 856 +#define TOTAL_KEYWORDS 866 #else /* USE_UNICODE_AGE_PROPERTIES */ -#define TOTAL_KEYWORDS 880 +#define TOTAL_KEYWORDS 891 #endif /* USE_UNICODE_AGE_PROPERTIES */ #define MIN_WORD_LENGTH 1 #define MAX_WORD_LENGTH 45 @@ -40704,13 +41773,13 @@ uniname2ctype_hash (register const char *str, register size_t len) 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, #else /* USE_UNICODE_AGE_PROPERTIES */ 6099, 6099, 6099, 6099, 6099, 6099, 12, 6099, 3, 1, - 4, 8, 36, 24, 14, 16, 10, 7, 6099, 6099, + 4, 8, 32, 26, 14, 17, 10, 7, 6099, 6099, #endif /* USE_UNICODE_AGE_PROPERTIES */ 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 6099, 1, 1425, 113, - 437, 37, 1086, 1071, 1051, 4, 1267, 9, 500, 88, + 437, 37, 1086, 1071, 1051, 4, 1984, 9, 500, 88, 8, 18, 1371, 1287, 54, 203, 310, 619, 1958, 603, 275, 1624, 44, 1, 22, 6099, 6099, 6099, 6099, 6099 #endif /* USE_UNICODE_PROPERTIES */ @@ -40940,6 +42009,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str636[sizeof("innewa")]; char uniname2ctype_pool_str639[sizeof("sk")]; char uniname2ctype_pool_str642[sizeof("control")]; + char uniname2ctype_pool_str643[sizeof("inkawi")]; char uniname2ctype_pool_str645[sizeof("inancientsymbols")]; char uniname2ctype_pool_str647[sizeof("palm")]; char uniname2ctype_pool_str650[sizeof("inlycian")]; @@ -40954,6 +42024,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str695[sizeof("inwarangciti")]; char uniname2ctype_pool_str696[sizeof("sora")]; char uniname2ctype_pool_str697[sizeof("inopticalcharacterrecognition")]; + char uniname2ctype_pool_str700[sizeof("kawi")]; char uniname2ctype_pool_str703[sizeof("inoldsogdian")]; char uniname2ctype_pool_str705[sizeof("inmalayalam")]; char uniname2ctype_pool_str707[sizeof("bamum")]; @@ -41074,11 +42145,13 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1182[sizeof("inogham")]; char uniname2ctype_pool_str1183[sizeof("cher")]; char uniname2ctype_pool_str1185[sizeof("chakma")]; + char uniname2ctype_pool_str1186[sizeof("inkaktoviknumerals")]; char uniname2ctype_pool_str1190[sizeof("emoji")]; char uniname2ctype_pool_str1191[sizeof("insiddham")]; char uniname2ctype_pool_str1197[sizeof("cherokee")]; char uniname2ctype_pool_str1198[sizeof("khar")]; char uniname2ctype_pool_str1203[sizeof("inmongolian")]; + char uniname2ctype_pool_str1204[sizeof("innagmundari")]; char uniname2ctype_pool_str1207[sizeof("incherokeesupplement")]; char uniname2ctype_pool_str1209[sizeof("manichaean")]; char uniname2ctype_pool_str1212[sizeof("inolchiki")]; @@ -41122,48 +42195,46 @@ struct uniname2ctype_pool_t #ifdef USE_UNICODE_AGE_PROPERTIES char uniname2ctype_pool_str1257[sizeof("age=6.0")]; char uniname2ctype_pool_str1258[sizeof("age=6.2")]; - char uniname2ctype_pool_str1259[sizeof("age=7.0")]; + char uniname2ctype_pool_str1259[sizeof("age=15.0")]; + char uniname2ctype_pool_str1260[sizeof("age=7.0")]; char uniname2ctype_pool_str1262[sizeof("age=6.3")]; #endif /* USE_UNICODE_AGE_PROPERTIES */ char uniname2ctype_pool_str1263[sizeof("cwt")]; #ifdef USE_UNICODE_AGE_PROPERTIES - char uniname2ctype_pool_str1265[sizeof("age=5.1")]; + char uniname2ctype_pool_str1265[sizeof("age=14.0")]; #endif /* USE_UNICODE_AGE_PROPERTIES */ char uniname2ctype_pool_str1266[sizeof("unassigned")]; #ifdef USE_UNICODE_AGE_PROPERTIES - char uniname2ctype_pool_str1267[sizeof("age=5.0")]; - char uniname2ctype_pool_str1268[sizeof("age=5.2")]; - char uniname2ctype_pool_str1269[sizeof("age=14.0")]; + char uniname2ctype_pool_str1267[sizeof("age=5.1")]; + char uniname2ctype_pool_str1269[sizeof("age=5.0")]; + char uniname2ctype_pool_str1270[sizeof("age=5.2")]; #endif /* USE_UNICODE_AGE_PROPERTIES */ char uniname2ctype_pool_str1271[sizeof("diacritic")]; +#ifdef USE_UNICODE_AGE_PROPERTIES + char uniname2ctype_pool_str1273[sizeof("age=4.1")]; +#endif /* USE_UNICODE_AGE_PROPERTIES */ char uniname2ctype_pool_str1274[sizeof("ahom")]; #ifdef USE_UNICODE_AGE_PROPERTIES - char uniname2ctype_pool_str1277[sizeof("age=4.1")]; - char uniname2ctype_pool_str1279[sizeof("age=4.0")]; + char uniname2ctype_pool_str1275[sizeof("age=4.0")]; #endif /* USE_UNICODE_AGE_PROPERTIES */ char uniname2ctype_pool_str1282[sizeof("incjkunifiedideographsextensione")]; - char uniname2ctype_pool_str1284[sizeof("hani")]; char uniname2ctype_pool_str1285[sizeof("khmr")]; - char uniname2ctype_pool_str1287[sizeof("han")]; char uniname2ctype_pool_str1289[sizeof("insinhala")]; char uniname2ctype_pool_str1292[sizeof("inmiscellaneoustechnical")]; char uniname2ctype_pool_str1297[sizeof("saur")]; - char uniname2ctype_pool_str1298[sizeof("hano")]; char uniname2ctype_pool_str1300[sizeof("guru")]; char uniname2ctype_pool_str1301[sizeof("sundanese")]; char uniname2ctype_pool_str1306[sizeof("punct")]; char uniname2ctype_pool_str1314[sizeof("paucinhau")]; char uniname2ctype_pool_str1317[sizeof("gurmukhi")]; - char uniname2ctype_pool_str1323[sizeof("inkhojki")]; - char uniname2ctype_pool_str1327[sizeof("hanunoo")]; char uniname2ctype_pool_str1328[sizeof("chorasmian")]; - char uniname2ctype_pool_str1330[sizeof("hira")]; char uniname2ctype_pool_str1331[sizeof("logicalorderexception")]; char uniname2ctype_pool_str1340[sizeof("khmer")]; char uniname2ctype_pool_str1343[sizeof("limbu")]; char uniname2ctype_pool_str1349[sizeof("chrs")]; char uniname2ctype_pool_str1352[sizeof("oriya")]; char uniname2ctype_pool_str1354[sizeof("inscriptionalpahlavi")]; + char uniname2ctype_pool_str1356[sizeof("incyrillicextendedd")]; char uniname2ctype_pool_str1358[sizeof("incjkunifiedideographsextensionc")]; char uniname2ctype_pool_str1360[sizeof("cntrl")]; char uniname2ctype_pool_str1365[sizeof("inlatinextendedadditional")]; @@ -41231,7 +42302,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1587[sizeof("indogra")]; char uniname2ctype_pool_str1597[sizeof("arab")]; char uniname2ctype_pool_str1598[sizeof("medefaidrin")]; - char uniname2ctype_pool_str1601[sizeof("hatran")]; char uniname2ctype_pool_str1607[sizeof("inshorthandformatcontrols")]; char uniname2ctype_pool_str1613[sizeof("phli")]; char uniname2ctype_pool_str1617[sizeof("inimperialaramaic")]; @@ -41240,7 +42310,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1623[sizeof("inanatolianhieroglyphs")]; char uniname2ctype_pool_str1629[sizeof("punctuation")]; char uniname2ctype_pool_str1635[sizeof("graphemeextend")]; - char uniname2ctype_pool_str1636[sizeof("hatr")]; char uniname2ctype_pool_str1643[sizeof("cwl")]; char uniname2ctype_pool_str1644[sizeof("vith")]; char uniname2ctype_pool_str1654[sizeof("ingeometricshapes")]; @@ -41298,7 +42367,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1845[sizeof("oidc")]; char uniname2ctype_pool_str1848[sizeof("bopo")]; char uniname2ctype_pool_str1851[sizeof("cuneiform")]; - char uniname2ctype_pool_str1857[sizeof("hex")]; char uniname2ctype_pool_str1866[sizeof("caseignorable")]; char uniname2ctype_pool_str1871[sizeof("inoldpersian")]; char uniname2ctype_pool_str1881[sizeof("cwu")]; @@ -41317,10 +42385,8 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1935[sizeof("oids")]; char uniname2ctype_pool_str1936[sizeof("inarabicextendeda")]; char uniname2ctype_pool_str1941[sizeof("modifierletter")]; - char uniname2ctype_pool_str1948[sizeof("gujr")]; char uniname2ctype_pool_str1950[sizeof("incjksymbolsandpunctuation")]; char uniname2ctype_pool_str1956[sizeof("olower")]; - char uniname2ctype_pool_str1957[sizeof("gujarati")]; char uniname2ctype_pool_str1958[sizeof("bopomofo")]; char uniname2ctype_pool_str1964[sizeof("inlisu")]; char uniname2ctype_pool_str1967[sizeof("inoldpermic")]; @@ -41334,19 +42400,26 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str1993[sizeof("inbalinese")]; char uniname2ctype_pool_str1994[sizeof("sorasompeng")]; char uniname2ctype_pool_str1996[sizeof("closepunctuation")]; + char uniname2ctype_pool_str2001[sizeof("hani")]; char uniname2ctype_pool_str2002[sizeof("inmayannumerals")]; + char uniname2ctype_pool_str2004[sizeof("han")]; char uniname2ctype_pool_str2006[sizeof("inmiscellaneousmathematicalsymbolsb")]; char uniname2ctype_pool_str2010[sizeof("inlepcha")]; char uniname2ctype_pool_str2011[sizeof("patsyn")]; char uniname2ctype_pool_str2012[sizeof("inlisusupplement")]; char uniname2ctype_pool_str2014[sizeof("insyriacsupplement")]; + char uniname2ctype_pool_str2015[sizeof("hano")]; char uniname2ctype_pool_str2016[sizeof("newa")]; char uniname2ctype_pool_str2023[sizeof("spacingmark")]; char uniname2ctype_pool_str2024[sizeof("inpalmyrene")]; char uniname2ctype_pool_str2026[sizeof("takr")]; char uniname2ctype_pool_str2033[sizeof("xposixpunct")]; + char uniname2ctype_pool_str2040[sizeof("inkhojki")]; char uniname2ctype_pool_str2042[sizeof("taile")]; char uniname2ctype_pool_str2043[sizeof("assigned")]; + char uniname2ctype_pool_str2044[sizeof("hanunoo")]; + char uniname2ctype_pool_str2047[sizeof("hira")]; + char uniname2ctype_pool_str2048[sizeof("inarabicextendedc")]; char uniname2ctype_pool_str2062[sizeof("newtailue")]; char uniname2ctype_pool_str2070[sizeof("space")]; char uniname2ctype_pool_str2073[sizeof("intelugu")]; @@ -41397,41 +42470,35 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2278[sizeof("shaw")]; char uniname2ctype_pool_str2279[sizeof("palmyrene")]; char uniname2ctype_pool_str2283[sizeof("soyo")]; + char uniname2ctype_pool_str2296[sizeof("incjkunifiedideographsextensionh")]; char uniname2ctype_pool_str2305[sizeof("sgnw")]; char uniname2ctype_pool_str2308[sizeof("toto")]; char uniname2ctype_pool_str2312[sizeof("caucasianalbanian")]; char uniname2ctype_pool_str2315[sizeof("inmathematicalalphanumericsymbols")]; char uniname2ctype_pool_str2316[sizeof("incjkunifiedideographsextensiong")]; + char uniname2ctype_pool_str2318[sizeof("hatran")]; char uniname2ctype_pool_str2321[sizeof("taiviet")]; char uniname2ctype_pool_str2323[sizeof("meroitichieroglyphs")]; char uniname2ctype_pool_str2327[sizeof("ingeorgianextended")]; char uniname2ctype_pool_str2331[sizeof("incjkunifiedideographsextensionf")]; char uniname2ctype_pool_str2333[sizeof("oldpersian")]; - char uniname2ctype_pool_str2341[sizeof("mahj")]; char uniname2ctype_pool_str2343[sizeof("induployan")]; char uniname2ctype_pool_str2344[sizeof("incyrillicextendedb")]; char uniname2ctype_pool_str2345[sizeof("dash")]; - char uniname2ctype_pool_str2350[sizeof("mahajani")]; - char uniname2ctype_pool_str2351[sizeof("hang")]; + char uniname2ctype_pool_str2353[sizeof("hatr")]; char uniname2ctype_pool_str2361[sizeof("innyiakengpuachuehmong")]; char uniname2ctype_pool_str2364[sizeof("incombiningdiacriticalmarks")]; - char uniname2ctype_pool_str2370[sizeof("ingujarati")]; char uniname2ctype_pool_str2373[sizeof("nl")]; char uniname2ctype_pool_str2374[sizeof("incombiningdiacriticalmarksforsymbols")]; char uniname2ctype_pool_str2375[sizeof("khudawadi")]; - char uniname2ctype_pool_str2389[sizeof("ingunjalagondi")]; char uniname2ctype_pool_str2397[sizeof("incjkradicalssupplement")]; char uniname2ctype_pool_str2398[sizeof("inglagolitic")]; char uniname2ctype_pool_str2405[sizeof("orkh")]; - char uniname2ctype_pool_str2406[sizeof("hiragana")]; char uniname2ctype_pool_str2414[sizeof("syrc")]; - char uniname2ctype_pool_str2418[sizeof("inrejang")]; char uniname2ctype_pool_str2427[sizeof("surrogate")]; - char uniname2ctype_pool_str2428[sizeof("khoj")]; char uniname2ctype_pool_str2433[sizeof("indevanagari")]; char uniname2ctype_pool_str2434[sizeof("avestan")]; char uniname2ctype_pool_str2437[sizeof("oldpermic")]; - char uniname2ctype_pool_str2438[sizeof("hmng")]; char uniname2ctype_pool_str2440[sizeof("ethi")]; char uniname2ctype_pool_str2451[sizeof("ogam")]; char uniname2ctype_pool_str2454[sizeof("rohg")]; @@ -41439,7 +42506,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2464[sizeof("java")]; char uniname2ctype_pool_str2470[sizeof("inphagspa")]; char uniname2ctype_pool_str2475[sizeof("lepcha")]; - char uniname2ctype_pool_str2476[sizeof("inenclosedcjklettersandmonths")]; + char uniname2ctype_pool_str2476[sizeof("indevanagariextendeda")]; char uniname2ctype_pool_str2478[sizeof("intifinagh")]; char uniname2ctype_pool_str2479[sizeof("intagalog")]; char uniname2ctype_pool_str2481[sizeof("incombiningdiacriticalmarkssupplement")]; @@ -41449,6 +42516,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2513[sizeof("insymbolsandpictographsextendeda")]; char uniname2ctype_pool_str2530[sizeof("syriac")]; char uniname2ctype_pool_str2534[sizeof("inbengali")]; + char uniname2ctype_pool_str2535[sizeof("nagm")]; char uniname2ctype_pool_str2545[sizeof("extendedpictographic")]; char uniname2ctype_pool_str2548[sizeof("buhd")]; char uniname2ctype_pool_str2549[sizeof("javanese")]; @@ -41457,6 +42525,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2567[sizeof("inlatin1supplement")]; char uniname2ctype_pool_str2570[sizeof("ingothic")]; char uniname2ctype_pool_str2572[sizeof("invariationselectors")]; + char uniname2ctype_pool_str2574[sizeof("hex")]; char uniname2ctype_pool_str2575[sizeof("inverticalforms")]; char uniname2ctype_pool_str2576[sizeof("ebase")]; char uniname2ctype_pool_str2582[sizeof("incurrencysymbols")]; @@ -41470,15 +42539,15 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2652[sizeof("invedicextensions")]; char uniname2ctype_pool_str2656[sizeof("inlimbu")]; char uniname2ctype_pool_str2657[sizeof("olditalic")]; - char uniname2ctype_pool_str2660[sizeof("rjng")]; + char uniname2ctype_pool_str2665[sizeof("gujr")]; char uniname2ctype_pool_str2666[sizeof("mathsymbol")]; char uniname2ctype_pool_str2670[sizeof("incjkunifiedideographsextensionb")]; + char uniname2ctype_pool_str2674[sizeof("gujarati")]; char uniname2ctype_pool_str2688[sizeof("phagspa")]; char uniname2ctype_pool_str2689[sizeof("invariationselectorssupplement")]; char uniname2ctype_pool_str2694[sizeof("currencysymbol")]; char uniname2ctype_pool_str2705[sizeof("inlinearbsyllabary")]; char uniname2ctype_pool_str2726[sizeof("wancho")]; - char uniname2ctype_pool_str2738[sizeof("hmnp")]; char uniname2ctype_pool_str2750[sizeof("inpaucinhau")]; char uniname2ctype_pool_str2761[sizeof("other")]; char uniname2ctype_pool_str2762[sizeof("otheridcontinue")]; @@ -41488,7 +42557,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2772[sizeof("warangciti")]; char uniname2ctype_pool_str2775[sizeof("othernumber")]; char uniname2ctype_pool_str2786[sizeof("digit")]; - char uniname2ctype_pool_str2787[sizeof("hebr")]; char uniname2ctype_pool_str2793[sizeof("nonspacingmark")]; char uniname2ctype_pool_str2801[sizeof("titlecaseletter")]; char uniname2ctype_pool_str2808[sizeof("inmeroiticcursive")]; @@ -41508,7 +42576,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2871[sizeof("noncharactercodepoint")]; char uniname2ctype_pool_str2879[sizeof("oldhungarian")]; char uniname2ctype_pool_str2886[sizeof("insymbolsforlegacycomputing")]; - char uniname2ctype_pool_str2901[sizeof("hangul")]; char uniname2ctype_pool_str2902[sizeof("insmallformvariants")]; char uniname2ctype_pool_str2904[sizeof("inhangulsyllables")]; char uniname2ctype_pool_str2905[sizeof("emojipresentation")]; @@ -41520,15 +42587,12 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str2964[sizeof("inpsalterpahlavi")]; char uniname2ctype_pool_str2966[sizeof("whitespace")]; char uniname2ctype_pool_str2967[sizeof("finalpunctuation")]; - char uniname2ctype_pool_str2969[sizeof("hung")]; char uniname2ctype_pool_str2970[sizeof("orya")]; - char uniname2ctype_pool_str2972[sizeof("hexdigit")]; char uniname2ctype_pool_str2980[sizeof("phlp")]; char uniname2ctype_pool_str2984[sizeof("inbamumsupplement")]; char uniname2ctype_pool_str2986[sizeof("buhid")]; char uniname2ctype_pool_str2987[sizeof("paragraphseparator")]; char uniname2ctype_pool_str2988[sizeof("inalphabeticpresentationforms")]; - char uniname2ctype_pool_str2993[sizeof("hluw")]; char uniname2ctype_pool_str2997[sizeof("inlatinextendedg")]; char uniname2ctype_pool_str3001[sizeof("elba")]; char uniname2ctype_pool_str3002[sizeof("changeswhentitlecased")]; @@ -41545,30 +42609,39 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str3048[sizeof("tagbanwa")]; char uniname2ctype_pool_str3052[sizeof("tamil")]; char uniname2ctype_pool_str3053[sizeof("khitansmallscript")]; + char uniname2ctype_pool_str3058[sizeof("mahj")]; + char uniname2ctype_pool_str3067[sizeof("mahajani")]; + char uniname2ctype_pool_str3068[sizeof("hang")]; char uniname2ctype_pool_str3071[sizeof("tirh")]; char uniname2ctype_pool_str3072[sizeof("sylotinagri")]; char uniname2ctype_pool_str3082[sizeof("talu")]; + char uniname2ctype_pool_str3084[sizeof("nagmundari")]; char uniname2ctype_pool_str3086[sizeof("deva")]; + char uniname2ctype_pool_str3087[sizeof("ingujarati")]; char uniname2ctype_pool_str3091[sizeof("deprecated")]; char uniname2ctype_pool_str3099[sizeof("inarabicpresentationformsb")]; char uniname2ctype_pool_str3104[sizeof("devanagari")]; + char uniname2ctype_pool_str3106[sizeof("ingunjalagondi")]; char uniname2ctype_pool_str3107[sizeof("graphemeclusterbreak=t")]; char uniname2ctype_pool_str3109[sizeof("graphemeclusterbreak=lvt")]; char uniname2ctype_pool_str3110[sizeof("taitham")]; char uniname2ctype_pool_str3111[sizeof("nbat")]; char uniname2ctype_pool_str3118[sizeof("telu")]; + char uniname2ctype_pool_str3123[sizeof("hiragana")]; char uniname2ctype_pool_str3125[sizeof("nabataean")]; - char uniname2ctype_pool_str3140[sizeof("inmahjongtiles")]; + char uniname2ctype_pool_str3135[sizeof("inrejang")]; char uniname2ctype_pool_str3142[sizeof("intangutsupplement")]; + char uniname2ctype_pool_str3145[sizeof("khoj")]; + char uniname2ctype_pool_str3155[sizeof("hmng")]; char uniname2ctype_pool_str3157[sizeof("cyprominoan")]; char uniname2ctype_pool_str3158[sizeof("inhebrew")]; char uniname2ctype_pool_str3176[sizeof("inmathematicaloperators")]; char uniname2ctype_pool_str3180[sizeof("inarabicsupplement")]; + char uniname2ctype_pool_str3193[sizeof("inenclosedcjklettersandmonths")]; char uniname2ctype_pool_str3209[sizeof("changeswhenlowercased")]; char uniname2ctype_pool_str3212[sizeof("tangut")]; char uniname2ctype_pool_str3215[sizeof("elbasan")]; char uniname2ctype_pool_str3218[sizeof("osmanya")]; - char uniname2ctype_pool_str3227[sizeof("inyijinghexagramsymbols")]; char uniname2ctype_pool_str3237[sizeof("insuperscriptsandsubscripts")]; char uniname2ctype_pool_str3239[sizeof("graphemeclusterbreak=extend")]; char uniname2ctype_pool_str3240[sizeof("graphemeclusterbreak=prepend")]; @@ -41579,7 +42652,6 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str3275[sizeof("kayahli")]; char uniname2ctype_pool_str3284[sizeof("inplayingcards")]; char uniname2ctype_pool_str3287[sizeof("elym")]; - char uniname2ctype_pool_str3290[sizeof("injavanese")]; char uniname2ctype_pool_str3297[sizeof("graphemeclusterbreak=l")]; char uniname2ctype_pool_str3303[sizeof("graphemeclusterbreak=control")]; char uniname2ctype_pool_str3313[sizeof("ogrext")]; @@ -41595,6 +42667,7 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str3371[sizeof("cypriot")]; char uniname2ctype_pool_str3372[sizeof("any")]; char uniname2ctype_pool_str3373[sizeof("otheruppercase")]; + char uniname2ctype_pool_str3377[sizeof("rjng")]; char uniname2ctype_pool_str3391[sizeof("wspace")]; char uniname2ctype_pool_str3396[sizeof("inindicsiyaqnumbers")]; char uniname2ctype_pool_str3405[sizeof("inprivateusearea")]; @@ -41602,11 +42675,12 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str3428[sizeof("oupper")]; char uniname2ctype_pool_str3433[sizeof("signwriting")]; char uniname2ctype_pool_str3436[sizeof("nushu")]; - char uniname2ctype_pool_str3452[sizeof("hanifirohingya")]; + char uniname2ctype_pool_str3455[sizeof("hmnp")]; char uniname2ctype_pool_str3458[sizeof("upper")]; char uniname2ctype_pool_str3460[sizeof("insupplementalarrowsc")]; char uniname2ctype_pool_str3483[sizeof("omath")]; char uniname2ctype_pool_str3502[sizeof("modifiersymbol")]; + char uniname2ctype_pool_str3504[sizeof("hebr")]; char uniname2ctype_pool_str3505[sizeof("inhalfwidthandfullwidthforms")]; char uniname2ctype_pool_str3511[sizeof("insupplementalmathematicaloperators")]; char uniname2ctype_pool_str3532[sizeof("inpahawhhmong")]; @@ -41615,60 +42689,68 @@ struct uniname2ctype_pool_t char uniname2ctype_pool_str3580[sizeof("dupl")]; char uniname2ctype_pool_str3590[sizeof("ogham")]; char uniname2ctype_pool_str3613[sizeof("dashpunctuation")]; + char uniname2ctype_pool_str3618[sizeof("hangul")]; char uniname2ctype_pool_str3648[sizeof("inhanguljamoextendedb")]; char uniname2ctype_pool_str3659[sizeof("bassavah")]; char uniname2ctype_pool_str3664[sizeof("aghb")]; + char uniname2ctype_pool_str3686[sizeof("hung")]; + char uniname2ctype_pool_str3689[sizeof("hexdigit")]; char uniname2ctype_pool_str3698[sizeof("incypriotsyllabary")]; char uniname2ctype_pool_str3699[sizeof("indivesakuru")]; char uniname2ctype_pool_str3701[sizeof("tibt")]; char uniname2ctype_pool_str3705[sizeof("inlatinextendedb")]; + char uniname2ctype_pool_str3710[sizeof("hluw")]; char uniname2ctype_pool_str3713[sizeof("tibetan")]; char uniname2ctype_pool_str3721[sizeof("inyisyllables")]; char uniname2ctype_pool_str3744[sizeof("oldnortharabian")]; char uniname2ctype_pool_str3754[sizeof("defaultignorablecodepoint")]; char uniname2ctype_pool_str3766[sizeof("inhighprivateusesurrogates")]; - char uniname2ctype_pool_str3770[sizeof("rejang")]; char uniname2ctype_pool_str3799[sizeof("soyombo")]; char uniname2ctype_pool_str3807[sizeof("otherdefaultignorablecodepoint")]; char uniname2ctype_pool_str3842[sizeof("pahawhhmong")]; char uniname2ctype_pool_str3845[sizeof("unifiedideograph")]; char uniname2ctype_pool_str3850[sizeof("othermath")]; char uniname2ctype_pool_str3854[sizeof("changeswhencasefolded")]; + char uniname2ctype_pool_str3857[sizeof("inmahjongtiles")]; char uniname2ctype_pool_str3868[sizeof("dep")]; char uniname2ctype_pool_str3881[sizeof("divesakuru")]; char uniname2ctype_pool_str3884[sizeof("graphemeclusterbreak=lf")]; char uniname2ctype_pool_str3891[sizeof("uppercaseletter")]; char uniname2ctype_pool_str3924[sizeof("insupplementalpunctuation")]; char uniname2ctype_pool_str3942[sizeof("ethiopic")]; + char uniname2ctype_pool_str3944[sizeof("inyijinghexagramsymbols")]; char uniname2ctype_pool_str3949[sizeof("ecomp")]; char uniname2ctype_pool_str3976[sizeof("inglagoliticsupplement")]; - char uniname2ctype_pool_str3978[sizeof("hebrew")]; char uniname2ctype_pool_str3998[sizeof("inbopomofoextended")]; - char uniname2ctype_pool_str4066[sizeof("graphemeclusterbreak=zwj")]; + char uniname2ctype_pool_str4007[sizeof("injavanese")]; char uniname2ctype_pool_str4106[sizeof("otherpunctuation")]; char uniname2ctype_pool_str4116[sizeof("tifinagh")]; char uniname2ctype_pool_str4127[sizeof("tfng")]; + char uniname2ctype_pool_str4169[sizeof("hanifirohingya")]; char uniname2ctype_pool_str4231[sizeof("tavt")]; char uniname2ctype_pool_str4308[sizeof("inboxdrawing")]; char uniname2ctype_pool_str4309[sizeof("oldsoutharabian")]; - char uniname2ctype_pool_str4321[sizeof("hyphen")]; char uniname2ctype_pool_str4348[sizeof("inegyptianhieroglyphs")]; char uniname2ctype_pool_str4361[sizeof("inegyptianhieroglyphformatcontrols")]; char uniname2ctype_pool_str4459[sizeof("tagb")]; + char uniname2ctype_pool_str4487[sizeof("rejang")]; char uniname2ctype_pool_str4604[sizeof("tglg")]; char uniname2ctype_pool_str4626[sizeof("tagalog")]; char uniname2ctype_pool_str4627[sizeof("othergraphemeextend")]; char uniname2ctype_pool_str4674[sizeof("insupplementaryprivateuseareaa")]; char uniname2ctype_pool_str4683[sizeof("inhighsurrogates")]; + char uniname2ctype_pool_str4695[sizeof("hebrew")]; char uniname2ctype_pool_str4734[sizeof("duployan")]; char uniname2ctype_pool_str4755[sizeof("graphemeclusterbreak=v")]; char uniname2ctype_pool_str4756[sizeof("graphemeclusterbreak=lv")]; char uniname2ctype_pool_str4772[sizeof("insupplementalarrowsb")]; + char uniname2ctype_pool_str4783[sizeof("graphemeclusterbreak=zwj")]; char uniname2ctype_pool_str4810[sizeof("telugu")]; char uniname2ctype_pool_str4898[sizeof("zyyy")]; char uniname2ctype_pool_str4982[sizeof("olduyghur")]; char uniname2ctype_pool_str4986[sizeof("inhangulcompatibilityjamo")]; char uniname2ctype_pool_str5018[sizeof("openpunctuation")]; + char uniname2ctype_pool_str5038[sizeof("hyphen")]; char uniname2ctype_pool_str5134[sizeof("insupplementalsymbolsandpictographs")]; char uniname2ctype_pool_str5141[sizeof("egyp")]; char uniname2ctype_pool_str5300[sizeof("nyiakengpuachuehmong")]; @@ -41845,6 +42927,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "innewa", "sk", "control", + "inkawi", "inancientsymbols", "palm", "inlycian", @@ -41859,6 +42942,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inwarangciti", "sora", "inopticalcharacterrecognition", + "kawi", "inoldsogdian", "inmalayalam", "bamum", @@ -41991,11 +43075,13 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inogham", "cher", "chakma", + "inkaktoviknumerals", "emoji", "insiddham", "cherokee", "khar", "inmongolian", + "innagmundari", "incherokeesupplement", "manichaean", "inolchiki", @@ -42039,48 +43125,46 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = #ifdef USE_UNICODE_AGE_PROPERTIES "age=6.0", "age=6.2", + "age=15.0", "age=7.0", "age=6.3", #endif /* USE_UNICODE_AGE_PROPERTIES */ "cwt", #ifdef USE_UNICODE_AGE_PROPERTIES - "age=5.1", + "age=14.0", #endif /* USE_UNICODE_AGE_PROPERTIES */ "unassigned", #ifdef USE_UNICODE_AGE_PROPERTIES + "age=5.1", "age=5.0", "age=5.2", - "age=14.0", #endif /* USE_UNICODE_AGE_PROPERTIES */ "diacritic", - "ahom", #ifdef USE_UNICODE_AGE_PROPERTIES "age=4.1", +#endif /* USE_UNICODE_AGE_PROPERTIES */ + "ahom", +#ifdef USE_UNICODE_AGE_PROPERTIES "age=4.0", #endif /* USE_UNICODE_AGE_PROPERTIES */ "incjkunifiedideographsextensione", - "hani", "khmr", - "han", "insinhala", "inmiscellaneoustechnical", "saur", - "hano", "guru", "sundanese", "punct", "paucinhau", "gurmukhi", - "inkhojki", - "hanunoo", "chorasmian", - "hira", "logicalorderexception", "khmer", "limbu", "chrs", "oriya", "inscriptionalpahlavi", + "incyrillicextendedd", "incjkunifiedideographsextensionc", #endif /* USE_UNICODE_PROPERTIES */ "cntrl", @@ -42152,7 +43236,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "indogra", "arab", "medefaidrin", - "hatran", "inshorthandformatcontrols", "phli", "inimperialaramaic", @@ -42161,7 +43244,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inanatolianhieroglyphs", "punctuation", "graphemeextend", - "hatr", "cwl", "vith", "ingeometricshapes", @@ -42219,7 +43301,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "oidc", "bopo", "cuneiform", - "hex", "caseignorable", "inoldpersian", "cwu", @@ -42238,10 +43319,8 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "oids", "inarabicextendeda", "modifierletter", - "gujr", "incjksymbolsandpunctuation", "olower", - "gujarati", "bopomofo", "inlisu", "inoldpermic", @@ -42255,12 +43334,15 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inbalinese", "sorasompeng", "closepunctuation", + "hani", "inmayannumerals", + "han", "inmiscellaneousmathematicalsymbolsb", "inlepcha", "patsyn", "inlisusupplement", "insyriacsupplement", + "hano", "newa", "spacingmark", "inpalmyrene", @@ -42270,8 +43352,12 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = #ifndef USE_UNICODE_PROPERTIES "lower", #else /* USE_UNICODE_PROPERTIES */ + "inkhojki", "taile", "assigned", + "hanunoo", + "hira", + "inarabicextendedc", "newtailue", "space", "intelugu", @@ -42324,41 +43410,35 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "shaw", "palmyrene", "soyo", + "incjkunifiedideographsextensionh", "sgnw", "toto", "caucasianalbanian", "inmathematicalalphanumericsymbols", "incjkunifiedideographsextensiong", + "hatran", "taiviet", "meroitichieroglyphs", "ingeorgianextended", "incjkunifiedideographsextensionf", "oldpersian", - "mahj", "induployan", "incyrillicextendedb", "dash", - "mahajani", - "hang", + "hatr", "innyiakengpuachuehmong", "incombiningdiacriticalmarks", - "ingujarati", "nl", "incombiningdiacriticalmarksforsymbols", "khudawadi", - "ingunjalagondi", "incjkradicalssupplement", "inglagolitic", "orkh", - "hiragana", "syrc", - "inrejang", "surrogate", - "khoj", "indevanagari", "avestan", "oldpermic", - "hmng", "ethi", "ogam", "rohg", @@ -42366,7 +43446,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "java", "inphagspa", "lepcha", - "inenclosedcjklettersandmonths", + "indevanagariextendeda", "intifinagh", "intagalog", "incombiningdiacriticalmarkssupplement", @@ -42376,6 +43456,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "insymbolsandpictographsextendeda", "syriac", "inbengali", + "nagm", "extendedpictographic", "buhd", "javanese", @@ -42384,6 +43465,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inlatin1supplement", "ingothic", "invariationselectors", + "hex", "inverticalforms", "ebase", "incurrencysymbols", @@ -42397,15 +43479,15 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "invedicextensions", "inlimbu", "olditalic", - "rjng", + "gujr", "mathsymbol", "incjkunifiedideographsextensionb", + "gujarati", "phagspa", "invariationselectorssupplement", "currencysymbol", "inlinearbsyllabary", "wancho", - "hmnp", "inpaucinhau", "other", "otheridcontinue", @@ -42419,7 +43501,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = #ifndef USE_UNICODE_PROPERTIES "blank" #else /* USE_UNICODE_PROPERTIES */ - "hebr", "nonspacingmark", "titlecaseletter", "inmeroiticcursive", @@ -42439,7 +43520,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "noncharactercodepoint", "oldhungarian", "insymbolsforlegacycomputing", - "hangul", "insmallformvariants", "inhangulsyllables", "emojipresentation", @@ -42451,15 +43531,12 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "inpsalterpahlavi", "whitespace", "finalpunctuation", - "hung", "orya", - "hexdigit", "phlp", "inbamumsupplement", "buhid", "paragraphseparator", "inalphabeticpresentationforms", - "hluw", "inlatinextendedg", "elba", "changeswhentitlecased", @@ -42476,30 +43553,39 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "tagbanwa", "tamil", "khitansmallscript", + "mahj", + "mahajani", + "hang", "tirh", "sylotinagri", "talu", + "nagmundari", "deva", + "ingujarati", "deprecated", "inarabicpresentationformsb", "devanagari", + "ingunjalagondi", "graphemeclusterbreak=t", "graphemeclusterbreak=lvt", "taitham", "nbat", "telu", + "hiragana", "nabataean", - "inmahjongtiles", + "inrejang", "intangutsupplement", + "khoj", + "hmng", "cyprominoan", "inhebrew", "inmathematicaloperators", "inarabicsupplement", + "inenclosedcjklettersandmonths", "changeswhenlowercased", "tangut", "elbasan", "osmanya", - "inyijinghexagramsymbols", "insuperscriptsandsubscripts", "graphemeclusterbreak=extend", "graphemeclusterbreak=prepend", @@ -42510,7 +43596,6 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "kayahli", "inplayingcards", "elym", - "injavanese", "graphemeclusterbreak=l", "graphemeclusterbreak=control", "ogrext", @@ -42526,6 +43611,7 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "cypriot", "any", "otheruppercase", + "rjng", "wspace", "inindicsiyaqnumbers", "inprivateusearea", @@ -42533,11 +43619,12 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "oupper", "signwriting", "nushu", - "hanifirohingya", + "hmnp", "upper", "insupplementalarrowsc", "omath", "modifiersymbol", + "hebr", "inhalfwidthandfullwidthforms", "insupplementalmathematicaloperators", "inpahawhhmong", @@ -42546,60 +43633,68 @@ static const struct uniname2ctype_pool_t uniname2ctype_pool_contents = "dupl", "ogham", "dashpunctuation", + "hangul", "inhanguljamoextendedb", "bassavah", "aghb", + "hung", + "hexdigit", "incypriotsyllabary", "indivesakuru", "tibt", "inlatinextendedb", + "hluw", "tibetan", "inyisyllables", "oldnortharabian", "defaultignorablecodepoint", "inhighprivateusesurrogates", - "rejang", "soyombo", "otherdefaultignorablecodepoint", "pahawhhmong", "unifiedideograph", "othermath", "changeswhencasefolded", + "inmahjongtiles", "dep", "divesakuru", "graphemeclusterbreak=lf", "uppercaseletter", "insupplementalpunctuation", "ethiopic", + "inyijinghexagramsymbols", "ecomp", "inglagoliticsupplement", - "hebrew", "inbopomofoextended", - "graphemeclusterbreak=zwj", + "injavanese", "otherpunctuation", "tifinagh", "tfng", + "hanifirohingya", "tavt", "inboxdrawing", "oldsoutharabian", - "hyphen", "inegyptianhieroglyphs", "inegyptianhieroglyphformatcontrols", "tagb", + "rejang", "tglg", "tagalog", "othergraphemeextend", "insupplementaryprivateuseareaa", "inhighsurrogates", + "hebrew", "duployan", "graphemeclusterbreak=v", "graphemeclusterbreak=lv", "insupplementalarrowsb", + "graphemeclusterbreak=zwj", "telugu", "zyyy", "olduyghur", "inhangulcompatibilityjamo", "openpunctuation", + "hyphen", "insupplementalsymbolsandpictographs", "egyp", "nyiakengpuachuehmong", @@ -42635,13 +43730,13 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str48), 95}, {uniname2ctype_offset(str49), 95}, {-1}, {-1}, - {uniname2ctype_offset(str52), 343}, + {uniname2ctype_offset(str52), 346}, {-1}, {-1}, {uniname2ctype_offset(str55), 21}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str64), 44}, {-1}, - {uniname2ctype_offset(str66), 330}, + {uniname2ctype_offset(str66), 333}, {uniname2ctype_offset(str67), 52}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str71), 181}, @@ -42659,9 +43754,9 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str94), 33}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str100), 149}, - {uniname2ctype_offset(str101), 510}, + {uniname2ctype_offset(str101), 513}, {uniname2ctype_offset(str102), 108}, - {uniname2ctype_offset(str103), 261}, + {uniname2ctype_offset(str103), 263}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str107), 31}, {uniname2ctype_offset(str108), 77}, @@ -42680,27 +43775,27 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str130), 42}, {uniname2ctype_offset(str131), 172}, {-1}, {-1}, - {uniname2ctype_offset(str134), 494}, + {uniname2ctype_offset(str134), 497}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str139), 170}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str145), 513}, - {uniname2ctype_offset(str146), 569}, + {uniname2ctype_offset(str145), 516}, + {uniname2ctype_offset(str146), 575}, {-1}, - {uniname2ctype_offset(str148), 574}, - {uniname2ctype_offset(str149), 531}, + {uniname2ctype_offset(str148), 580}, + {uniname2ctype_offset(str149), 535}, {-1}, {uniname2ctype_offset(str151), 18}, {uniname2ctype_offset(str152), 169}, {uniname2ctype_offset(str153), 160}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str158), 276}, + {uniname2ctype_offset(str158), 278}, {-1}, {-1}, - {uniname2ctype_offset(str161), 324}, + {uniname2ctype_offset(str161), 327}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str177), 349}, - {uniname2ctype_offset(str178), 558}, + {uniname2ctype_offset(str177), 352}, + {uniname2ctype_offset(str178), 563}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str183), 75}, {-1}, {-1}, @@ -42709,19 +43804,19 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str190), 208}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str203), 357}, - {uniname2ctype_offset(str204), 485}, + {uniname2ctype_offset(str203), 360}, + {uniname2ctype_offset(str204), 488}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str210), 575}, + {uniname2ctype_offset(str210), 581}, {-1}, - {uniname2ctype_offset(str212), 362}, + {uniname2ctype_offset(str212), 365}, {uniname2ctype_offset(str213), 115}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str218), 545}, + {uniname2ctype_offset(str218), 549}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str226), 171}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str230), 526}, + {uniname2ctype_offset(str230), 530}, {uniname2ctype_offset(str231), 31}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str236), 25}, @@ -42733,64 +43828,64 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {uniname2ctype_offset(str253), 102}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str260), 562}, + {uniname2ctype_offset(str260), 568}, {-1}, {-1}, {uniname2ctype_offset(str263), 161}, {-1}, {uniname2ctype_offset(str265), 19}, {-1}, {uniname2ctype_offset(str267), 79}, - {uniname2ctype_offset(str268), 354}, + {uniname2ctype_offset(str268), 357}, {-1}, - {uniname2ctype_offset(str270), 268}, + {uniname2ctype_offset(str270), 270}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str274), 561}, - {uniname2ctype_offset(str275), 514}, + {uniname2ctype_offset(str274), 567}, + {uniname2ctype_offset(str275), 517}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str281), 318}, + {uniname2ctype_offset(str281), 321}, {uniname2ctype_offset(str282), 40}, {uniname2ctype_offset(str283), 79}, {-1}, - {uniname2ctype_offset(str285), 533}, + {uniname2ctype_offset(str285), 537}, {-1}, {uniname2ctype_offset(str287), 144}, {uniname2ctype_offset(str288), 144}, - {uniname2ctype_offset(str289), 555}, + {uniname2ctype_offset(str289), 560}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str293), 218}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str297), 212}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str301), 392}, + {uniname2ctype_offset(str301), 395}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str311), 322}, + {uniname2ctype_offset(str311), 325}, {-1}, - {uniname2ctype_offset(str313), 453}, + {uniname2ctype_offset(str313), 456}, {-1}, - {uniname2ctype_offset(str315), 241}, + {uniname2ctype_offset(str315), 243}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str320), 269}, + {uniname2ctype_offset(str320), 271}, {-1}, {uniname2ctype_offset(str322), 129}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str328), 331}, + {uniname2ctype_offset(str328), 334}, {-1}, {-1}, {uniname2ctype_offset(str331), 76}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str335), 556}, + {uniname2ctype_offset(str335), 561}, {-1}, {-1}, - {uniname2ctype_offset(str338), 329}, + {uniname2ctype_offset(str338), 332}, {-1}, {uniname2ctype_offset(str340), 76}, {-1}, - {uniname2ctype_offset(str342), 346}, + {uniname2ctype_offset(str342), 349}, {-1}, {-1}, {uniname2ctype_offset(str345), 53}, - {uniname2ctype_offset(str346), 268}, + {uniname2ctype_offset(str346), 270}, {-1}, - {uniname2ctype_offset(str348), 423}, + {uniname2ctype_offset(str348), 426}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str352), 529}, + {uniname2ctype_offset(str352), 533}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str362), 163}, {-1}, {-1}, {-1}, @@ -42798,14 +43893,14 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str373), 160}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str381), 550}, + {uniname2ctype_offset(str381), 554}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str386), 368}, + {uniname2ctype_offset(str386), 371}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str399), 327}, + {uniname2ctype_offset(str399), 330}, {-1}, - {uniname2ctype_offset(str401), 544}, + {uniname2ctype_offset(str401), 548}, {-1}, {-1}, {uniname2ctype_offset(str404), 81}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -42828,176 +43923,179 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {uniname2ctype_offset(str470), 22}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str475), 521}, + {uniname2ctype_offset(str475), 524}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str480), 454}, + {uniname2ctype_offset(str480), 457}, {uniname2ctype_offset(str481), 188}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str486), 473}, + {uniname2ctype_offset(str486), 476}, {-1}, - {uniname2ctype_offset(str488), 582}, + {uniname2ctype_offset(str488), 588}, {-1}, {-1}, - {uniname2ctype_offset(str491), 467}, + {uniname2ctype_offset(str491), 470}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str500), 127}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str504), 187}, - {uniname2ctype_offset(str505), 247}, + {uniname2ctype_offset(str505), 249}, {uniname2ctype_offset(str506), 24}, {-1}, {-1}, {uniname2ctype_offset(str509), 24}, {-1}, - {uniname2ctype_offset(str511), 460}, + {uniname2ctype_offset(str511), 463}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str520), 420}, + {uniname2ctype_offset(str520), 423}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str533), 230}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str538), 91}, {-1}, {-1}, - {uniname2ctype_offset(str541), 549}, + {uniname2ctype_offset(str541), 553}, {-1}, {uniname2ctype_offset(str543), 91}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str556), 542}, + {uniname2ctype_offset(str556), 546}, {-1}, - {uniname2ctype_offset(str558), 347}, + {uniname2ctype_offset(str558), 350}, {uniname2ctype_offset(str559), 70}, - {uniname2ctype_offset(str560), 512}, + {uniname2ctype_offset(str560), 515}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str565), 615}, + {uniname2ctype_offset(str565), 624}, {uniname2ctype_offset(str566), 37}, {-1}, {uniname2ctype_offset(str568), 113}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str572), 499}, + {uniname2ctype_offset(str572), 502}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str577), 602}, + {uniname2ctype_offset(str577), 611}, {-1}, {uniname2ctype_offset(str579), 106}, {-1}, {-1}, - {uniname2ctype_offset(str582), 403}, - {uniname2ctype_offset(str583), 477}, + {uniname2ctype_offset(str582), 406}, + {uniname2ctype_offset(str583), 480}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str590), 74}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str594), 168}, {-1}, - {uniname2ctype_offset(str596), 613}, + {uniname2ctype_offset(str596), 622}, {uniname2ctype_offset(str597), 146}, {-1}, {-1}, - {uniname2ctype_offset(str600), 487}, + {uniname2ctype_offset(str600), 490}, {-1}, {uniname2ctype_offset(str602), 70}, {-1}, - {uniname2ctype_offset(str604), 573}, - {uniname2ctype_offset(str605), 620}, + {uniname2ctype_offset(str604), 579}, + {uniname2ctype_offset(str605), 629}, {-1}, {-1}, - {uniname2ctype_offset(str608), 628}, + {uniname2ctype_offset(str608), 637}, {uniname2ctype_offset(str609), 229}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str614), 603}, + {uniname2ctype_offset(str614), 612}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str624), 195}, - {uniname2ctype_offset(str625), 444}, + {uniname2ctype_offset(str625), 447}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str630), 29}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str636), 539}, + {uniname2ctype_offset(str636), 543}, {-1}, {-1}, {uniname2ctype_offset(str639), 49}, {-1}, {-1}, {uniname2ctype_offset(str642), 19}, - {-1}, {-1}, - {uniname2ctype_offset(str645), 482}, + {uniname2ctype_offset(str643), 564}, + {-1}, + {uniname2ctype_offset(str645), 485}, {-1}, {uniname2ctype_offset(str647), 192}, {-1}, {-1}, - {uniname2ctype_offset(str650), 484}, + {uniname2ctype_offset(str650), 487}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str657), 51}, {-1}, {-1}, - {uniname2ctype_offset(str660), 266}, + {uniname2ctype_offset(str660), 268}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str672), 332}, + {uniname2ctype_offset(str672), 335}, {-1}, {-1}, {uniname2ctype_offset(str675), 68}, {-1}, {-1}, {uniname2ctype_offset(str678), 171}, - {uniname2ctype_offset(str679), 599}, + {uniname2ctype_offset(str679), 607}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str683), 265}, + {uniname2ctype_offset(str683), 267}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str692), 69}, {-1}, {-1}, - {uniname2ctype_offset(str695), 547}, + {uniname2ctype_offset(str695), 551}, {uniname2ctype_offset(str696), 175}, - {uniname2ctype_offset(str697), 396}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str703), 523}, + {uniname2ctype_offset(str697), 399}, + {-1}, {-1}, + {uniname2ctype_offset(str700), 236}, + {-1}, {-1}, + {uniname2ctype_offset(str703), 527}, {-1}, - {uniname2ctype_offset(str705), 344}, + {uniname2ctype_offset(str705), 347}, {-1}, {uniname2ctype_offset(str707), 158}, - {uniname2ctype_offset(str708), 581}, + {uniname2ctype_offset(str708), 587}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str713), 373}, + {uniname2ctype_offset(str713), 376}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str720), 72}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str737), 7}, - {uniname2ctype_offset(str738), 370}, + {uniname2ctype_offset(str738), 373}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str742), 6}, {-1}, {-1}, - {uniname2ctype_offset(str745), 267}, + {uniname2ctype_offset(str745), 269}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str753), 238}, + {uniname2ctype_offset(str753), 240}, {-1}, - {uniname2ctype_offset(str755), 511}, + {uniname2ctype_offset(str755), 514}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str760), 428}, + {uniname2ctype_offset(str760), 431}, {uniname2ctype_offset(str761), 167}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str766), 156}, - {uniname2ctype_offset(str767), 600}, + {uniname2ctype_offset(str767), 608}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str771), 167}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str776), 266}, + {uniname2ctype_offset(str776), 268}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str783), 156}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str787), 254}, + {uniname2ctype_offset(str787), 256}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str791), 193}, {-1}, {-1}, - {uniname2ctype_offset(str794), 583}, + {uniname2ctype_offset(str794), 589}, {-1}, {-1}, {uniname2ctype_offset(str797), 50}, {-1}, - {uniname2ctype_offset(str799), 608}, + {uniname2ctype_offset(str799), 617}, {-1}, {-1}, {uniname2ctype_offset(str802), 13}, - {uniname2ctype_offset(str803), 587}, + {uniname2ctype_offset(str803), 593}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str809), 443}, + {uniname2ctype_offset(str809), 446}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str814), 490}, + {uniname2ctype_offset(str814), 493}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str818), 395}, + {uniname2ctype_offset(str818), 398}, {-1}, {-1}, - {uniname2ctype_offset(str821), 479}, - {uniname2ctype_offset(str822), 589}, + {uniname2ctype_offset(str821), 482}, + {uniname2ctype_offset(str822), 595}, {uniname2ctype_offset(str823), 47}, {uniname2ctype_offset(str824), 112}, - {uniname2ctype_offset(str825), 441}, + {uniname2ctype_offset(str825), 444}, {-1}, {-1}, - {uniname2ctype_offset(str828), 590}, + {uniname2ctype_offset(str828), 596}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str838), 157}, {-1}, {-1}, {-1}, @@ -43010,32 +44108,32 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str855), 67}, {-1}, - {uniname2ctype_offset(str857), 316}, + {uniname2ctype_offset(str857), 319}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str875), 366}, + {uniname2ctype_offset(str875), 369}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str886), 401}, + {uniname2ctype_offset(str886), 404}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str895), 409}, + {uniname2ctype_offset(str895), 412}, {-1}, {-1}, - {uniname2ctype_offset(str898), 497}, + {uniname2ctype_offset(str898), 500}, {-1}, - {uniname2ctype_offset(str900), 612}, + {uniname2ctype_offset(str900), 621}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str906), 518}, - {uniname2ctype_offset(str907), 446}, + {uniname2ctype_offset(str906), 521}, + {uniname2ctype_offset(str907), 449}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str920), 416}, + {uniname2ctype_offset(str920), 419}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str924), 68}, - {uniname2ctype_offset(str925), 592}, - {uniname2ctype_offset(str926), 341}, + {uniname2ctype_offset(str925), 599}, + {uniname2ctype_offset(str926), 344}, {-1}, - {uniname2ctype_offset(str928), 536}, - {uniname2ctype_offset(str929), 458}, + {uniname2ctype_offset(str928), 540}, + {uniname2ctype_offset(str929), 461}, {uniname2ctype_offset(str930), 41}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -43044,13 +44142,13 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str961), 2}, {-1}, - {uniname2ctype_offset(str963), 255}, + {uniname2ctype_offset(str963), 257}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str974), 507}, + {uniname2ctype_offset(str974), 510}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str986), 367}, + {uniname2ctype_offset(str986), 370}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str994), 85}, {uniname2ctype_offset(str995), 104}, @@ -43058,21 +44156,21 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1010), 26}, {-1}, {-1}, - {uniname2ctype_offset(str1013), 492}, + {uniname2ctype_offset(str1013), 495}, {-1}, - {uniname2ctype_offset(str1015), 481}, + {uniname2ctype_offset(str1015), 484}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1021), 67}, {-1}, {-1}, {uniname2ctype_offset(str1024), 53}, - {uniname2ctype_offset(str1025), 456}, + {uniname2ctype_offset(str1025), 459}, {-1}, {-1}, {uniname2ctype_offset(str1028), 136}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1032), 377}, + {uniname2ctype_offset(str1032), 380}, {-1}, {-1}, - {uniname2ctype_offset(str1035), 319}, - {uniname2ctype_offset(str1036), 563}, + {uniname2ctype_offset(str1035), 322}, + {uniname2ctype_offset(str1036), 569}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1049), 173}, @@ -43082,26 +44180,26 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1072), 197}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1081), 411}, + {uniname2ctype_offset(str1081), 414}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1085), 257}, + {uniname2ctype_offset(str1085), 259}, {-1}, - {uniname2ctype_offset(str1087), 593}, + {uniname2ctype_offset(str1087), 600}, {-1}, {uniname2ctype_offset(str1089), 115}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1095), 248}, + {uniname2ctype_offset(str1095), 250}, {uniname2ctype_offset(str1096), 71}, - {uniname2ctype_offset(str1097), 537}, + {uniname2ctype_offset(str1097), 541}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1102), 520}, + {uniname2ctype_offset(str1102), 523}, {-1}, {uniname2ctype_offset(str1104), 228}, {uniname2ctype_offset(str1105), 217}, {-1}, - {uniname2ctype_offset(str1107), 538}, + {uniname2ctype_offset(str1107), 542}, {-1}, - {uniname2ctype_offset(str1109), 237}, + {uniname2ctype_offset(str1109), 239}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1114), 69}, {uniname2ctype_offset(str1115), 11}, @@ -43110,7 +44208,7 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str1120), 60}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1134), 422}, + {uniname2ctype_offset(str1134), 425}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1140), 93}, {-1}, @@ -43118,153 +44216,155 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {uniname2ctype_offset(str1145), 137}, {uniname2ctype_offset(str1146), 131}, - {uniname2ctype_offset(str1147), 264}, + {uniname2ctype_offset(str1147), 266}, {-1}, {uniname2ctype_offset(str1149), 158}, {uniname2ctype_offset(str1150), 98}, - {uniname2ctype_offset(str1151), 495}, + {uniname2ctype_offset(str1151), 498}, {uniname2ctype_offset(str1152), 217}, {uniname2ctype_offset(str1153), 138}, {-1}, {-1}, - {uniname2ctype_offset(str1156), 525}, + {uniname2ctype_offset(str1156), 529}, {uniname2ctype_offset(str1157), 203}, {uniname2ctype_offset(str1158), 166}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1164), 238}, + {uniname2ctype_offset(str1164), 240}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1168), 104}, {-1}, - {uniname2ctype_offset(str1170), 386}, - {uniname2ctype_offset(str1171), 532}, + {uniname2ctype_offset(str1170), 389}, + {uniname2ctype_offset(str1171), 536}, {-1}, {-1}, - {uniname2ctype_offset(str1174), 323}, + {uniname2ctype_offset(str1174), 326}, {uniname2ctype_offset(str1175), 26}, {uniname2ctype_offset(str1176), 208}, {uniname2ctype_offset(str1177), 74}, - {uniname2ctype_offset(str1178), 350}, + {uniname2ctype_offset(str1178), 353}, {-1}, {uniname2ctype_offset(str1180), 183}, {uniname2ctype_offset(str1181), 151}, - {uniname2ctype_offset(str1182), 356}, + {uniname2ctype_offset(str1182), 359}, {uniname2ctype_offset(str1183), 101}, {-1}, {uniname2ctype_offset(str1185), 170}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1190), 270}, - {uniname2ctype_offset(str1191), 541}, + {uniname2ctype_offset(str1186), 597}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str1190), 272}, + {uniname2ctype_offset(str1191), 545}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1197), 101}, {uniname2ctype_offset(str1198), 135}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1203), 363}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1207), 459}, + {uniname2ctype_offset(str1203), 366}, + {uniname2ctype_offset(str1204), 609}, + {-1}, {-1}, + {uniname2ctype_offset(str1207), 462}, {-1}, {uniname2ctype_offset(str1209), 186}, {-1}, {-1}, - {uniname2ctype_offset(str1212), 376}, + {uniname2ctype_offset(str1212), 379}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1223), 578}, + {uniname2ctype_offset(str1223), 584}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1227), 241}, + {uniname2ctype_offset(str1227), 243}, {-1}, {uniname2ctype_offset(str1229), 235}, - {uniname2ctype_offset(str1230), 265}, + {uniname2ctype_offset(str1230), 267}, {uniname2ctype_offset(str1231), 206}, - {uniname2ctype_offset(str1232), 352}, + {uniname2ctype_offset(str1232), 355}, {uniname2ctype_offset(str1233), 73}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1234), 296}, - {uniname2ctype_offset(str1235), 298}, - {uniname2ctype_offset(str1236), 295}, - {uniname2ctype_offset(str1237), 297}, + {uniname2ctype_offset(str1234), 298}, + {uniname2ctype_offset(str1235), 300}, + {uniname2ctype_offset(str1236), 297}, + {uniname2ctype_offset(str1237), 299}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1241), 299}, - {uniname2ctype_offset(str1242), 277}, + {uniname2ctype_offset(str1241), 301}, + {uniname2ctype_offset(str1242), 279}, #endif /* USE_UNICODE_AGE_PROPERTIES */ {uniname2ctype_offset(str1243), 25}, - {uniname2ctype_offset(str1244), 338}, + {uniname2ctype_offset(str1244), 341}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1245), 279}, + {uniname2ctype_offset(str1245), 281}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1246), 432}, + {uniname2ctype_offset(str1246), 435}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1247), 278}, + {uniname2ctype_offset(str1247), 280}, #endif /* USE_UNICODE_AGE_PROPERTIES */ {uniname2ctype_offset(str1248), 30}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, {-1}, {-1}, {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1249), 281}, - {uniname2ctype_offset(str1250), 294}, - {uniname2ctype_offset(str1251), 280}, - {uniname2ctype_offset(str1252), 282}, - {uniname2ctype_offset(str1253), 293}, + {uniname2ctype_offset(str1249), 283}, + {uniname2ctype_offset(str1250), 296}, + {uniname2ctype_offset(str1251), 282}, + {uniname2ctype_offset(str1252), 284}, + {uniname2ctype_offset(str1253), 295}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1254), 560}, + {uniname2ctype_offset(str1254), 566}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1255), 289}, + {uniname2ctype_offset(str1255), 291}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1256), 276}, + {uniname2ctype_offset(str1256), 278}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1263), 64}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1257), 288}, - {uniname2ctype_offset(str1258), 290}, - {uniname2ctype_offset(str1259), 292}, + {uniname2ctype_offset(str1257), 290}, + {uniname2ctype_offset(str1258), 292}, + {uniname2ctype_offset(str1259), 303}, + {uniname2ctype_offset(str1260), 294}, + {-1}, + {uniname2ctype_offset(str1262), 293}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {-1}, {-1}, -#ifdef USE_UNICODE_AGE_PROPERTIES - {uniname2ctype_offset(str1262), 291}, {uniname2ctype_offset(str1263), 64}, +#ifndef USE_UNICODE_AGE_PROPERTIES + {-1}, {-1}, +#else /* USE_UNICODE_AGE_PROPERTIES */ {-1}, - {uniname2ctype_offset(str1265), 286}, + {uniname2ctype_offset(str1265), 302}, #endif /* USE_UNICODE_AGE_PROPERTIES */ {uniname2ctype_offset(str1266), 21}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, {-1}, {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1267), 285}, - {uniname2ctype_offset(str1268), 287}, - {uniname2ctype_offset(str1269), 300}, + {uniname2ctype_offset(str1267), 288}, {-1}, + {uniname2ctype_offset(str1269), 287}, + {uniname2ctype_offset(str1270), 289}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1271), 248}, + {uniname2ctype_offset(str1271), 250}, +#ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, +#else /* USE_UNICODE_AGE_PROPERTIES */ + {-1}, + {uniname2ctype_offset(str1273), 286}, +#endif /* USE_UNICODE_AGE_PROPERTIES */ {uniname2ctype_offset(str1274), 200}, #ifndef USE_UNICODE_AGE_PROPERTIES {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #else /* USE_UNICODE_AGE_PROPERTIES */ - {-1}, {-1}, - {uniname2ctype_offset(str1277), 284}, - {-1}, - {uniname2ctype_offset(str1279), 283}, - {-1}, {-1}, + {uniname2ctype_offset(str1275), 285}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #endif /* USE_UNICODE_AGE_PROPERTIES */ - {uniname2ctype_offset(str1282), 626}, - {-1}, - {uniname2ctype_offset(str1284), 110}, + {uniname2ctype_offset(str1282), 635}, + {-1}, {-1}, {uniname2ctype_offset(str1285), 105}, - {-1}, - {uniname2ctype_offset(str1287), 110}, - {-1}, - {uniname2ctype_offset(str1289), 345}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str1289), 348}, {-1}, {-1}, - {uniname2ctype_offset(str1292), 394}, + {uniname2ctype_offset(str1292), 397}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1297), 145}, - {uniname2ctype_offset(str1298), 117}, - {-1}, + {-1}, {-1}, {uniname2ctype_offset(str1300), 86}, {uniname2ctype_offset(str1301), 141}, {-1}, {-1}, {-1}, {-1}, @@ -43273,14 +44373,11 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str1314), 193}, {-1}, {-1}, {uniname2ctype_offset(str1317), 86}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1323), 535}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1327), 117}, - {uniname2ctype_offset(str1328), 227}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1330), 107}, - {uniname2ctype_offset(str1331), 261}, + {uniname2ctype_offset(str1328), 227}, + {-1}, {-1}, + {uniname2ctype_offset(str1331), 263}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1340), 105}, {-1}, {-1}, @@ -43291,18 +44388,20 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str1352), 88}, {-1}, {uniname2ctype_offset(str1354), 164}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1358), 624}, + {-1}, + {uniname2ctype_offset(str1356), 605}, + {-1}, + {uniname2ctype_offset(str1358), 633}, {-1}, {uniname2ctype_offset(str1360), 3}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1365), 384}, + {uniname2ctype_offset(str1365), 387}, {-1}, - {uniname2ctype_offset(str1367), 530}, + {uniname2ctype_offset(str1367), 534}, {-1}, - {uniname2ctype_offset(str1369), 256}, + {uniname2ctype_offset(str1369), 258}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1373), 272}, + {uniname2ctype_offset(str1373), 274}, {-1}, {uniname2ctype_offset(str1375), 135}, {-1}, {-1}, {-1}, {-1}, @@ -43315,29 +44414,29 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str1392), 138}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1400), 201}, - {uniname2ctype_offset(str1401), 397}, + {uniname2ctype_offset(str1401), 400}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1407), 224}, {-1}, {uniname2ctype_offset(str1409), 38}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1415), 570}, + {uniname2ctype_offset(str1415), 576}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1419), 140}, {uniname2ctype_offset(str1420), 140}, {-1}, - {uniname2ctype_offset(str1422), 321}, + {uniname2ctype_offset(str1422), 324}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1426), 39}, {-1}, {uniname2ctype_offset(str1428), 181}, {uniname2ctype_offset(str1429), 36}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1438), 434}, + {uniname2ctype_offset(str1438), 437}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1442), 540}, + {uniname2ctype_offset(str1442), 544}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1448), 505}, + {uniname2ctype_offset(str1448), 508}, {uniname2ctype_offset(str1449), 122}, {-1}, {uniname2ctype_offset(str1451), 203}, @@ -43348,109 +44447,106 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {uniname2ctype_offset(str1460), 215}, {-1}, - {uniname2ctype_offset(str1462), 554}, + {uniname2ctype_offset(str1462), 559}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1470), 504}, - {uniname2ctype_offset(str1471), 506}, + {uniname2ctype_offset(str1470), 507}, + {uniname2ctype_offset(str1471), 509}, {-1}, {-1}, {uniname2ctype_offset(str1474), 134}, - {uniname2ctype_offset(str1475), 426}, - {uniname2ctype_offset(str1476), 508}, + {uniname2ctype_offset(str1475), 429}, + {uniname2ctype_offset(str1476), 511}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1480), 245}, + {uniname2ctype_offset(str1480), 247}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1489), 33}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1495), 260}, + {uniname2ctype_offset(str1495), 262}, {-1}, - {uniname2ctype_offset(str1497), 496}, + {uniname2ctype_offset(str1497), 499}, {-1}, - {uniname2ctype_offset(str1499), 611}, + {uniname2ctype_offset(str1499), 620}, {-1}, {uniname2ctype_offset(str1501), 196}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1507), 122}, {uniname2ctype_offset(str1508), 231}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1517), 610}, + {uniname2ctype_offset(str1517), 619}, {-1}, {-1}, - {uniname2ctype_offset(str1520), 237}, + {uniname2ctype_offset(str1520), 239}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1524), 483}, + {uniname2ctype_offset(str1524), 486}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1529), 120}, {-1}, - {uniname2ctype_offset(str1531), 419}, + {uniname2ctype_offset(str1531), 422}, {-1}, {uniname2ctype_offset(str1533), 142}, {-1}, {-1}, {uniname2ctype_offset(str1536), 127}, - {uniname2ctype_offset(str1537), 269}, + {uniname2ctype_offset(str1537), 271}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1542), 465}, + {uniname2ctype_offset(str1542), 468}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1547), 168}, {-1}, - {uniname2ctype_offset(str1549), 519}, + {uniname2ctype_offset(str1549), 522}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1557), 85}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1563), 273}, + {uniname2ctype_offset(str1563), 275}, {-1}, - {uniname2ctype_offset(str1565), 326}, + {uniname2ctype_offset(str1565), 329}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1570), 210}, {-1}, {uniname2ctype_offset(str1572), 115}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1577), 564}, + {uniname2ctype_offset(str1577), 570}, {-1}, {-1}, {uniname2ctype_offset(str1580), 131}, {-1}, {uniname2ctype_offset(str1582), 219}, {uniname2ctype_offset(str1583), 125}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1587), 546}, + {uniname2ctype_offset(str1587), 550}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1597), 81}, {uniname2ctype_offset(str1598), 219}, - {-1}, {-1}, - {uniname2ctype_offset(str1601), 202}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1607), 586}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str1607), 592}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1613), 164}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1617), 502}, - {uniname2ctype_offset(str1618), 272}, + {uniname2ctype_offset(str1617), 505}, + {uniname2ctype_offset(str1618), 274}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1622), 385}, - {uniname2ctype_offset(str1623), 567}, + {uniname2ctype_offset(str1622), 388}, + {uniname2ctype_offset(str1623), 573}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1629), 39}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1635), 72}, - {uniname2ctype_offset(str1636), 202}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1643), 62}, {uniname2ctype_offset(str1644), 235}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1654), 400}, - {uniname2ctype_offset(str1655), 274}, + {uniname2ctype_offset(str1654), 403}, + {uniname2ctype_offset(str1655), 276}, {-1}, {uniname2ctype_offset(str1657), 114}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1662), 129}, {-1}, - {uniname2ctype_offset(str1664), 448}, + {uniname2ctype_offset(str1664), 451}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1671), 340}, + {uniname2ctype_offset(str1671), 343}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1675), 469}, + {uniname2ctype_offset(str1675), 472}, {-1}, - {uniname2ctype_offset(str1677), 314}, + {uniname2ctype_offset(str1677), 317}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1682), 625}, + {uniname2ctype_offset(str1682), 634}, {-1}, {uniname2ctype_offset(str1684), 199}, {-1}, @@ -43459,186 +44555,192 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {uniname2ctype_offset(str1691), 124}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1699), 379}, + {uniname2ctype_offset(str1699), 382}, {-1}, - {uniname2ctype_offset(str1701), 522}, + {uniname2ctype_offset(str1701), 525}, {-1}, {-1}, {uniname2ctype_offset(str1704), 207}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1714), 207}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1722), 359}, + {uniname2ctype_offset(str1722), 362}, {-1}, - {uniname2ctype_offset(str1724), 576}, + {uniname2ctype_offset(str1724), 582}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1728), 221}, - {uniname2ctype_offset(str1729), 442}, + {uniname2ctype_offset(str1729), 445}, {uniname2ctype_offset(str1730), 222}, - {uniname2ctype_offset(str1731), 534}, - {uniname2ctype_offset(str1732), 247}, + {uniname2ctype_offset(str1731), 538}, + {uniname2ctype_offset(str1732), 249}, {uniname2ctype_offset(str1733), 123}, {uniname2ctype_offset(str1734), 114}, - {uniname2ctype_offset(str1735), 258}, + {uniname2ctype_offset(str1735), 260}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1740), 129}, {-1}, {uniname2ctype_offset(str1742), 161}, {-1}, {-1}, - {uniname2ctype_offset(str1745), 524}, - {uniname2ctype_offset(str1746), 402}, + {uniname2ctype_offset(str1745), 528}, + {uniname2ctype_offset(str1746), 405}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1750), 20}, {-1}, - {uniname2ctype_offset(str1752), 516}, + {uniname2ctype_offset(str1752), 519}, {uniname2ctype_offset(str1753), 148}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1757), 515}, + {uniname2ctype_offset(str1757), 518}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1766), 73}, {-1}, {uniname2ctype_offset(str1768), 148}, - {uniname2ctype_offset(str1769), 374}, + {uniname2ctype_offset(str1769), 377}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1776), 126}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1781), 552}, + {uniname2ctype_offset(str1781), 556}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1788), 97}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1793), 97}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1806), 348}, + {uniname2ctype_offset(str1806), 351}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1810), 630}, - {uniname2ctype_offset(str1811), 245}, + {uniname2ctype_offset(str1810), 640}, + {uniname2ctype_offset(str1811), 247}, {-1}, - {uniname2ctype_offset(str1813), 264}, + {uniname2ctype_offset(str1813), 266}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1821), 224}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1828), 399}, + {uniname2ctype_offset(str1828), 402}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1838), 614}, + {uniname2ctype_offset(str1838), 623}, {-1}, {-1}, - {uniname2ctype_offset(str1841), 457}, - {uniname2ctype_offset(str1842), 391}, + {uniname2ctype_offset(str1841), 460}, + {uniname2ctype_offset(str1842), 394}, {uniname2ctype_offset(str1843), 65}, {-1}, - {uniname2ctype_offset(str1845), 263}, + {uniname2ctype_offset(str1845), 265}, {-1}, {-1}, {uniname2ctype_offset(str1848), 109}, {-1}, {-1}, {uniname2ctype_offset(str1851), 137}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1857), 244}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1866), 61}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1871), 491}, + {uniname2ctype_offset(str1871), 494}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1881), 63}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1888), 527}, - {uniname2ctype_offset(str1889), 551}, + {uniname2ctype_offset(str1888), 531}, + {uniname2ctype_offset(str1889), 555}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1896), 617}, + {uniname2ctype_offset(str1896), 626}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1902), 431}, + {uniname2ctype_offset(str1902), 434}, {-1}, - {uniname2ctype_offset(str1904), 452}, - {uniname2ctype_offset(str1905), 584}, + {uniname2ctype_offset(str1904), 455}, + {uniname2ctype_offset(str1905), 590}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1912), 580}, + {uniname2ctype_offset(str1912), 586}, {uniname2ctype_offset(str1913), 143}, {-1}, {-1}, - {uniname2ctype_offset(str1916), 588}, + {uniname2ctype_offset(str1916), 594}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1924), 143}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1929), 429}, + {uniname2ctype_offset(str1929), 432}, {-1}, {-1}, - {uniname2ctype_offset(str1932), 412}, + {uniname2ctype_offset(str1932), 415}, {-1}, {-1}, - {uniname2ctype_offset(str1935), 262}, - {uniname2ctype_offset(str1936), 335}, + {uniname2ctype_offset(str1935), 264}, + {uniname2ctype_offset(str1936), 338}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1941), 27}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1948), 87}, - {-1}, - {uniname2ctype_offset(str1950), 421}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str1950), 424}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1956), 250}, - {uniname2ctype_offset(str1957), 87}, + {uniname2ctype_offset(str1956), 252}, + {-1}, {uniname2ctype_offset(str1958), 109}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1964), 437}, + {uniname2ctype_offset(str1964), 440}, {-1}, {-1}, - {uniname2ctype_offset(str1967), 489}, - {uniname2ctype_offset(str1968), 634}, - {uniname2ctype_offset(str1969), 249}, + {uniname2ctype_offset(str1967), 492}, + {uniname2ctype_offset(str1968), 644}, + {uniname2ctype_offset(str1969), 251}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1974), 355}, + {uniname2ctype_offset(str1974), 358}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str1981), 176}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str1985), 405}, + {uniname2ctype_offset(str1985), 408}, {-1}, {-1}, - {uniname2ctype_offset(str1988), 438}, + {uniname2ctype_offset(str1988), 441}, {-1}, {-1}, {uniname2ctype_offset(str1991), 1}, {-1}, - {uniname2ctype_offset(str1993), 372}, + {uniname2ctype_offset(str1993), 375}, {uniname2ctype_offset(str1994), 175}, {-1}, {uniname2ctype_offset(str1996), 42}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2002), 591}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2006), 407}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2010), 375}, - {uniname2ctype_offset(str2011), 267}, - {uniname2ctype_offset(str2012), 559}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2001), 110}, + {uniname2ctype_offset(str2002), 598}, {-1}, - {uniname2ctype_offset(str2014), 333}, + {uniname2ctype_offset(str2004), 110}, + {-1}, + {uniname2ctype_offset(str2006), 410}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2010), 378}, + {uniname2ctype_offset(str2011), 269}, + {uniname2ctype_offset(str2012), 565}, {-1}, + {uniname2ctype_offset(str2014), 336}, + {uniname2ctype_offset(str2015), 117}, {uniname2ctype_offset(str2016), 209}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2023), 32}, - {uniname2ctype_offset(str2024), 503}, + {uniname2ctype_offset(str2024), 506}, {-1}, {uniname2ctype_offset(str2026), 176}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2033), 8}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2040), 539}, + {-1}, {uniname2ctype_offset(str2042), 121}, {uniname2ctype_offset(str2043), 17}, + {uniname2ctype_offset(str2044), 117}, + {-1}, {-1}, + {uniname2ctype_offset(str2047), 107}, + {uniname2ctype_offset(str2048), 526}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2062), 130}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2070), 9}, {-1}, {-1}, - {uniname2ctype_offset(str2073), 342}, + {uniname2ctype_offset(str2073), 345}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2077), 150}, - {uniname2ctype_offset(str2078), 254}, + {uniname2ctype_offset(str2078), 256}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2090), 275}, + {uniname2ctype_offset(str2090), 277}, {-1}, {uniname2ctype_offset(str2092), 150}, {-1}, {-1}, - {uniname2ctype_offset(str2095), 353}, + {uniname2ctype_offset(str2095), 356}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2103), 162}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2110), 123}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2114), 436}, - {uniname2ctype_offset(str2115), 381}, + {uniname2ctype_offset(str2114), 439}, + {uniname2ctype_offset(str2115), 384}, {-1}, {uniname2ctype_offset(str2117), 174}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -43649,15 +44751,15 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {uniname2ctype_offset(str2137), 12}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2141), 242}, + {uniname2ctype_offset(str2141), 244}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2145), 52}, - {uniname2ctype_offset(str2146), 413}, + {uniname2ctype_offset(str2146), 416}, {-1}, {-1}, {uniname2ctype_offset(str2149), 221}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2163), 249}, + {uniname2ctype_offset(str2163), 251}, {-1}, {uniname2ctype_offset(str2165), 174}, {uniname2ctype_offset(str2166), 5}, @@ -43670,107 +44772,100 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str2182), 216}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2195), 351}, - {uniname2ctype_offset(str2196), 493}, + {uniname2ctype_offset(str2195), 354}, + {uniname2ctype_offset(str2196), 496}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2207), 196}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2211), 20}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2216), 364}, + {uniname2ctype_offset(str2216), 367}, {uniname2ctype_offset(str2217), 128}, - {uniname2ctype_offset(str2218), 543}, + {uniname2ctype_offset(str2218), 547}, {uniname2ctype_offset(str2219), 78}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2224), 450}, + {uniname2ctype_offset(str2224), 453}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2228), 43}, {uniname2ctype_offset(str2229), 35}, {-1}, {-1}, - {uniname2ctype_offset(str2232), 382}, + {uniname2ctype_offset(str2232), 385}, {uniname2ctype_offset(str2233), 216}, {uniname2ctype_offset(str2234), 92}, - {uniname2ctype_offset(str2235), 486}, + {uniname2ctype_offset(str2235), 489}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2241), 92}, {-1}, {-1}, - {uniname2ctype_offset(str2244), 440}, + {uniname2ctype_offset(str2244), 443}, {-1}, {-1}, {uniname2ctype_offset(str2247), 36}, - {uniname2ctype_offset(str2248), 595}, + {uniname2ctype_offset(str2248), 602}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2276), 415}, + {uniname2ctype_offset(str2276), 418}, {-1}, {uniname2ctype_offset(str2278), 124}, {uniname2ctype_offset(str2279), 192}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2283), 214}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2296), 639}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2305), 205}, {-1}, {-1}, {uniname2ctype_offset(str2308), 234}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2312), 177}, {-1}, {-1}, - {uniname2ctype_offset(str2315), 594}, - {uniname2ctype_offset(str2316), 629}, - {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2315), 601}, + {uniname2ctype_offset(str2316), 638}, + {-1}, + {uniname2ctype_offset(str2318), 202}, + {-1}, {-1}, {uniname2ctype_offset(str2321), 153}, {-1}, {uniname2ctype_offset(str2323), 172}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2327), 378}, + {uniname2ctype_offset(str2327), 381}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2331), 627}, + {uniname2ctype_offset(str2331), 636}, {-1}, {uniname2ctype_offset(str2333), 134}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2341), 185}, - {-1}, - {uniname2ctype_offset(str2343), 585}, - {uniname2ctype_offset(str2344), 439}, - {uniname2ctype_offset(str2345), 239}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2350), 185}, - {uniname2ctype_offset(str2351), 99}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2361), 598}, - {-1}, {-1}, - {uniname2ctype_offset(str2364), 320}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2370), 339}, + {uniname2ctype_offset(str2343), 591}, + {uniname2ctype_offset(str2344), 442}, + {uniname2ctype_offset(str2345), 241}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2353), 202}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2361), 606}, {-1}, {-1}, + {uniname2ctype_offset(str2364), 323}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2373), 37}, - {uniname2ctype_offset(str2374), 389}, + {uniname2ctype_offset(str2374), 392}, {uniname2ctype_offset(str2375), 197}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2389), 557}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2397), 418}, - {uniname2ctype_offset(str2398), 410}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2397), 421}, + {uniname2ctype_offset(str2398), 413}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2405), 165}, - {uniname2ctype_offset(str2406), 107}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2414), 82}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2418), 449}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2427), 23}, - {uniname2ctype_offset(str2428), 183}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2433), 336}, + {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2433), 339}, {uniname2ctype_offset(str2434), 154}, {-1}, {-1}, {uniname2ctype_offset(str2437), 194}, - {uniname2ctype_offset(str2438), 182}, - {-1}, + {-1}, {-1}, {uniname2ctype_offset(str2440), 100}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -43778,36 +44873,36 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {uniname2ctype_offset(str2454), 220}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2460), 255}, + {uniname2ctype_offset(str2460), 257}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2464), 159}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2470), 445}, + {uniname2ctype_offset(str2470), 448}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2475), 142}, - {uniname2ctype_offset(str2476), 430}, + {uniname2ctype_offset(str2476), 558}, {-1}, - {uniname2ctype_offset(str2478), 414}, - {uniname2ctype_offset(str2479), 358}, + {uniname2ctype_offset(str2478), 417}, + {uniname2ctype_offset(str2479), 361}, {-1}, - {uniname2ctype_offset(str2481), 383}, + {uniname2ctype_offset(str2481), 386}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2500), 121}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2506), 528}, + {uniname2ctype_offset(str2506), 532}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2511), 242}, + {uniname2ctype_offset(str2511), 244}, {-1}, - {uniname2ctype_offset(str2513), 621}, + {uniname2ctype_offset(str2513), 630}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2530), 82}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2534), 337}, + {uniname2ctype_offset(str2534), 340}, + {uniname2ctype_offset(str2535), 237}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, - {uniname2ctype_offset(str2545), 275}, + {uniname2ctype_offset(str2545), 277}, {-1}, {-1}, {uniname2ctype_offset(str2548), 118}, {uniname2ctype_offset(str2549), 159}, @@ -43815,74 +44910,74 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str2551), 89}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2564), 369}, + {uniname2ctype_offset(str2564), 372}, {-1}, {-1}, - {uniname2ctype_offset(str2567), 315}, + {uniname2ctype_offset(str2567), 318}, {-1}, {-1}, - {uniname2ctype_offset(str2570), 488}, + {uniname2ctype_offset(str2570), 491}, {-1}, - {uniname2ctype_offset(str2572), 470}, - {-1}, {-1}, - {uniname2ctype_offset(str2575), 471}, - {uniname2ctype_offset(str2576), 273}, + {uniname2ctype_offset(str2572), 473}, + {-1}, + {uniname2ctype_offset(str2574), 246}, + {uniname2ctype_offset(str2575), 474}, + {uniname2ctype_offset(str2576), 275}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2582), 388}, + {uniname2ctype_offset(str2582), 391}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2588), 154}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2602), 210}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2606), 371}, + {uniname2ctype_offset(str2606), 374}, {-1}, - {uniname2ctype_offset(str2608), 455}, + {uniname2ctype_offset(str2608), 458}, {uniname2ctype_offset(str2609), 55}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2625), 260}, + {uniname2ctype_offset(str2625), 262}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2648), 252}, + {uniname2ctype_offset(str2648), 254}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2652), 380}, + {uniname2ctype_offset(str2652), 383}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2656), 365}, + {uniname2ctype_offset(str2656), 368}, {uniname2ctype_offset(str2657), 112}, - {-1}, {-1}, - {uniname2ctype_offset(str2660), 147}, - {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2665), 87}, {uniname2ctype_offset(str2666), 50}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2670), 623}, + {uniname2ctype_offset(str2670), 632}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2674), 87}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2688), 139}, - {uniname2ctype_offset(str2689), 631}, + {uniname2ctype_offset(str2689), 641}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2694), 48}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2705), 478}, + {uniname2ctype_offset(str2705), 481}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2726), 226}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, - {uniname2ctype_offset(str2738), 225}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, - {uniname2ctype_offset(str2750), 553}, + {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2750), 557}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2761), 18}, - {uniname2ctype_offset(str2762), 263}, + {uniname2ctype_offset(str2762), 265}, {-1}, {-1}, {uniname2ctype_offset(str2765), 133}, {uniname2ctype_offset(str2766), 54}, {-1}, {-1}, - {uniname2ctype_offset(str2769), 480}, + {uniname2ctype_offset(str2769), 483}, {-1}, {-1}, {uniname2ctype_offset(str2772), 199}, {-1}, {-1}, @@ -43890,94 +44985,87 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2786), 4}, - {uniname2ctype_offset(str2787), 80}, - {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2793), 34}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2801), 29}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2808), 509}, + {uniname2ctype_offset(str2808), 512}, {-1}, {uniname2ctype_offset(str2810), 226}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2816), 307}, + {uniname2ctype_offset(str2816), 310}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2821), 390}, + {uniname2ctype_offset(str2821), 393}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2830), 605}, + {uniname2ctype_offset(str2830), 614}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2834), 361}, + {uniname2ctype_offset(str2834), 364}, {-1}, {uniname2ctype_offset(str2836), 78}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2847), 246}, + {uniname2ctype_offset(str2847), 248}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2852), 302}, + {uniname2ctype_offset(str2852), 305}, {-1}, {-1}, {uniname2ctype_offset(str2855), 190}, {uniname2ctype_offset(str2856), 66}, {-1}, {-1}, - {uniname2ctype_offset(str2859), 424}, + {uniname2ctype_offset(str2859), 427}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2867), 306}, - {uniname2ctype_offset(str2868), 246}, + {uniname2ctype_offset(str2867), 309}, + {uniname2ctype_offset(str2868), 248}, {-1}, {-1}, - {uniname2ctype_offset(str2871), 252}, + {uniname2ctype_offset(str2871), 254}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2879), 204}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2886), 622}, + {uniname2ctype_offset(str2886), 631}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2901), 99}, - {uniname2ctype_offset(str2902), 474}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2902), 477}, {-1}, - {uniname2ctype_offset(str2904), 461}, - {uniname2ctype_offset(str2905), 271}, + {uniname2ctype_offset(str2904), 464}, + {uniname2ctype_offset(str2905), 273}, {-1}, - {uniname2ctype_offset(str2907), 271}, + {uniname2ctype_offset(str2907), 273}, {-1}, - {uniname2ctype_offset(str2909), 571}, + {uniname2ctype_offset(str2909), 577}, {-1}, - {uniname2ctype_offset(str2911), 447}, + {uniname2ctype_offset(str2911), 450}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2941), 360}, + {uniname2ctype_offset(str2941), 363}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2953), 198}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2964), 517}, + {uniname2ctype_offset(str2964), 520}, {-1}, - {uniname2ctype_offset(str2966), 236}, + {uniname2ctype_offset(str2966), 238}, {uniname2ctype_offset(str2967), 43}, - {-1}, - {uniname2ctype_offset(str2969), 204}, + {-1}, {-1}, {uniname2ctype_offset(str2970), 88}, - {-1}, - {uniname2ctype_offset(str2972), 244}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str2980), 195}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2984), 568}, + {uniname2ctype_offset(str2984), 574}, {-1}, {uniname2ctype_offset(str2986), 118}, {uniname2ctype_offset(str2987), 54}, - {uniname2ctype_offset(str2988), 468}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2993), 201}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str2997), 596}, + {uniname2ctype_offset(str2988), 471}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str2997), 603}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3001), 180}, {uniname2ctype_offset(str3002), 64}, {-1}, {-1}, - {uniname2ctype_offset(str3005), 472}, - {uniname2ctype_offset(str3006), 577}, + {uniname2ctype_offset(str3005), 475}, + {uniname2ctype_offset(str3006), 583}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3015), 83}, {-1}, {-1}, @@ -43985,60 +45073,73 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str3019), 165}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3026), 83}, - {uniname2ctype_offset(str3027), 500}, + {uniname2ctype_offset(str3027), 503}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3035), 232}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3042), 211}, {-1}, {-1}, - {uniname2ctype_offset(str3045), 606}, + {uniname2ctype_offset(str3045), 615}, {-1}, {-1}, {uniname2ctype_offset(str3048), 119}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3052), 89}, {uniname2ctype_offset(str3053), 229}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3058), 185}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3067), 185}, + {uniname2ctype_offset(str3068), 99}, + {-1}, {-1}, {uniname2ctype_offset(str3071), 198}, {uniname2ctype_offset(str3072), 133}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3082), 130}, - {-1}, {-1}, {-1}, + {-1}, + {uniname2ctype_offset(str3084), 237}, + {-1}, {uniname2ctype_offset(str3086), 84}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3091), 259}, + {uniname2ctype_offset(str3087), 342}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3091), 261}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3099), 475}, + {uniname2ctype_offset(str3099), 478}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3104), 84}, - {-1}, {-1}, - {uniname2ctype_offset(str3107), 310}, {-1}, - {uniname2ctype_offset(str3109), 312}, + {uniname2ctype_offset(str3106), 562}, + {uniname2ctype_offset(str3107), 313}, + {-1}, + {uniname2ctype_offset(str3109), 315}, {uniname2ctype_offset(str3110), 152}, {uniname2ctype_offset(str3111), 191}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3118), 90}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3123), 107}, + {-1}, {uniname2ctype_offset(str3125), 191}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3140), 607}, - {-1}, - {uniname2ctype_offset(str3142), 579}, + {uniname2ctype_offset(str3135), 452}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3142), 585}, + {-1}, {-1}, + {uniname2ctype_offset(str3145), 183}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3155), 182}, + {-1}, {uniname2ctype_offset(str3157), 231}, - {uniname2ctype_offset(str3158), 325}, + {uniname2ctype_offset(str3158), 328}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3176), 393}, + {uniname2ctype_offset(str3176), 396}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3180), 328}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3180), 331}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3193), 433}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3209), 62}, {-1}, {-1}, {uniname2ctype_offset(str3212), 211}, @@ -44046,97 +45147,97 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str3215), 180}, {-1}, {-1}, {uniname2ctype_offset(str3218), 125}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3227), 433}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3237), 387}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3237), 390}, {-1}, - {uniname2ctype_offset(str3239), 305}, - {uniname2ctype_offset(str3240), 301}, + {uniname2ctype_offset(str3239), 308}, + {uniname2ctype_offset(str3240), 304}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3248), 213}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3254), 250}, + {uniname2ctype_offset(str3254), 252}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3265), 601}, + {uniname2ctype_offset(str3265), 610}, {-1}, {uniname2ctype_offset(str3267), 28}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3275), 146}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3284), 609}, + {uniname2ctype_offset(str3284), 618}, {-1}, {-1}, {uniname2ctype_offset(str3287), 223}, - {-1}, {-1}, - {uniname2ctype_offset(str3290), 451}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3297), 308}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3297), 311}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3303), 304}, + {uniname2ctype_offset(str3303), 307}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3313), 253}, + {uniname2ctype_offset(str3313), 255}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3320), 223}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3328), 63}, - {uniname2ctype_offset(str3329), 616}, + {uniname2ctype_offset(str3329), 625}, {-1}, {uniname2ctype_offset(str3331), 222}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3338), 262}, + {uniname2ctype_offset(str3338), 264}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3348), 404}, + {uniname2ctype_offset(str3348), 407}, {-1}, - {uniname2ctype_offset(str3350), 498}, + {uniname2ctype_offset(str3350), 501}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3355), 47}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3360), 334}, + {uniname2ctype_offset(str3360), 337}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3371), 126}, {uniname2ctype_offset(str3372), 16}, - {uniname2ctype_offset(str3373), 251}, + {uniname2ctype_offset(str3373), 253}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3377), 147}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3391), 236}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3396), 604}, + {uniname2ctype_offset(str3391), 238}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3396), 613}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3405), 466}, + {uniname2ctype_offset(str3405), 469}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3416), 51}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3428), 251}, + {uniname2ctype_offset(str3428), 253}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3433), 205}, {-1}, {-1}, {uniname2ctype_offset(str3436), 213}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3452), 220}, - {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3455), 225}, + {-1}, {-1}, {uniname2ctype_offset(str3458), 10}, {-1}, - {uniname2ctype_offset(str3460), 618}, + {uniname2ctype_offset(str3460), 627}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3483), 243}, + {uniname2ctype_offset(str3483), 245}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3502), 49}, - {-1}, {-1}, - {uniname2ctype_offset(str3505), 476}, + {-1}, + {uniname2ctype_offset(str3504), 80}, + {uniname2ctype_offset(str3505), 479}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3511), 408}, + {uniname2ctype_offset(str3511), 411}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3532), 572}, + {uniname2ctype_offset(str3532), 578}, {uniname2ctype_offset(str3533), 57}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44152,11 +45253,13 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3613), 41}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3618), 99}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3648), 462}, + {-1}, {-1}, + {uniname2ctype_offset(str3648), 465}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3659), 178}, @@ -44164,18 +45267,23 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str3664), 177}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3698), 501}, - {uniname2ctype_offset(str3699), 548}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3686), 204}, + {-1}, {-1}, + {uniname2ctype_offset(str3689), 246}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3698), 504}, + {uniname2ctype_offset(str3699), 552}, {-1}, {uniname2ctype_offset(str3701), 96}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3705), 317}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3705), 320}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3710), 201}, + {-1}, {-1}, {uniname2ctype_offset(str3713), 96}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3721), 435}, + {uniname2ctype_offset(str3721), 438}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44184,57 +45292,59 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str3754), 71}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3766), 464}, - {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3770), 147}, + {uniname2ctype_offset(str3766), 467}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3799), 214}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3807), 258}, + {uniname2ctype_offset(str3807), 260}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3842), 182}, {-1}, {-1}, - {uniname2ctype_offset(str3845), 257}, + {uniname2ctype_offset(str3845), 259}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3850), 243}, + {uniname2ctype_offset(str3850), 245}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3854), 65}, + {-1}, {-1}, + {uniname2ctype_offset(str3857), 616}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3868), 259}, + {-1}, + {uniname2ctype_offset(str3868), 261}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3881), 228}, {-1}, {-1}, - {uniname2ctype_offset(str3884), 303}, + {uniname2ctype_offset(str3884), 306}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3891), 30}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3924), 417}, + {uniname2ctype_offset(str3924), 420}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str3942), 100}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3949), 274}, + {-1}, + {uniname2ctype_offset(str3944), 436}, + {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3949), 276}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str3976), 597}, - {-1}, - {uniname2ctype_offset(str3978), 80}, + {uniname2ctype_offset(str3976), 604}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, - {uniname2ctype_offset(str3998), 427}, + {-1}, {-1}, {-1}, + {uniname2ctype_offset(str3998), 430}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str4007), 454}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44242,13 +45352,10 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4066), 313}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4106), 45}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4116), 132}, @@ -44259,14 +45366,15 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str4169), 220}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4231), 153}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44277,18 +45385,17 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4308), 398}, + {uniname2ctype_offset(str4308), 401}, {uniname2ctype_offset(str4309), 162}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, - {uniname2ctype_offset(str4321), 240}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4348), 565}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, + {uniname2ctype_offset(str4348), 571}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4361), 566}, + {uniname2ctype_offset(str4361), 572}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44304,6 +45411,7 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str4487), 147}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44316,42 +45424,45 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4604), 116}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4626), 116}, - {uniname2ctype_offset(str4627), 253}, + {uniname2ctype_offset(str4627), 255}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4674), 632}, + {uniname2ctype_offset(str4674), 642}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4683), 463}, + {uniname2ctype_offset(str4683), 466}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, + {uniname2ctype_offset(str4695), 80}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {uniname2ctype_offset(str4734), 179}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4755), 309}, - {uniname2ctype_offset(str4756), 311}, + {uniname2ctype_offset(str4755), 312}, + {uniname2ctype_offset(str4756), 314}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4772), 406}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str4772), 409}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, + {uniname2ctype_offset(str4783), 316}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {uniname2ctype_offset(str4810), 90}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44376,7 +45487,7 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {uniname2ctype_offset(str4982), 232}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str4986), 425}, + {uniname2ctype_offset(str4986), 428}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44384,6 +45495,8 @@ uniname2ctype_p (register const char *str, register size_t len) {uniname2ctype_offset(str5018), 46}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, + {uniname2ctype_offset(str5038), 242}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, @@ -44394,8 +45507,8 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str5134), 619}, + {-1}, {-1}, {-1}, {-1}, {-1}, + {uniname2ctype_offset(str5134), 628}, #endif /* USE_UNICODE_PROPERTIES */ {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #ifndef USE_UNICODE_PROPERTIES @@ -44525,7 +45638,7 @@ uniname2ctype_p (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {uniname2ctype_offset(str6098), 633} + {uniname2ctype_offset(str6098), 643} #endif /* USE_UNICODE_PROPERTIES */ }; @@ -44556,22 +45669,22 @@ uniname2ctype(const UChar *name, unsigned int len) return -1; } #if defined ONIG_UNICODE_VERSION_STRING && !( \ - ONIG_UNICODE_VERSION_MAJOR == 14 && \ + ONIG_UNICODE_VERSION_MAJOR == 15 && \ ONIG_UNICODE_VERSION_MINOR == 0 && \ ONIG_UNICODE_VERSION_TEENY == 0 && \ 1) # error ONIG_UNICODE_VERSION_STRING mismatch #endif -#define ONIG_UNICODE_VERSION_STRING "14.0.0" -#define ONIG_UNICODE_VERSION_MAJOR 14 +#define ONIG_UNICODE_VERSION_STRING "15.0.0" +#define ONIG_UNICODE_VERSION_MAJOR 15 #define ONIG_UNICODE_VERSION_MINOR 0 #define ONIG_UNICODE_VERSION_TEENY 0 #if defined ONIG_UNICODE_EMOJI_VERSION_STRING && !( \ - ONIG_UNICODE_EMOJI_VERSION_MAJOR == 14 && \ + ONIG_UNICODE_EMOJI_VERSION_MAJOR == 15 && \ ONIG_UNICODE_EMOJI_VERSION_MINOR == 0 && \ 1) # error ONIG_UNICODE_EMOJI_VERSION_STRING mismatch #endif -#define ONIG_UNICODE_EMOJI_VERSION_STRING "14.0" -#define ONIG_UNICODE_EMOJI_VERSION_MAJOR 14 +#define ONIG_UNICODE_EMOJI_VERSION_STRING "15.0" +#define ONIG_UNICODE_EMOJI_VERSION_MAJOR 15 #define ONIG_UNICODE_EMOJI_VERSION_MINOR 0 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); } @@ -779,7 +779,7 @@ inject_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, p)) ENUM_WANT_SVALUE(); - if (memo->v1 == Qundef) { + if (UNDEF_P(memo->v1)) { MEMO_V1_SET(memo, i); } else { @@ -796,7 +796,7 @@ inject_op_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, p)) ENUM_WANT_SVALUE(); - if (memo->v1 == Qundef) { + if (UNDEF_P(memo->v1)) { MEMO_V1_SET(memo, i); } else if (SYMBOL_P(name = memo->u3.value)) { @@ -820,9 +820,9 @@ ary_inject_op(VALUE ary, VALUE init, VALUE op) long i, n; if (RARRAY_LEN(ary) == 0) - return init == Qundef ? Qnil : init; + return UNDEF_P(init) ? Qnil : init; - if (init == Qundef) { + if (UNDEF_P(init)) { v = RARRAY_AREF(ary, 0); i = 1; if (RARRAY_LEN(ary) == 1) @@ -1051,7 +1051,7 @@ enum_inject(int argc, VALUE *argv, VALUE obj) memo = MEMO_NEW(init, Qnil, op); rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo); - if (memo->v1 == Qundef) return Qnil; + if (UNDEF_P(memo->v1)) return Qnil; return memo->v1; } @@ -1373,7 +1373,6 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _data)) static int sort_by_cmp(const void *ap, const void *bp, void *data) { - struct cmp_opt_data cmp_opt = { 0, 0 }; VALUE a; VALUE b; VALUE ary = (VALUE)data; @@ -1385,7 +1384,7 @@ sort_by_cmp(const void *ap, const void *bp, void *data) a = *(VALUE *)ap; b = *(VALUE *)bp; - return OPTIMIZED_CMP(a, b, cmp_opt); + return OPTIMIZED_CMP(a, b); } /* @@ -1677,7 +1676,7 @@ enum_any(int argc, VALUE *argv, VALUE obj) DEFINE_ENUMFUNCS(one) { if (RTEST(result)) { - if (memo->v1 == Qundef) { + if (UNDEF_P(memo->v1)) { MEMO_V1_SET(memo, Qtrue); } else if (memo->v1 == Qtrue) { @@ -1713,11 +1712,10 @@ cmpint_reenter_check(struct nmin_data *data, VALUE val) static int nmin_cmp(const void *ap, const void *bp, void *_data) { - struct cmp_opt_data cmp_opt = { 0, 0 }; struct nmin_data *data = (struct nmin_data *)_data; VALUE a = *(const VALUE *)ap, b = *(const VALUE *)bp; #define rb_cmpint(cmp, a, b) rb_cmpint(cmpint_reenter_check(data, (cmp)), a, b) - return OPTIMIZED_CMP(a, b, cmp_opt); + return OPTIMIZED_CMP(a, b); #undef rb_cmpint } @@ -1827,7 +1825,7 @@ nmin_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _data)) else cmpv = i; - if (data->limit != Qundef) { + if (!UNDEF_P(data->limit)) { int c = data->cmpfunc(&cmpv, &data->limit, data); if (data->rev) c = -c; @@ -1962,7 +1960,7 @@ enum_one(int argc, VALUE *argv, VALUE obj) WARN_UNUSED_BLOCK(argc); rb_block_call(obj, id_each, 0, 0, ENUMFUNC(one), (VALUE)memo); result = memo->v1; - if (result == Qundef) return Qfalse; + if (UNDEF_P(result)) return Qfalse; return result; } @@ -2027,7 +2025,6 @@ enum_none(int argc, VALUE *argv, VALUE obj) struct min_t { VALUE min; - struct cmp_opt_data cmp_opt; }; static VALUE @@ -2037,11 +2034,11 @@ min_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) ENUM_WANT_SVALUE(); - if (memo->min == Qundef) { + if (UNDEF_P(memo->min)) { memo->min = i; } else { - if (OPTIMIZED_CMP(i, memo->min, memo->cmp_opt) < 0) { + if (OPTIMIZED_CMP(i, memo->min) < 0) { memo->min = i; } } @@ -2056,7 +2053,7 @@ min_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) ENUM_WANT_SVALUE(); - if (memo->min == Qundef) { + if (UNDEF_P(memo->min)) { memo->min = i; } else { @@ -2130,7 +2127,7 @@ static VALUE enum_min(int argc, VALUE *argv, VALUE obj) { VALUE memo; - struct min_t *m = NEW_CMP_OPT_MEMO(struct min_t, memo); + struct min_t *m = NEW_MEMO_FOR(struct min_t, memo); VALUE result; VALUE num; @@ -2138,8 +2135,6 @@ enum_min(int argc, VALUE *argv, VALUE obj) return rb_nmin_run(obj, num, 0, 0, 0); m->min = Qundef; - m->cmp_opt.opt_methods = 0; - m->cmp_opt.opt_inited = 0; if (rb_block_given_p()) { rb_block_call(obj, id_each, 0, 0, min_ii, memo); } @@ -2147,13 +2142,12 @@ enum_min(int argc, VALUE *argv, VALUE obj) rb_block_call(obj, id_each, 0, 0, min_i, memo); } result = m->min; - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } struct max_t { VALUE max; - struct cmp_opt_data cmp_opt; }; static VALUE @@ -2163,11 +2157,11 @@ max_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) ENUM_WANT_SVALUE(); - if (memo->max == Qundef) { + if (UNDEF_P(memo->max)) { memo->max = i; } else { - if (OPTIMIZED_CMP(i, memo->max, memo->cmp_opt) > 0) { + if (OPTIMIZED_CMP(i, memo->max) > 0) { memo->max = i; } } @@ -2182,7 +2176,7 @@ max_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) ENUM_WANT_SVALUE(); - if (memo->max == Qundef) { + if (UNDEF_P(memo->max)) { memo->max = i; } else { @@ -2255,7 +2249,7 @@ static VALUE enum_max(int argc, VALUE *argv, VALUE obj) { VALUE memo; - struct max_t *m = NEW_CMP_OPT_MEMO(struct max_t, memo); + struct max_t *m = NEW_MEMO_FOR(struct max_t, memo); VALUE result; VALUE num; @@ -2263,8 +2257,6 @@ enum_max(int argc, VALUE *argv, VALUE obj) return rb_nmin_run(obj, num, 0, 1, 0); m->max = Qundef; - m->cmp_opt.opt_methods = 0; - m->cmp_opt.opt_inited = 0; if (rb_block_given_p()) { rb_block_call(obj, id_each, 0, 0, max_ii, (VALUE)memo); } @@ -2272,7 +2264,7 @@ enum_max(int argc, VALUE *argv, VALUE obj) rb_block_call(obj, id_each, 0, 0, max_i, (VALUE)memo); } result = m->max; - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } @@ -2280,7 +2272,6 @@ struct minmax_t { VALUE min; VALUE max; VALUE last; - struct cmp_opt_data cmp_opt; }; static void @@ -2288,16 +2279,16 @@ minmax_i_update(VALUE i, VALUE j, struct minmax_t *memo) { int n; - if (memo->min == Qundef) { + if (UNDEF_P(memo->min)) { memo->min = i; memo->max = j; } else { - n = OPTIMIZED_CMP(i, memo->min, memo->cmp_opt); + n = OPTIMIZED_CMP(i, memo->min); if (n < 0) { memo->min = i; } - n = OPTIMIZED_CMP(j, memo->max, memo->cmp_opt); + n = OPTIMIZED_CMP(j, memo->max); if (n > 0) { memo->max = j; } @@ -2313,14 +2304,14 @@ minmax_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) ENUM_WANT_SVALUE(); - if (memo->last == Qundef) { + if (UNDEF_P(memo->last)) { memo->last = i; return Qnil; } j = memo->last; memo->last = Qundef; - n = OPTIMIZED_CMP(j, i, memo->cmp_opt); + n = OPTIMIZED_CMP(j, i); if (n == 0) i = j; else if (n < 0) { @@ -2340,7 +2331,7 @@ minmax_ii_update(VALUE i, VALUE j, struct minmax_t *memo) { int n; - if (memo->min == Qundef) { + if (UNDEF_P(memo->min)) { memo->min = i; memo->max = j; } @@ -2365,7 +2356,7 @@ minmax_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) ENUM_WANT_SVALUE(); - if (memo->last == Qundef) { + if (UNDEF_P(memo->last)) { memo->last = i; return Qnil; } @@ -2422,23 +2413,21 @@ static VALUE enum_minmax(VALUE obj) { VALUE memo; - struct minmax_t *m = NEW_CMP_OPT_MEMO(struct minmax_t, memo); + struct minmax_t *m = NEW_MEMO_FOR(struct minmax_t, memo); m->min = Qundef; m->last = Qundef; - m->cmp_opt.opt_methods = 0; - m->cmp_opt.opt_inited = 0; if (rb_block_given_p()) { rb_block_call(obj, id_each, 0, 0, minmax_ii, memo); - if (m->last != Qundef) + if (!UNDEF_P(m->last)) minmax_ii_update(m->last, m->last, m); } else { rb_block_call(obj, id_each, 0, 0, minmax_i, memo); - if (m->last != Qundef) + if (!UNDEF_P(m->last)) minmax_i_update(m->last, m->last, m); } - if (m->min != Qundef) { + if (!UNDEF_P(m->min)) { return rb_assoc_new(m->min, m->max); } return rb_assoc_new(Qnil, Qnil); @@ -2447,18 +2436,17 @@ enum_minmax(VALUE obj) static VALUE min_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) { - struct cmp_opt_data cmp_opt = { 0, 0 }; struct MEMO *memo = MEMO_CAST(args); VALUE v; ENUM_WANT_SVALUE(); v = enum_yield(argc, i); - if (memo->v1 == Qundef) { + if (UNDEF_P(memo->v1)) { MEMO_V1_SET(memo, v); MEMO_V2_SET(memo, i); } - else if (OPTIMIZED_CMP(v, memo->v1, cmp_opt) < 0) { + else if (OPTIMIZED_CMP(v, memo->v1) < 0) { MEMO_V1_SET(memo, v); MEMO_V2_SET(memo, i); } @@ -2522,18 +2510,17 @@ enum_min_by(int argc, VALUE *argv, VALUE obj) static VALUE max_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) { - struct cmp_opt_data cmp_opt = { 0, 0 }; struct MEMO *memo = MEMO_CAST(args); VALUE v; ENUM_WANT_SVALUE(); v = enum_yield(argc, i); - if (memo->v1 == Qundef) { + if (UNDEF_P(memo->v1)) { MEMO_V1_SET(memo, v); MEMO_V2_SET(memo, i); } - else if (OPTIMIZED_CMP(v, memo->v1, cmp_opt) > 0) { + else if (OPTIMIZED_CMP(v, memo->v1) > 0) { MEMO_V1_SET(memo, v); MEMO_V2_SET(memo, i); } @@ -2606,20 +2593,18 @@ struct minmax_by_t { static void minmax_by_i_update(VALUE v1, VALUE v2, VALUE i1, VALUE i2, struct minmax_by_t *memo) { - struct cmp_opt_data cmp_opt = { 0, 0 }; - - if (memo->min_bv == Qundef) { + if (UNDEF_P(memo->min_bv)) { memo->min_bv = v1; memo->max_bv = v2; memo->min = i1; memo->max = i2; } else { - if (OPTIMIZED_CMP(v1, memo->min_bv, cmp_opt) < 0) { + if (OPTIMIZED_CMP(v1, memo->min_bv) < 0) { memo->min_bv = v1; memo->min = i1; } - if (OPTIMIZED_CMP(v2, memo->max_bv, cmp_opt) > 0) { + if (OPTIMIZED_CMP(v2, memo->max_bv) > 0) { memo->max_bv = v2; memo->max = i2; } @@ -2629,7 +2614,6 @@ minmax_by_i_update(VALUE v1, VALUE v2, VALUE i1, VALUE i2, struct minmax_by_t *m static VALUE minmax_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) { - struct cmp_opt_data cmp_opt = { 0, 0 }; struct minmax_by_t *memo = MEMO_FOR(struct minmax_by_t, _memo); VALUE vi, vj, j; int n; @@ -2638,7 +2622,7 @@ minmax_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) vi = enum_yield(argc, i); - if (memo->last_bv == Qundef) { + if (UNDEF_P(memo->last_bv)) { memo->last_bv = vi; memo->last = i; return Qnil; @@ -2647,7 +2631,7 @@ minmax_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) j = memo->last; memo->last_bv = Qundef; - n = OPTIMIZED_CMP(vj, vi, cmp_opt); + n = OPTIMIZED_CMP(vj, vi); if (n == 0) { i = j; vi = vj; @@ -2705,7 +2689,7 @@ enum_minmax_by(VALUE obj) m->last_bv = Qundef; m->last = Qundef; rb_block_call(obj, id_each, 0, 0, minmax_by_i, memo); - if (m->last_bv != Qundef) + if (!UNDEF_P(m->last_bv)) minmax_by_i_update(m->last_bv, m->last_bv, m->last, m->last, m); m = MEMO_FOR(struct minmax_by_t, memo); return rb_assoc_new(m->min, m->max); @@ -3033,7 +3017,6 @@ each_cons_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) static VALUE enum_each_cons_size(VALUE obj, VALUE args, VALUE eobj) { - struct cmp_opt_data cmp_opt = { 0, 0 }; const VALUE zero = LONG2FIX(0); VALUE n, size; long cons_size = NUM2LONG(RARRAY_AREF(args, 0)); @@ -3043,7 +3026,7 @@ enum_each_cons_size(VALUE obj, VALUE args, VALUE eobj) if (NIL_P(size)) return Qnil; n = add_int(size, 1 - cons_size); - return (OPTIMIZED_CMP(n, zero, cmp_opt) == -1) ? zero : n; + return (OPTIMIZED_CMP(n, zero) == -1) ? zero : n; } /* @@ -3185,7 +3168,7 @@ zip_i(RB_BLOCK_CALL_FUNC_ARGLIST(val, memoval)) v[1] = RARRAY_AREF(args, i); rb_rescue2(call_next, (VALUE)v, call_stop, (VALUE)v, rb_eStopIteration, (VALUE)0); - if (v[0] == Qundef) { + if (UNDEF_P(v[0])) { RARRAY_ASET(args, i, Qnil); v[0] = Qnil; } @@ -3834,7 +3817,7 @@ slicebefore_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator)) /* * call-seq: * slice_before(pattern) -> enumerator - * slice_before {|array| ... } -> enumerator + * slice_before {|elt| ... } -> enumerator * * With argument +pattern+, returns an enumerator that uses the pattern * to partition elements into arrays ("slices"). @@ -4155,7 +4138,7 @@ slicewhen_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo)) ENUM_WANT_SVALUE(); - if (memo->prev_elt == Qundef) { + if (UNDEF_P(memo->prev_elt)) { /* The first element */ memo->prev_elt = i; memo->prev_elts = rb_ary_new3(1, i); @@ -4395,7 +4378,7 @@ sum_iter_bignum(VALUE i, struct enum_sum_memo *memo) static void sum_iter_rational(VALUE i, struct enum_sum_memo *memo) { - if (memo->r == Qundef) { + if (UNDEF_P(memo->r)) { memo->r = i; } else { @@ -4476,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: @@ -4616,7 +4599,7 @@ enum_sum(int argc, VALUE* argv, VALUE obj) else { if (memo.n != 0) memo.v = rb_fix_plus(LONG2FIX(memo.n), memo.v); - if (memo.r != Qundef) { + if (!UNDEF_P(memo.r)) { memo.v = rb_rational_plus(memo.r, memo.v); } return memo.v; diff --git a/enumerator.c b/enumerator.c index 2c9858cda6..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 @@ -173,9 +208,11 @@ struct producer { typedef struct MEMO *lazyenum_proc_func(VALUE, struct MEMO *, VALUE, long); typedef VALUE lazyenum_size_func(VALUE, VALUE); +typedef int lazyenum_precheck_func(VALUE proc_entry); typedef struct { lazyenum_proc_func *proc; lazyenum_size_func *size; + lazyenum_precheck_func *precheck; } lazyenum_funcs; struct proc_entry { @@ -260,7 +297,7 @@ enumerator_ptr(VALUE obj) struct enumerator *ptr; TypedData_Get_Struct(obj, struct enumerator, &enumerator_data_type, ptr); - if (!ptr || ptr->obj == Qundef) { + if (!ptr || UNDEF_P(ptr->obj)) { rb_raise(rb_eArgError, "uninitialized enumerator"); } return ptr; @@ -519,8 +556,8 @@ rb_enumeratorize(VALUE obj, VALUE meth, int argc, const VALUE *argv) return rb_enumeratorize_with_size(obj, meth, argc, argv, 0); } -static VALUE -lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat); +static VALUE lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat); +static int lazy_precheck(VALUE procs); VALUE rb_enumeratorize_with_size_kw(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat) @@ -598,9 +635,10 @@ enumerator_block_call(VALUE obj, rb_block_call_func *func, VALUE arg) static VALUE enumerator_each(int argc, VALUE *argv, VALUE obj) { + struct enumerator *e = enumerator_ptr(obj); + if (argc > 0) { - struct enumerator *e = enumerator_ptr(obj = rb_obj_dup(obj)); - VALUE args = e->args; + VALUE args = (e = enumerator_ptr(obj = rb_obj_dup(obj)))->args; if (args) { #if SIZEOF_INT < SIZEOF_LONG /* check int range overflow */ @@ -617,6 +655,9 @@ enumerator_each(int argc, VALUE *argv, VALUE obj) e->size_fn = 0; } if (!rb_block_given_p()) return obj; + + if (!lazy_precheck(e->procs)) return Qnil; + return enumerator_block_call(obj, 0, obj); } @@ -735,7 +776,7 @@ next_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, obj)) VALUE feedvalue = Qnil; VALUE args = rb_ary_new4(argc, argv); rb_fiber_yield(1, &args); - if (e->feedvalue != Qundef) { + if (!UNDEF_P(e->feedvalue)) { feedvalue = e->feedvalue; e->feedvalue = Qundef; } @@ -769,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(); @@ -840,7 +889,7 @@ enumerator_next_values(VALUE obj) struct enumerator *e = enumerator_ptr(obj); VALUE vs; - if (e->lookahead != Qundef) { + if (!UNDEF_P(e->lookahead)) { vs = e->lookahead; e->lookahead = Qundef; return vs; @@ -901,7 +950,7 @@ enumerator_peek_values(VALUE obj) { struct enumerator *e = enumerator_ptr(obj); - if (e->lookahead == Qundef) { + if (UNDEF_P(e->lookahead)) { e->lookahead = get_next_values(obj, e); } return e->lookahead; @@ -1025,7 +1074,7 @@ enumerator_feed(VALUE obj, VALUE v) { struct enumerator *e = enumerator_ptr(obj); - if (e->feedvalue != Qundef) { + if (!UNDEF_P(e->feedvalue)) { rb_raise(rb_eTypeError, "feed value already set"); } e->feedvalue = v; @@ -1070,7 +1119,7 @@ inspect_enumerator(VALUE obj, VALUE dummy, int recur) cname = rb_obj_class(obj); - if (!e || e->obj == Qundef) { + if (!e || UNDEF_P(e->obj)) { return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(cname)); } @@ -1239,7 +1288,7 @@ enumerator_size(VALUE obj) argv = RARRAY_CONST_PTR(e->args); } size = rb_check_funcall_kw(e->size, id_call, argc, argv, e->kw_splat); - if (size != Qundef) return size; + if (!UNDEF_P(size)) return size; return e->size; } @@ -1285,7 +1334,7 @@ yielder_ptr(VALUE obj) struct yielder *ptr; TypedData_Get_Struct(obj, struct yielder, &yielder_data_type, ptr); - if (!ptr || ptr->proc == Qundef) { + if (!ptr || UNDEF_P(ptr->proc)) { rb_raise(rb_eArgError, "uninitialized yielder"); } return ptr; @@ -1425,7 +1474,7 @@ generator_ptr(VALUE obj) struct generator *ptr; TypedData_Get_Struct(obj, struct generator, &generator_data_type, ptr); - if (!ptr || ptr->proc == Qundef) { + if (!ptr || UNDEF_P(ptr->proc)) { rb_raise(rb_eArgError, "uninitialized generator"); } return ptr; @@ -1529,7 +1578,7 @@ static VALUE enum_size(VALUE self) { VALUE r = rb_check_funcall(self, id_size, 0, 0); - return (r == Qundef) ? Qnil : r; + return UNDEF_P(r) ? Qnil : r; } static VALUE @@ -1562,7 +1611,7 @@ lazy_init_iterator(RB_BLOCK_CALL_FUNC_ARGLIST(val, m)) result = rb_yield_values2(len, nargv); ALLOCV_END(args); } - if (result == Qundef) rb_iter_break(); + if (UNDEF_P(result)) rb_iter_break(); return Qnil; } @@ -1676,6 +1725,22 @@ lazy_generator_init(VALUE enumerator, VALUE procs) return generator; } +static int +lazy_precheck(VALUE procs) +{ + if (RTEST(procs)) { + long num_procs = RARRAY_LEN(procs), i = num_procs; + while (i-- > 0) { + VALUE proc = RARRAY_AREF(procs, i); + struct proc_entry *entry = proc_entry_ptr(proc); + lazyenum_precheck_func *precheck = entry->fn->precheck; + if (precheck && !precheck(proc)) return FALSE; + } + } + + return TRUE; +} + /* * Document-class: Enumerator::Lazy * @@ -2425,13 +2490,8 @@ lazy_take_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_ind } remain = NUM2LONG(memo); - if (remain == 0) { - LAZY_MEMO_SET_BREAK(result); - } - else { - if (--remain == 0) LAZY_MEMO_SET_BREAK(result); - rb_ary_store(memos, memo_index, LONG2NUM(remain)); - } + if (--remain == 0) LAZY_MEMO_SET_BREAK(result); + rb_ary_store(memos, memo_index, LONG2NUM(remain)); return result; } @@ -2444,8 +2504,15 @@ lazy_take_size(VALUE entry, VALUE receiver) return LONG2NUM(len); } +static int +lazy_take_precheck(VALUE proc_entry) +{ + struct proc_entry *entry = proc_entry_ptr(proc_entry); + return entry->memo != INT2FIX(0); +} + static const lazyenum_funcs lazy_take_funcs = { - lazy_take_proc, lazy_take_size, + lazy_take_proc, lazy_take_size, lazy_take_precheck, }; /* @@ -2459,20 +2526,14 @@ static VALUE lazy_take(VALUE obj, VALUE n) { long len = NUM2LONG(n); - int argc = 0; - VALUE argv[2]; if (len < 0) { rb_raise(rb_eArgError, "attempt to take negative size"); } - if (len == 0) { - argv[0] = sym_cycle; - argv[1] = INT2NUM(0); - argc = 2; - } + n = LONG2NUM(len); /* no more conversion */ - return lazy_add_method(obj, argc, argv, n, rb_ary_new3(1, n), &lazy_take_funcs); + return lazy_add_method(obj, 0, 0, n, rb_ary_new3(1, n), &lazy_take_funcs); } static struct MEMO * @@ -2923,7 +2984,7 @@ producer_ptr(VALUE obj) struct producer *ptr; TypedData_Get_Struct(obj, struct producer, &producer_data_type, ptr); - if (!ptr || ptr->proc == Qundef) { + if (!ptr || UNDEF_P(ptr->proc)) { rb_raise(rb_eArgError, "uninitialized producer"); } return ptr; @@ -2978,7 +3039,7 @@ producer_each_i(VALUE obj) init = ptr->init; proc = ptr->proc; - if (init == Qundef) { + if (UNDEF_P(init)) { curr = Qnil; } else { @@ -3109,7 +3170,7 @@ enum_chain_ptr(VALUE obj) struct enum_chain *ptr; TypedData_Get_Struct(obj, struct enum_chain, &enum_chain_data_type, ptr); - if (!ptr || ptr->enums == Qundef) { + if (!ptr || UNDEF_P(ptr->enums)) { rb_raise(rb_eArgError, "uninitialized chain"); } return ptr; @@ -3302,7 +3363,7 @@ inspect_enum_chain(VALUE obj, VALUE dummy, int recur) TypedData_Get_Struct(obj, struct enum_chain, &enum_chain_data_type, ptr); - if (!ptr || ptr->enums == Qundef) { + if (!ptr || UNDEF_P(ptr->enums)) { return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(klass)); } @@ -3384,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. @@ -3431,7 +3492,7 @@ enum_product_ptr(VALUE obj) struct enum_product *ptr; TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr); - if (!ptr || ptr->enums == Qundef) { + if (!ptr || UNDEF_P(ptr->enums)) { rb_raise(rb_eArgError, "uninitialized product"); } return ptr; @@ -3462,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); @@ -3570,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; @@ -3642,7 +3710,7 @@ inspect_enum_product(VALUE obj, VALUE dummy, int recur) TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr); - if (!ptr || ptr->enums == Qundef) { + if (!ptr || UNDEF_P(ptr->enums)) { return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(klass)); } @@ -3668,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 @@ -3676,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; } /* @@ -4567,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); @@ -4578,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); @@ -125,6 +125,8 @@ err_vcatf(VALUE str, const char *pre, const char *file, int line, return str; } +static VALUE syntax_error_with_path(VALUE, VALUE, VALUE*, rb_encoding*); + VALUE rb_syntax_error_append(VALUE exc, VALUE file, int line, int column, rb_encoding *enc, const char *fmt, va_list args) @@ -138,15 +140,7 @@ rb_syntax_error_append(VALUE exc, VALUE file, int line, int column, } else { VALUE mesg; - if (NIL_P(exc)) { - mesg = rb_enc_str_new(0, 0, enc); - exc = rb_class_new_instance(1, &mesg, rb_eSyntaxError); - } - else { - mesg = rb_attr_get(exc, idMesg); - if (RSTRING_LEN(mesg) > 0 && *(RSTRING_END(mesg)-1) != '\n') - rb_str_cat_cstr(mesg, "\n"); - } + exc = syntax_error_with_path(exc, file, &mesg, enc); err_vcatf(mesg, NULL, fn, line, fmt, args); } @@ -426,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)); } @@ -458,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)); } @@ -511,12 +505,9 @@ rb_warn_deprecated(const char *fmt, const char *suggest, ...) { if (!deprecation_warning_enabled()) return; - va_list args; - va_start(args, suggest); - VALUE mesg = warning_string(0, fmt, args); - va_end(args); - - warn_deprecated(mesg, NULL, suggest); + with_warning_string_from(mesg, 0, fmt, suggest) { + warn_deprecated(mesg, NULL, suggest); + } } void @@ -524,12 +515,9 @@ rb_warn_deprecated_to_remove(const char *removal, const char *fmt, const char *s { if (!deprecation_warning_enabled()) return; - va_list args; - va_start(args, suggest); - VALUE mesg = warning_string(0, fmt, args); - va_end(args); - - warn_deprecated(mesg, removal, suggest); + with_warning_string_from(mesg, 0, fmt, suggest) { + warn_deprecated(mesg, removal, suggest); + } } static inline int @@ -672,6 +660,11 @@ bug_important_message(FILE *out, const char *const msg, size_t len) fwrite(p, 1, endmsg - p, out); } +#undef CRASH_REPORTER_MAY_BE_CREATED +#if defined(__APPLE__) && \ + (!defined(MAC_OS_X_VERSION_10_6) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6 || defined(__POWERPC__)) /* 10.6 PPC case */ +# define CRASH_REPORTER_MAY_BE_CREATED +#endif static void preface_dump(FILE *out) { @@ -680,7 +673,7 @@ preface_dump(FILE *out) "-- Crash Report log information " "--------------------------------------------\n" " See Crash Report log file in one of the following locations:\n" -# if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6 +# ifdef CRASH_REPORTER_MAY_BE_CREATED " * ~/Library/Logs/CrashReporter\n" " * /Library/Logs/CrashReporter\n" # endif @@ -705,7 +698,7 @@ postscript_dump(FILE *out) "[IMPORTANT]" /*" ------------------------------------------------"*/ "\n""Don't forget to include the Crash Report log file under\n" -# if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6 +# ifdef CRASH_REPORTER_MAY_BE_CREATED "CrashReporter or " # endif "DiagnosticReports directory in bug reports.\n" @@ -1006,7 +999,7 @@ rb_check_type(VALUE x, int t) { int xt; - if (RB_UNLIKELY(x == Qundef)) { + if (RB_UNLIKELY(UNDEF_P(x))) { rb_bug(UNDEF_LEAKED); } @@ -1027,7 +1020,7 @@ rb_check_type(VALUE x, int t) void rb_unexpected_type(VALUE x, int t) { - if (RB_UNLIKELY(x == Qundef)) { + if (RB_UNLIKELY(UNDEF_P(x))) { rb_bug(UNDEF_LEAKED); } @@ -1229,7 +1222,7 @@ VALUE rb_get_message(VALUE exc) { VALUE e = rb_check_funcall(exc, id_message, 0, 0); - if (e == Qundef) return Qnil; + if (UNDEF_P(e)) return Qnil; if (!RB_TYPE_P(e, T_STRING)) e = rb_check_string_type(e); return e; } @@ -1244,7 +1237,7 @@ rb_get_detailed_message(VALUE exc, VALUE opt) else { e = rb_check_funcall_kw(exc, id_detailed_message, 1, &opt, 1); } - if (e == Qundef) return Qnil; + if (UNDEF_P(e)) return Qnil; if (!RB_TYPE_P(e, T_STRING)) e = rb_check_string_type(e); return e; } @@ -1636,15 +1629,15 @@ exc_equal(VALUE exc, VALUE obj) int state; obj = rb_protect(try_convert_to_exception, obj, &state); - if (state || obj == Qundef) { + if (state || UNDEF_P(obj)) { rb_set_errinfo(Qnil); return Qfalse; } if (rb_obj_class(exc) != rb_obj_class(obj)) return Qfalse; mesg = rb_check_funcall(obj, id_message, 0, 0); - if (mesg == Qundef) return Qfalse; + if (UNDEF_P(mesg)) return Qfalse; backtrace = rb_check_funcall(obj, id_backtrace, 0, 0); - if (backtrace == Qundef) return Qfalse; + if (UNDEF_P(backtrace)) return Qfalse; } else { mesg = rb_attr_get(obj, id_mesg); @@ -1747,7 +1740,7 @@ exit_success_p(VALUE exc) static VALUE err_init_recv(VALUE exc, VALUE recv) { - if (recv != Qundef) rb_ivar_set(exc, id_recv, recv); + if (!UNDEF_P(recv)) rb_ivar_set(exc, id_recv, recv); return exc; } @@ -1825,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; } @@ -2091,7 +2086,7 @@ name_err_mesg_to_str(VALUE obj) break; default: d = rb_protect(name_err_mesg_receiver_name, obj, &state); - if (state || d == Qundef || NIL_P(d)) + if (state || NIL_OR_UNDEF_P(d)) d = rb_protect(rb_inspect, obj, &state); if (state) { rb_set_errinfo(Qnil); @@ -2146,7 +2141,7 @@ name_err_receiver(VALUE self) VALUE *ptr, recv, mesg; recv = rb_ivar_lookup(self, id_recv, Qundef); - if (recv != Qundef) return recv; + if (!UNDEF_P(recv)) return recv; mesg = rb_attr_get(self, id_mesg); if (!rb_typeddata_is_kind_of(mesg, &name_err_mesg_data_type)) { @@ -2204,7 +2199,7 @@ key_err_receiver(VALUE self) VALUE recv; recv = rb_ivar_lookup(self, id_receiver, Qundef); - if (recv != Qundef) return recv; + if (!UNDEF_P(recv)) return recv; rb_raise(rb_eArgError, "no receiver is available"); } @@ -2221,7 +2216,7 @@ key_err_key(VALUE self) VALUE key; key = rb_ivar_lookup(self, id_key, Qundef); - if (key != Qundef) return key; + if (!UNDEF_P(key)) return key; rb_raise(rb_eArgError, "no key is available"); } @@ -2259,7 +2254,7 @@ key_err_initialize(int argc, VALUE *argv, VALUE self) keywords[1] = id_key; rb_get_kwargs(options, keywords, 0, numberof(values), values); for (i = 0; i < numberof(values); ++i) { - if (values[i] != Qundef) { + if (!UNDEF_P(values[i])) { rb_ivar_set(self, keywords[i], values[i]); } } @@ -2281,7 +2276,7 @@ no_matching_pattern_key_err_matchee(VALUE self) VALUE matchee; matchee = rb_ivar_lookup(self, id_matchee, Qundef); - if (matchee != Qundef) return matchee; + if (!UNDEF_P(matchee)) return matchee; rb_raise(rb_eArgError, "no matchee is available"); } @@ -2298,7 +2293,7 @@ no_matching_pattern_key_err_key(VALUE self) VALUE key; key = rb_ivar_lookup(self, id_key, Qundef); - if (key != Qundef) return key; + if (!UNDEF_P(key)) return key; rb_raise(rb_eArgError, "no key is available"); } @@ -2325,7 +2320,7 @@ no_matching_pattern_key_err_initialize(int argc, VALUE *argv, VALUE self) keywords[1] = id_key; rb_get_kwargs(options, keywords, 0, numberof(values), values); for (i = 0; i < numberof(values); ++i) { - if (values[i] != Qundef) { + if (!UNDEF_P(values[i])) { rb_ivar_set(self, keywords[i], values[i]); } } @@ -2354,6 +2349,25 @@ syntax_error_initialize(int argc, VALUE *argv, VALUE self) return rb_call_super(argc, argv); } +static VALUE +syntax_error_with_path(VALUE exc, VALUE path, VALUE *mesg, rb_encoding *enc) +{ + if (NIL_P(exc)) { + *mesg = rb_enc_str_new(0, 0, enc); + exc = rb_class_new_instance(1, mesg, rb_eSyntaxError); + rb_ivar_set(exc, id_i_path, path); + } + else { + if (rb_attr_get(exc, id_i_path) != path) { + rb_raise(rb_eArgError, "SyntaxError#path changed"); + } + VALUE s = *mesg = rb_attr_get(exc, idMesg); + if (RSTRING_LEN(s) > 0 && *(RSTRING_END(s)-1) != '\n') + rb_str_cat_cstr(s, "\n"); + } + return exc; +} + /* * Document-module: Errno * @@ -2947,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) { @@ -2961,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); } @@ -3012,9 +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, path, TRUE, FALSE, FALSE); + rb_eLoadError = rb_define_class("LoadError", rb_eScriptError); /* the path failed to load */ - rb_attr(rb_eLoadError, rb_intern_const("path"), TRUE, FALSE, FALSE); + rb_attr(rb_eLoadError, path, TRUE, FALSE, FALSE); rb_eNotImpError = rb_define_class("NotImplementedError", rb_eScriptError); @@ -21,6 +21,7 @@ #include "gc.h" #include "internal.h" #include "internal/class.h" +#include "internal/cont.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/hash.h" @@ -42,7 +43,7 @@ NORETURN(static void rb_raise_jump(VALUE, VALUE)); void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec); void rb_ec_clear_all_trace_func(const rb_execution_context_t *ec); -static int rb_ec_cleanup(rb_execution_context_t *ec, int ex); +static int rb_ec_cleanup(rb_execution_context_t *ec, enum ruby_tag_type ex); static int rb_ec_exec_node(rb_execution_context_t *ec, void *n); VALUE rb_eLocalJumpError; @@ -99,8 +100,10 @@ ruby_init(void) { int state = ruby_setup(); if (state) { - if (RTEST(ruby_debug)) - error_print(GET_EC()); + if (RTEST(ruby_debug)) { + rb_execution_context_t *ec = GET_EC(); + rb_ec_error_print(ec, ec->errinfo); + } exit(EXIT_FAILURE); } } @@ -119,8 +122,9 @@ ruby_options(int argc, char **argv) } else { rb_ec_clear_current_thread_trace_func(ec); - state = error_handle(ec, state); - iseq = (void *)INT2FIX(state); + int exitcode = error_handle(ec, ec->errinfo, state); + ec->errinfo = Qnil; /* just been handled */ + iseq = (void *)INT2FIX(exitcode); } EC_POP_TAG(); return iseq; @@ -136,7 +140,7 @@ rb_ec_fiber_scheduler_finalize(rb_execution_context_t *ec) rb_fiber_scheduler_set(Qnil); } else { - state = error_handle(ec, state); + state = error_handle(ec, ec->errinfo, state); } EC_POP_TAG(); } @@ -175,20 +179,21 @@ ruby_finalize(void) int ruby_cleanup(int ex) { - return rb_ec_cleanup(GET_EC(), ex); + return rb_ec_cleanup(GET_EC(), (enum ruby_tag_type)ex); } static int -rb_ec_cleanup(rb_execution_context_t *ec, int ex0) +rb_ec_cleanup(rb_execution_context_t *ec, enum ruby_tag_type ex) { int state; - volatile VALUE errs[2] = { Qundef, Qundef }; - int nerr; + volatile VALUE save_error = Qundef; + volatile int sysex = EXIT_SUCCESS; + volatile int signaled = 0; rb_thread_t *th = rb_ec_thread_ptr(ec); rb_thread_t *const volatile th0 = th; - volatile int sysex = EXIT_SUCCESS; volatile int step = 0; - volatile int ex = ex0; + volatile VALUE message = Qnil; + VALUE buf; rb_threadptr_interrupt(th); rb_threadptr_check_signal(th); @@ -198,60 +203,61 @@ rb_ec_cleanup(rb_execution_context_t *ec, int ex0) SAVE_ROOT_JMPBUF(th, { RUBY_VM_CHECK_INTS(ec); }); step_0: step++; - errs[1] = ec->errinfo; + save_error = ec->errinfo; if (THROW_DATA_P(ec->errinfo)) ec->errinfo = Qnil; - ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]); + ruby_init_stack(&message); + /* exits with failure but silently when an exception raised + * here */ SAVE_ROOT_JMPBUF(th, rb_ec_teardown(ec)); step_1: step++; + VALUE err = ec->errinfo; + volatile int mode0 = 0, mode1 = 0; + if (err != save_error && !NIL_P(err)) { + mode0 = exiting_split(err, &sysex, &signaled); + } + + /* exceptions after here will be ignored */ + + /* build error message including causes */ + err = ATOMIC_VALUE_EXCHANGE(save_error, Qnil); + + if (!NIL_P(err) && !THROW_DATA_P(err)) { + mode1 = exiting_split(err, (mode0 & EXITING_WITH_STATUS) ? NULL : &sysex, &signaled); + if (mode1 & EXITING_WITH_MESSAGE) { + buf = rb_str_new(NULL, 0); + SAVE_ROOT_JMPBUF(th, rb_ec_error_print_detailed(ec, err, buf, Qundef)); + message = buf; + } + } + + step_2: step++; /* protect from Thread#raise */ th->status = THREAD_KILLED; - errs[0] = ec->errinfo; SAVE_ROOT_JMPBUF(th, rb_ractor_terminate_all()); + + step_3: step++; + if (!NIL_P(buf = message)) { + warn_print_str(buf); + } + else if (!NIL_OR_UNDEF_P(err = save_error) || + (ex != TAG_NONE && !((mode0|mode1) & EXITING_WITH_STATUS))) { + sysex = error_handle(ec, err, ex); + } } else { th = th0; switch (step) { case 0: goto step_0; case 1: goto step_1; - } - if (ex == 0) ex = state; - } - ec->errinfo = errs[1]; - sysex = error_handle(ec, ex); - - state = 0; - for (nerr = 0; nerr < numberof(errs); ++nerr) { - VALUE err = ATOMIC_VALUE_EXCHANGE(errs[nerr], Qnil); - VALUE sig; - - if (!RTEST(err)) continue; - - /* ec->errinfo contains a NODE while break'ing */ - if (THROW_DATA_P(err)) continue; - - if (rb_obj_is_kind_of(err, rb_eSystemExit)) { - sysex = sysexit_status(err); - break; - } - else if (rb_obj_is_kind_of(err, rb_eSignal)) { - VALUE sig = rb_ivar_get(err, id_signo); - state = NUM2INT(sig); - break; - } - else if (rb_obj_is_kind_of(err, rb_eSystemCallError) && - FIXNUM_P(sig = rb_attr_get(err, id_signo))) { - state = NUM2INT(sig); - break; - } - else if (sysex == EXIT_SUCCESS) { - sysex = EXIT_FAILURE; + case 2: goto step_2; + case 3: goto step_3; } } - mjit_finish(true); // We still need ISeqs here. + mjit_finish(true); // We still need ISeqs here, so it's before rb_ec_finalize(). rb_ec_finalize(ec); @@ -262,7 +268,10 @@ rb_ec_cleanup(rb_execution_context_t *ec, int ex0) th = th0; rb_thread_stop_timer_thread(); ruby_vm_destruct(th->vm); - if (state) ruby_default_signal(state); + // For YJIT, call this after ruby_vm_destruct() frees jit_cont for the root fiber. + rb_jit_cont_finish(); + + if (signaled) ruby_default_signal(signaled); return sysex; } @@ -314,7 +323,7 @@ ruby_run_node(void *n) rb_execution_context_t *ec = GET_EC(); int status; if (!ruby_executable_node(n, &status)) { - rb_ec_cleanup(ec, 0); + rb_ec_cleanup(ec, (NIL_P(ec->errinfo) ? TAG_NONE : TAG_RAISE)); return status; } ruby_init_stack((void *)&status); @@ -511,7 +520,7 @@ exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) nocause = 0; nocircular = 1; } - if (*cause == Qundef) { + if (UNDEF_P(*cause)) { if (nocause) { *cause = Qnil; nocircular = 1; @@ -527,13 +536,17 @@ exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) rb_raise(rb_eTypeError, "exception object expected"); } - if (!nocircular && !NIL_P(*cause) && *cause != Qundef && *cause != mesg) { + 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; } @@ -546,18 +559,18 @@ setup_exception(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE const char *file = rb_source_location_cstr(&line); const char *const volatile file0 = file; - if ((file && !NIL_P(mesg)) || (cause != Qundef)) { + if ((file && !NIL_P(mesg)) || !UNDEF_P(cause)) { volatile int state = 0; EC_PUSH_TAG(ec); if (EC_EXEC_TAG() == TAG_NONE && !(state = rb_ec_set_raised(ec))) { VALUE bt = rb_get_backtrace(mesg); - if (!NIL_P(bt) || cause == Qundef) { + if (!NIL_P(bt) || UNDEF_P(cause)) { if (OBJ_FROZEN(mesg)) { mesg = rb_obj_dup(mesg); } } - if (cause != Qundef && !THROW_DATA_P(cause)) { + if (!UNDEF_P(cause) && !THROW_DATA_P(cause)) { exc_setup_cause(mesg, cause); } if (NIL_P(bt)) { @@ -630,10 +643,14 @@ setup_exception(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE void rb_ec_setup_exception(const rb_execution_context_t *ec, VALUE mesg, VALUE cause) { - if (cause == Qundef) { + if (UNDEF_P(cause)) { cause = get_ec_errinfo(ec); } if (cause != mesg) { + if (THROW_DATA_P(cause)) { + cause = Qnil; + } + rb_ivar_set(mesg, id_cause, cause); } } @@ -725,7 +742,7 @@ rb_f_raise(int argc, VALUE *argv) argc = extract_raise_opts(argc, argv, opts); if (argc == 0) { - if (*cause != Qundef) { + if (!UNDEF_P(*cause)) { rb_raise(rb_eArgError, "only cause is given with no arguments"); } err = get_errinfo(); @@ -801,7 +818,7 @@ make_exception(int argc, const VALUE *argv, int isstr) if (NIL_P(mesg)) { mesg = rb_check_funcall(argv[0], idException, argc != 1, &argv[1]); } - if (mesg == Qundef) { + if (UNDEF_P(mesg)) { rb_raise(rb_eTypeError, "exception class/object expected"); } if (!rb_obj_is_kind_of(mesg, rb_eException)) { @@ -1760,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 @@ -1772,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 6582b805aa..9806683000 100644 --- a/eval_error.c +++ b/eval_error.c @@ -73,12 +73,6 @@ set_backtrace(VALUE info, VALUE bt) rb_check_funcall(info, set_backtrace, 1, &bt); } -static void -error_print(rb_execution_context_t *ec) -{ - rb_ec_error_print(ec, ec->errinfo); -} - #define CSI_BEGIN "\033[" #define CSI_SGR "m" @@ -297,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; @@ -306,7 +311,7 @@ rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VA if (NIL_P(errinfo)) return; - if (errat == Qundef) { + if (UNDEF_P(errat)) { errat = Qnil; } eclass = CLASS_OF(errinfo); @@ -338,13 +343,13 @@ rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VA } } -void -rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) +static void +rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo, const VALUE str, VALUE emesg0) { volatile uint8_t raised_flag = ec->raised_flag; volatile VALUE errat = Qundef; - volatile VALUE emesg = Qundef; volatile bool written = false; + volatile VALUE emesg = emesg0; VALUE opt = rb_hash_new(); VALUE highlight = rb_stderr_tty_p() ? Qtrue : Qfalse; @@ -358,14 +363,14 @@ rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) if (EC_EXEC_TAG() == TAG_NONE) { errat = rb_get_backtrace(errinfo); } - if (emesg == Qundef) { + if (UNDEF_P(emesg)) { emesg = Qnil; emesg = rb_get_detailed_message(errinfo, opt); } if (!written) { written = true; - rb_error_write(errinfo, emesg, errat, Qnil, opt, highlight, Qfalse); + rb_error_write(errinfo, emesg, errat, str, opt, highlight, Qfalse); } EC_POP_TAG(); @@ -373,6 +378,12 @@ rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) rb_ec_raised_set(ec, raised_flag); } +void +rb_ec_error_print(rb_execution_context_t *volatile ec, volatile VALUE errinfo) +{ + rb_ec_error_print_detailed(ec, errinfo, Qnil, Qundef); +} + #define undef_mesg_for(v, k) rb_fstring_lit("undefined"v" method `%1$s' for "k" `%2$s'") #define undef_mesg(v) ( \ is_mod ? \ @@ -429,11 +440,63 @@ sysexit_status(VALUE err) return NUM2INT(st); } +enum { + EXITING_WITH_MESSAGE = 1, + EXITING_WITH_STATUS = 2, + EXITING_WITH_SIGNAL = 4 +}; +static int +exiting_split(VALUE errinfo, volatile int *exitcode, volatile int *sigstatus) +{ + int ex = EXIT_SUCCESS; + VALUE signo; + int sig = 0; + int result = 0; + + if (NIL_P(errinfo)) return 0; + + 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; + } + else if (rb_obj_is_kind_of(errinfo, rb_eSignal)) { + signo = rb_ivar_get(errinfo, id_signo); + sig = FIX2INT(signo); + result |= EXITING_WITH_SIGNAL; + /* no message when exiting by signal */ + if (signo == INT2FIX(SIGSEGV) || !rb_obj_is_instance_of(errinfo, rb_eSignal)) + /* except for SEGV and subclasses */ + result |= EXITING_WITH_MESSAGE; + } + else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) && + FIXNUM_P(signo = rb_attr_get(errinfo, id_signo))) { + sig = FIX2INT(signo); + result |= EXITING_WITH_SIGNAL; + /* no message when exiting by error to be mapped to signal */ + } + else { + ex = EXIT_FAILURE; + result |= EXITING_WITH_STATUS | EXITING_WITH_MESSAGE; + } + + if (exitcode && (result & EXITING_WITH_STATUS)) + *exitcode = ex; + if (sigstatus && (result & EXITING_WITH_SIGNAL)) + *sigstatus = sig; + + return result; +} + #define unknown_longjmp_status(status) \ rb_bug("Unknown longjmp status %d", status) static int -error_handle(rb_execution_context_t *ec, int ex) +error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) { int status = EXIT_FAILURE; @@ -469,26 +532,13 @@ error_handle(rb_execution_context_t *ec, int ex) error_pos(Qnil); warn_print("unexpected throw\n"); break; - case TAG_RAISE: { - VALUE errinfo = ec->errinfo; - if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { - status = sysexit_status(errinfo); - } - else if (rb_obj_is_instance_of(errinfo, rb_eSignal) && - rb_ivar_get(errinfo, id_signo) != INT2FIX(SIGSEGV)) { - /* no message when exiting by signal */ + case TAG_RAISE: + if (!(exiting_split(errinfo, &status, NULL) & EXITING_WITH_MESSAGE)) { + break; } - else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) && - FIXNUM_P(rb_attr_get(errinfo, id_signo))) { - /* no message when exiting by error to be mapped to signal */ - } - else { - rb_ec_error_print(ec, errinfo); - } - break; - } + /* fallthrough */ case TAG_FATAL: - error_print(ec); + rb_ec_error_print(ec, errinfo); break; default: unknown_longjmp_status(ex); diff --git a/eval_jump.c b/eval_jump.c index a6139bc27b..e8e74f4e70 100644 --- a/eval_jump.c +++ b/eval_jump.c @@ -121,7 +121,7 @@ rb_ec_exec_end_proc(rb_execution_context_t * ec) } else { EC_TMPPOP_TAG(); - error_handle(ec, state); + error_handle(ec, ec->errinfo, state); if (!NIL_P(ec->errinfo)) errinfo = ec->errinfo; EC_REPUSH_TAG(); goto again; diff --git a/ext/-test-/marshal/internal_ivar/internal_ivar.c b/ext/-test-/marshal/internal_ivar/internal_ivar.c index de0cf711aa..b2188f737a 100644 --- a/ext/-test-/marshal/internal_ivar/internal_ivar.c +++ b/ext/-test-/marshal/internal_ivar/internal_ivar.c @@ -36,10 +36,7 @@ Init_internal_ivar(void) VALUE newclass = rb_define_class_under(mMarshal, "InternalIVar", rb_cObject); id_normal_ivar = rb_intern_const("normal"); -#if 0 - /* leave id_internal_ivar being 0 */ - id_internal_ivar = rb_make_internal_id(); -#endif + id_internal_ivar = rb_intern_const("K"); id_encoding_short = rb_intern_const("E"); rb_define_method(newclass, "initialize", init, 3); rb_define_method(newclass, "normal", get_normal, 0); diff --git a/ext/-test-/num2int/num2int.c b/ext/-test-/num2int/num2int.c index 3aec3ccf3b..63a441fda6 100644 --- a/ext/-test-/num2int/num2int.c +++ b/ext/-test-/num2int/num2int.c @@ -4,7 +4,7 @@ static VALUE test_num2short(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%d", NUM2SHORT(num)); + snprintf(buf, sizeof(buf), "%d", NUM2SHORT(num)); return rb_str_new_cstr(buf); } @@ -12,7 +12,7 @@ static VALUE test_num2ushort(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%u", NUM2USHORT(num)); + snprintf(buf, sizeof(buf), "%u", NUM2USHORT(num)); return rb_str_new_cstr(buf); } @@ -20,7 +20,7 @@ static VALUE test_num2int(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%d", NUM2INT(num)); + snprintf(buf, sizeof(buf), "%d", NUM2INT(num)); return rb_str_new_cstr(buf); } @@ -28,7 +28,7 @@ static VALUE test_num2uint(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%u", NUM2UINT(num)); + snprintf(buf, sizeof(buf), "%u", NUM2UINT(num)); return rb_str_new_cstr(buf); } @@ -36,7 +36,7 @@ static VALUE test_num2long(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%ld", NUM2LONG(num)); + snprintf(buf, sizeof(buf), "%ld", NUM2LONG(num)); return rb_str_new_cstr(buf); } @@ -44,7 +44,7 @@ static VALUE test_num2ulong(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%lu", NUM2ULONG(num)); + snprintf(buf, sizeof(buf), "%lu", NUM2ULONG(num)); return rb_str_new_cstr(buf); } @@ -53,7 +53,7 @@ static VALUE test_num2ll(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%"PRI_LL_PREFIX"d", NUM2LL(num)); + snprintf(buf, sizeof(buf), "%"PRI_LL_PREFIX"d", NUM2LL(num)); return rb_str_new_cstr(buf); } @@ -61,7 +61,7 @@ static VALUE test_num2ull(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%"PRI_LL_PREFIX"u", NUM2ULL(num)); + snprintf(buf, sizeof(buf), "%"PRI_LL_PREFIX"u", NUM2ULL(num)); return rb_str_new_cstr(buf); } #endif @@ -70,7 +70,7 @@ static VALUE test_fix2short(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%d", FIX2SHORT(num)); + snprintf(buf, sizeof(buf), "%d", FIX2SHORT(num)); return rb_str_new_cstr(buf); } @@ -78,7 +78,7 @@ static VALUE test_fix2int(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%d", FIX2INT(num)); + snprintf(buf, sizeof(buf), "%d", FIX2INT(num)); return rb_str_new_cstr(buf); } @@ -86,7 +86,7 @@ static VALUE test_fix2uint(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%u", FIX2UINT(num)); + snprintf(buf, sizeof(buf), "%u", FIX2UINT(num)); return rb_str_new_cstr(buf); } @@ -94,7 +94,7 @@ static VALUE test_fix2long(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%ld", FIX2LONG(num)); + snprintf(buf, sizeof(buf), "%ld", FIX2LONG(num)); return rb_str_new_cstr(buf); } @@ -102,7 +102,7 @@ static VALUE test_fix2ulong(VALUE obj, VALUE num) { char buf[128]; - sprintf(buf, "%lu", FIX2ULONG(num)); + snprintf(buf, sizeof(buf), "%lu", FIX2ULONG(num)); return rb_str_new_cstr(buf); } diff --git a/ext/-test-/random/bad_version.c b/ext/-test-/random/bad_version.c new file mode 100644 index 0000000000..dae63a6d19 --- /dev/null +++ b/ext/-test-/random/bad_version.c @@ -0,0 +1,135 @@ +#include "ruby/random.h" + +#if RUBY_RANDOM_INTERFACE_VERSION_MAJOR < RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX +# define DEFINE_VERSION_MAX 1 +#else +# define DEFINE_VERSION_MAX 0 +#endif + +NORETURN(static void must_not_reach(void)); +static void +must_not_reach(void) +{ + rb_raise(rb_eTypeError, "must not reach"); +} + +NORETURN(static void bad_version_init(rb_random_t *, const uint32_t *, size_t)); +static void +bad_version_init(rb_random_t *rnd, const uint32_t *buf, size_t len) +{ + must_not_reach(); +} + +NORETURN(static void bad_version_init_int32(rb_random_t *, uint32_t)); +RB_RANDOM_DEFINE_INIT_INT32_FUNC(bad_version) + +NORETURN(static void bad_version_get_bytes(rb_random_t *, void *, size_t)); +static void +bad_version_get_bytes(rb_random_t *rnd, void *p, size_t n) +{ + must_not_reach(); +} + +NORETURN(static uint32_t bad_version_get_int32(rb_random_t *)); +static uint32_t +bad_version_get_int32(rb_random_t *rnd) +{ + must_not_reach(); + UNREACHABLE_RETURN(0); +} + +static VALUE +bad_version_alloc(VALUE klass, const rb_data_type_t *type) +{ + rb_random_t *rnd; + VALUE obj = TypedData_Make_Struct(klass, rb_random_t, type, rnd); + rb_random_base_init(rnd); + return obj; +} + +/* version 0 */ +static const rb_random_interface_t random_version_zero_if; + +static rb_random_data_type_t version_zero_type = { + "random/version_zero", + { + rb_random_mark, + RUBY_TYPED_DEFAULT_FREE, + }, + RB_RANDOM_PARENT, + (void *)&random_version_zero_if, + RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE +version_zero_alloc(VALUE klass) +{ + return bad_version_alloc(klass, &version_zero_type); +} + +static void +init_version_zero(VALUE mod, VALUE base) +{ + VALUE c = rb_define_class_under(mod, "VersionZero", base); + rb_define_alloc_func(c, version_zero_alloc); + RB_RANDOM_DATA_INIT_PARENT(version_zero_type); +} + +#if DEFINE_VERSION_MAX +/* version max */ +static const rb_random_interface_t random_version_max_if; +static rb_random_data_type_t version_max_type = { + "random/version_max", + { + rb_random_mark, + RUBY_TYPED_DEFAULT_FREE, + }, + RB_RANDOM_PARENT, + (void *)&random_version_max_if, + RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE +version_max_alloc(VALUE klass) +{ + return bad_version_alloc(klass, &version_max_type); +} + +static void +init_version_max(VALUE mod, VALUE base) +{ + VALUE c = rb_define_class_under(mod, "VersionMax", base); + rb_define_alloc_func(c, version_max_alloc); + RB_RANDOM_DATA_INIT_PARENT(version_max_type); +} +#else +static void +init_version_max(mod, base) +{ +} +#endif + +void +Init_random_bad_version(VALUE mod, VALUE base) +{ + init_version_zero(mod, base); + init_version_max(mod, base); +} + +#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR + +#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR 0 +static const rb_random_interface_t random_version_zero_if = { + 0, + RB_RANDOM_INTERFACE_DEFINE(bad_version) +}; +#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR + +#if DEFINE_VERSION_MAX +#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX +static const rb_random_interface_t random_version_max_if = { + 0, + RB_RANDOM_INTERFACE_DEFINE(bad_version) +}; +#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR +#endif diff --git a/ext/-test-/random/depend b/ext/-test-/random/depend index 602526cf7b..f2cbf7fc14 100644 --- a/ext/-test-/random/depend +++ b/ext/-test-/random/depend @@ -1,4 +1,164 @@ # AUTOGENERATED DEPENDENCIES START +bad_version.o: $(RUBY_EXTCONF_H) +bad_version.o: $(arch_hdrdir)/ruby/config.h +bad_version.o: $(hdrdir)/ruby/assert.h +bad_version.o: $(hdrdir)/ruby/backward.h +bad_version.o: $(hdrdir)/ruby/backward/2/assume.h +bad_version.o: $(hdrdir)/ruby/backward/2/attributes.h +bad_version.o: $(hdrdir)/ruby/backward/2/bool.h +bad_version.o: $(hdrdir)/ruby/backward/2/inttypes.h +bad_version.o: $(hdrdir)/ruby/backward/2/limits.h +bad_version.o: $(hdrdir)/ruby/backward/2/long_long.h +bad_version.o: $(hdrdir)/ruby/backward/2/stdalign.h +bad_version.o: $(hdrdir)/ruby/backward/2/stdarg.h +bad_version.o: $(hdrdir)/ruby/defines.h +bad_version.o: $(hdrdir)/ruby/intern.h +bad_version.o: $(hdrdir)/ruby/internal/abi.h +bad_version.o: $(hdrdir)/ruby/internal/anyargs.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/char.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/double.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/int.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/long.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/short.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +bad_version.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +bad_version.o: $(hdrdir)/ruby/internal/assume.h +bad_version.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +bad_version.o: $(hdrdir)/ruby/internal/attr/artificial.h +bad_version.o: $(hdrdir)/ruby/internal/attr/cold.h +bad_version.o: $(hdrdir)/ruby/internal/attr/const.h +bad_version.o: $(hdrdir)/ruby/internal/attr/constexpr.h +bad_version.o: $(hdrdir)/ruby/internal/attr/deprecated.h +bad_version.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +bad_version.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +bad_version.o: $(hdrdir)/ruby/internal/attr/error.h +bad_version.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +bad_version.o: $(hdrdir)/ruby/internal/attr/forceinline.h +bad_version.o: $(hdrdir)/ruby/internal/attr/format.h +bad_version.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +bad_version.o: $(hdrdir)/ruby/internal/attr/noalias.h +bad_version.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +bad_version.o: $(hdrdir)/ruby/internal/attr/noexcept.h +bad_version.o: $(hdrdir)/ruby/internal/attr/noinline.h +bad_version.o: $(hdrdir)/ruby/internal/attr/nonnull.h +bad_version.o: $(hdrdir)/ruby/internal/attr/noreturn.h +bad_version.o: $(hdrdir)/ruby/internal/attr/pure.h +bad_version.o: $(hdrdir)/ruby/internal/attr/restrict.h +bad_version.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +bad_version.o: $(hdrdir)/ruby/internal/attr/warning.h +bad_version.o: $(hdrdir)/ruby/internal/attr/weakref.h +bad_version.o: $(hdrdir)/ruby/internal/cast.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +bad_version.o: $(hdrdir)/ruby/internal/compiler_since.h +bad_version.o: $(hdrdir)/ruby/internal/config.h +bad_version.o: $(hdrdir)/ruby/internal/constant_p.h +bad_version.o: $(hdrdir)/ruby/internal/core.h +bad_version.o: $(hdrdir)/ruby/internal/core/rarray.h +bad_version.o: $(hdrdir)/ruby/internal/core/rbasic.h +bad_version.o: $(hdrdir)/ruby/internal/core/rbignum.h +bad_version.o: $(hdrdir)/ruby/internal/core/rclass.h +bad_version.o: $(hdrdir)/ruby/internal/core/rdata.h +bad_version.o: $(hdrdir)/ruby/internal/core/rfile.h +bad_version.o: $(hdrdir)/ruby/internal/core/rhash.h +bad_version.o: $(hdrdir)/ruby/internal/core/robject.h +bad_version.o: $(hdrdir)/ruby/internal/core/rregexp.h +bad_version.o: $(hdrdir)/ruby/internal/core/rstring.h +bad_version.o: $(hdrdir)/ruby/internal/core/rstruct.h +bad_version.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +bad_version.o: $(hdrdir)/ruby/internal/ctype.h +bad_version.o: $(hdrdir)/ruby/internal/dllexport.h +bad_version.o: $(hdrdir)/ruby/internal/dosish.h +bad_version.o: $(hdrdir)/ruby/internal/error.h +bad_version.o: $(hdrdir)/ruby/internal/eval.h +bad_version.o: $(hdrdir)/ruby/internal/event.h +bad_version.o: $(hdrdir)/ruby/internal/fl_type.h +bad_version.o: $(hdrdir)/ruby/internal/gc.h +bad_version.o: $(hdrdir)/ruby/internal/glob.h +bad_version.o: $(hdrdir)/ruby/internal/globals.h +bad_version.o: $(hdrdir)/ruby/internal/has/attribute.h +bad_version.o: $(hdrdir)/ruby/internal/has/builtin.h +bad_version.o: $(hdrdir)/ruby/internal/has/c_attribute.h +bad_version.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +bad_version.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +bad_version.o: $(hdrdir)/ruby/internal/has/extension.h +bad_version.o: $(hdrdir)/ruby/internal/has/feature.h +bad_version.o: $(hdrdir)/ruby/internal/has/warning.h +bad_version.o: $(hdrdir)/ruby/internal/intern/array.h +bad_version.o: $(hdrdir)/ruby/internal/intern/bignum.h +bad_version.o: $(hdrdir)/ruby/internal/intern/class.h +bad_version.o: $(hdrdir)/ruby/internal/intern/compar.h +bad_version.o: $(hdrdir)/ruby/internal/intern/complex.h +bad_version.o: $(hdrdir)/ruby/internal/intern/cont.h +bad_version.o: $(hdrdir)/ruby/internal/intern/dir.h +bad_version.o: $(hdrdir)/ruby/internal/intern/enum.h +bad_version.o: $(hdrdir)/ruby/internal/intern/enumerator.h +bad_version.o: $(hdrdir)/ruby/internal/intern/error.h +bad_version.o: $(hdrdir)/ruby/internal/intern/eval.h +bad_version.o: $(hdrdir)/ruby/internal/intern/file.h +bad_version.o: $(hdrdir)/ruby/internal/intern/gc.h +bad_version.o: $(hdrdir)/ruby/internal/intern/hash.h +bad_version.o: $(hdrdir)/ruby/internal/intern/io.h +bad_version.o: $(hdrdir)/ruby/internal/intern/load.h +bad_version.o: $(hdrdir)/ruby/internal/intern/marshal.h +bad_version.o: $(hdrdir)/ruby/internal/intern/numeric.h +bad_version.o: $(hdrdir)/ruby/internal/intern/object.h +bad_version.o: $(hdrdir)/ruby/internal/intern/parse.h +bad_version.o: $(hdrdir)/ruby/internal/intern/proc.h +bad_version.o: $(hdrdir)/ruby/internal/intern/process.h +bad_version.o: $(hdrdir)/ruby/internal/intern/random.h +bad_version.o: $(hdrdir)/ruby/internal/intern/range.h +bad_version.o: $(hdrdir)/ruby/internal/intern/rational.h +bad_version.o: $(hdrdir)/ruby/internal/intern/re.h +bad_version.o: $(hdrdir)/ruby/internal/intern/ruby.h +bad_version.o: $(hdrdir)/ruby/internal/intern/select.h +bad_version.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bad_version.o: $(hdrdir)/ruby/internal/intern/signal.h +bad_version.o: $(hdrdir)/ruby/internal/intern/sprintf.h +bad_version.o: $(hdrdir)/ruby/internal/intern/string.h +bad_version.o: $(hdrdir)/ruby/internal/intern/struct.h +bad_version.o: $(hdrdir)/ruby/internal/intern/thread.h +bad_version.o: $(hdrdir)/ruby/internal/intern/time.h +bad_version.o: $(hdrdir)/ruby/internal/intern/variable.h +bad_version.o: $(hdrdir)/ruby/internal/intern/vm.h +bad_version.o: $(hdrdir)/ruby/internal/interpreter.h +bad_version.o: $(hdrdir)/ruby/internal/iterator.h +bad_version.o: $(hdrdir)/ruby/internal/memory.h +bad_version.o: $(hdrdir)/ruby/internal/method.h +bad_version.o: $(hdrdir)/ruby/internal/module.h +bad_version.o: $(hdrdir)/ruby/internal/newobj.h +bad_version.o: $(hdrdir)/ruby/internal/rgengc.h +bad_version.o: $(hdrdir)/ruby/internal/scan_args.h +bad_version.o: $(hdrdir)/ruby/internal/special_consts.h +bad_version.o: $(hdrdir)/ruby/internal/static_assert.h +bad_version.o: $(hdrdir)/ruby/internal/stdalign.h +bad_version.o: $(hdrdir)/ruby/internal/stdbool.h +bad_version.o: $(hdrdir)/ruby/internal/symbol.h +bad_version.o: $(hdrdir)/ruby/internal/value.h +bad_version.o: $(hdrdir)/ruby/internal/value_type.h +bad_version.o: $(hdrdir)/ruby/internal/variable.h +bad_version.o: $(hdrdir)/ruby/internal/warning_push.h +bad_version.o: $(hdrdir)/ruby/internal/xmalloc.h +bad_version.o: $(hdrdir)/ruby/missing.h +bad_version.o: $(hdrdir)/ruby/random.h +bad_version.o: $(hdrdir)/ruby/ruby.h +bad_version.o: $(hdrdir)/ruby/st.h +bad_version.o: $(hdrdir)/ruby/subst.h +bad_version.o: bad_version.c init.o: $(RUBY_EXTCONF_H) init.o: $(arch_hdrdir)/ruby/config.h init.o: $(hdrdir)/ruby.h diff --git a/ext/-test-/random/loop.c b/ext/-test-/random/loop.c index 0572096403..b789ab1d01 100644 --- a/ext/-test-/random/loop.c +++ b/ext/-test-/random/loop.c @@ -13,6 +13,7 @@ static const rb_random_interface_t random_loop_if = { RB_RANDOM_INTERFACE_DEFINE_WITH_REAL(loop) }; +RB_RANDOM_DEFINE_INIT_INT32_FUNC(loop) static size_t random_loop_memsize(const void *ptr) { diff --git a/ext/-test-/rational/depend b/ext/-test-/rational/depend index 8729695886..ce977821b8 100644 --- a/ext/-test-/rational/depend +++ b/ext/-test-/rational/depend @@ -174,5 +174,6 @@ rat.o: $(top_srcdir)/internal/static_assert.h rat.o: $(top_srcdir)/internal/vm.h rat.o: $(top_srcdir)/internal/warnings.h rat.o: $(top_srcdir)/ruby_assert.h +rat.o: $(top_srcdir)/shape.h rat.o: rat.c # AUTOGENERATED DEPENDENCIES END 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/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1483f327a6..d6ea35c615 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -7,9 +7,7 @@ */ /* #define BIGDECIMAL_DEBUG 1 */ -#ifdef BIGDECIMAL_DEBUG -# define BIGDECIMAL_ENABLE_VPRINT 1 -#endif + #include "bigdecimal.h" #include "ruby/util.h" @@ -61,6 +59,13 @@ static ID id_to_r; static ID id_eq; static ID id_half; +#define RBD_NUM_ROUNDING_MODES 11 + +static struct { + ID id; + uint8_t mode; +} rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; + /* MACRO's to guard objects from GC by keeping them in stack */ #ifdef RBIMPL_ATTR_MAYBE_UNUSED #define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 @@ -102,10 +107,164 @@ static ID id_half; # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) /* + * ================== Memory allocation ============================ + */ + +#ifdef BIGDECIMAL_DEBUG +static size_t rbd_allocation_count = 0; /* Memory allocation counter */ +static inline void +atomic_allocation_count_inc(void) +{ + RUBY_ATOMIC_SIZE_INC(rbd_allocation_count); +} +static inline void +atomic_allocation_count_dec_nounderflow(void) +{ + if (rbd_allocation_count == 0) return; + RUBY_ATOMIC_SIZE_DEC(rbd_allocation_count); +} +static void +check_allocation_count_nonzero(void) +{ + if (rbd_allocation_count != 0) return; + rb_bug("[bigdecimal][rbd_free_struct] Too many memory free calls"); +} +#else +# define atomic_allocation_count_inc() /* nothing */ +# define atomic_allocation_count_dec_nounderflow() /* nothing */ +# define check_allocation_count_nonzero() /* nothing */ +#endif /* BIGDECIMAL_DEBUG */ + +PUREFUNC(static inline size_t rbd_struct_size(size_t const)); + +static inline size_t +rbd_struct_size(size_t const internal_digits) +{ + size_t const frac_len = (internal_digits == 0) ? 1 : internal_digits; + return offsetof(Real, frac) + frac_len * sizeof(DECDIG); +} + +static inline Real * +rbd_allocate_struct(size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + Real *real = ruby_xcalloc(1, size); + atomic_allocation_count_inc(); + real->MaxPrec = internal_digits; + return real; +} + +static size_t +rbd_calculate_internal_digits(size_t const digits, bool limit_precision) +{ + size_t const len = roomof(digits, BASE_FIG); + if (limit_precision) { + size_t const prec_limit = VpGetPrecLimit(); + if (prec_limit > 0) { + /* NOTE: 2 more digits for rounding and division */ + size_t const max_len = roomof(prec_limit, BASE_FIG) + 2; + if (len > max_len) + return max_len; + } + } + + return len; +} + +static inline Real * +rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_precision) +{ + size_t const internal_digits = rbd_calculate_internal_digits(decimal_digits, limit_precision); + return rbd_allocate_struct(internal_digits); +} + +static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); + +static Real * +rbd_reallocate_struct(Real *real, size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + VALUE obj = real ? real->obj : 0; + Real *new_real = (Real *)ruby_xrealloc(real, size); + new_real->MaxPrec = internal_digits; + if (obj) { + new_real->obj = 0; + BigDecimal_wrap_struct(obj, new_real); + } + return new_real; +} + +static void +rbd_free_struct(Real *real) +{ + if (real != NULL) { + check_allocation_count_nonzero(); + ruby_xfree(real); + atomic_allocation_count_dec_nounderflow(); + } +} + +#define NewZero rbd_allocate_struct_zero +static Real * +rbd_allocate_struct_zero(int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + VpSetZero(real, sign); + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited(int sign, size_t const digits)); +#define NewZeroLimited rbd_allocate_struct_zero_limited +static inline Real * +rbd_allocate_struct_zero_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit(int sign, size_t const digits)); +#define NewZeroNolimit rbd_allocate_struct_zero_nolimit +static inline Real * +rbd_allocate_struct_zero_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, false); +} + +#define NewOne rbd_allocate_struct_one +static Real * +rbd_allocate_struct_one(int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + VpSetOne(real); + if (sign < 0) + VpSetSign(real, VP_SIGN_NEGATIVE_FINITE); + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited(int sign, size_t const digits)); +#define NewOneLimited rbd_allocate_struct_one_limited +static inline Real * +rbd_allocate_struct_one_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); +#define NewOneNolimit rbd_allocate_struct_one_nolimit +static inline Real * +rbd_allocate_struct_one_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, false); +} + +/* * ================== Ruby Interface part ========================== */ #define DoSomeOne(x,y,f) rb_num_coerce_bin(x,y,f) @@ -120,10 +279,7 @@ static VALUE VpCheckGetValue(Real *p); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); - -#ifdef BIGDECIMAL_ENABLE_VPRINT static int VPrint(FILE *fp,const char *cntl_chr,Real *a); -#endif /* * **** BigDecimal part **** @@ -138,7 +294,7 @@ static VALUE BigDecimal_negative_zero(void); static void BigDecimal_delete(void *pv) { - VpFree(pv); + rbd_free_struct(pv); } static size_t @@ -161,6 +317,60 @@ static const rb_data_type_t BigDecimal_data_type = { #endif }; +static Real * +rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_zero(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); +#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap +static inline Real * +rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); +#define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap +static inline Real * +rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + +static Real * +rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_one(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); +#define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap +static inline Real * +rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); +#define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap +static inline Real * +rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + static inline int is_kind_of_BigDecimal(VALUE const v) { @@ -214,7 +424,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) case T_FIXNUM: { char szD[128]; - sprintf(szD, "%ld", FIX2LONG(v)); + snprintf(szD, 128, "%ld", FIX2LONG(v)); v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); break; } @@ -249,7 +459,7 @@ SomeOneMayDoIt: return NULL; /* NULL means to coerce */ } -static Real* +static inline Real* GetVpValue(VALUE v, int must) { return GetVpValueWithPrec(v, -1, must); @@ -264,7 +474,7 @@ GetVpValue(VALUE v, int must) * BigDecimal.double_fig # => 16 * */ -static VALUE +static inline VALUE BigDecimal_double_fig(VALUE self) { return INT2FIX(VpDblFig()); @@ -486,15 +696,15 @@ BigDecimal_precision_scale(VALUE self) * * Returns the number of decimal significant digits in +self+. * - * BigDecimal("0").scale # => 0 - * BigDecimal("1").scale # => 1 - * BigDecimal("1.1").scale # => 2 - * BigDecimal("3.1415").scale # => 5 - * BigDecimal("-1e20").precision # => 1 - * BigDecimal("1e-20").precision # => 1 - * BigDecimal("Infinity").scale # => 0 - * BigDecimal("-Infinity").scale # => 0 - * BigDecimal("NaN").scale # => 0 + * BigDecimal("0").n_significant_digits # => 0 + * BigDecimal("1").n_significant_digits # => 1 + * BigDecimal("1.1").n_significant_digits # => 2 + * BigDecimal("3.1415").n_significant_digits # => 5 + * BigDecimal("-1e20").n_significant_digits # => 1 + * BigDecimal("1e-20").n_significant_digits # => 1 + * BigDecimal("Infinity").n_significant_digits # => 0 + * BigDecimal("-Infinity").n_significant_digits # => 0 + * BigDecimal("NaN").n_significant_digits # => 0 */ static VALUE BigDecimal_n_significant_digits(VALUE self) @@ -573,13 +783,15 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) char *psz; VALUE dummy; volatile VALUE dump; + size_t len; rb_scan_args(argc, argv, "01", &dummy); GUARD_OBJ(vp,GetVpValue(self, 1)); dump = rb_str_new(0, VpNumOfChars(vp, "E")+50); psz = RSTRING_PTR(dump); - sprintf(psz, "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); - VpToString(vp, psz+strlen(psz), 0, 0); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); + len = strlen(psz); + VpToString(vp, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); return dump; } @@ -623,18 +835,19 @@ check_rounding_mode_option(VALUE const opts) assert(RB_TYPE_P(opts, T_HASH)); if (NIL_P(opts)) - goto noopt; + goto no_opt; mode = rb_hash_lookup2(opts, ID2SYM(id_half), Qundef); if (mode == Qundef || NIL_P(mode)) - goto noopt; + goto no_opt; if (SYMBOL_P(mode)) mode = rb_sym2str(mode); else if (!RB_TYPE_P(mode, T_STRING)) { - VALUE str_mode = rb_check_string_type(mode); - if (NIL_P(str_mode)) goto invalid; - mode = str_mode; + VALUE str_mode = rb_check_string_type(mode); + if (NIL_P(str_mode)) + goto invalid; + mode = str_mode; } s = RSTRING_PTR(mode); l = RSTRING_LEN(mode); @@ -652,13 +865,11 @@ check_rounding_mode_option(VALUE const opts) default: break; } + invalid: - if (NIL_P(mode)) - rb_raise(rb_eArgError, "invalid rounding mode: nil"); - else - rb_raise(rb_eArgError, "invalid rounding mode: %"PRIsVALUE, mode); + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", mode); - noopt: + no_opt: return VpGetRoundMode(); } @@ -667,34 +878,23 @@ check_rounding_mode(VALUE const v) { unsigned short sw; ID id; - switch (TYPE(v)) { - case T_SYMBOL: - id = SYM2ID(v); - if (id == id_up) - return VP_ROUND_UP; - if (id == id_down || id == id_truncate) - return VP_ROUND_DOWN; - if (id == id_half_up || id == id_default) - return VP_ROUND_HALF_UP; - if (id == id_half_down) - return VP_ROUND_HALF_DOWN; - if (id == id_half_even || id == id_banker) - return VP_ROUND_HALF_EVEN; - if (id == id_ceiling || id == id_ceil) - return VP_ROUND_CEIL; - if (id == id_floor) - return VP_ROUND_FLOOR; - rb_raise(rb_eArgError, "invalid rounding mode"); - - default: - break; + if (RB_TYPE_P(v, T_SYMBOL)) { + int i; + id = SYM2ID(v); + for (i = 0; i < RBD_NUM_ROUNDING_MODES; ++i) { + if (rbd_rounding_modes[i].id == id) { + return rbd_rounding_modes[i].mode; + } + } + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); } - - sw = NUM2USHORT(v); - if (!VpIsRoundMode(sw)) { - rb_raise(rb_eArgError, "invalid rounding mode"); + else { + sw = NUM2USHORT(v); + if (!VpIsRoundMode(sw)) { + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); + } + return sw; } - return sw; } /* call-seq: @@ -933,11 +1133,17 @@ GetAddSubPrec(Real *a, Real *b) return mx; } -static SIGNED_VALUE -GetPrecisionInt(VALUE v) +static inline SIGNED_VALUE +check_int_precision(VALUE v) { SIGNED_VALUE n; - n = NUM2INT(v); +#if SIZEOF_VALUE <= SIZEOF_LONG + n = (SIGNED_VALUE)NUM2LONG(v); +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG + n = (SIGNED_VALUE)NUM2LL(v); +#else +# error SIZEOF_VALUE is too large +#endif if (n < 0) { rb_raise(rb_eArgError, "negative precision"); } @@ -979,26 +1185,12 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } -#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) - -static Real * -VpReallocReal(Real *pv, size_t prec) -{ - VALUE obj = pv ? pv->obj : 0; - Real *new_pv = (Real *)VpMemRealloc(pv, offsetof(Real, frac) + prec * sizeof(DECDIG)); - if (obj) { - new_pv->obj = 0; - BigDecimal_wrap_struct(obj, new_pv); - } - return new_pv; -} - static Real * VpCopy(Real *pv, Real const* const x) { assert(x != NULL); - pv = VpReallocReal(pv, x->MaxPrec); + pv = rbd_reallocate_struct(pv, x->MaxPrec); pv->MaxPrec = x->MaxPrec; pv->Prec = x->Prec; pv->exponent = x->exponent; @@ -1119,7 +1311,7 @@ BigDecimal_to_f(VALUE self) str = rb_str_new(0, VpNumOfChars(p, "E")); buf = RSTRING_PTR(str); - VpToString(p, buf, 0, 0); + VpToString(p, buf, RSTRING_LEN(str), 0, 0); errno = 0; d = strtod(buf, 0); if (errno == ERANGE) { @@ -1276,17 +1468,17 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a, b); if (mx == (size_t)-1L) { - GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); - VpAddSub(c, a, b, 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, 1); } else { - GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0", true)); - if(!mx) { - VpSetInf(c, VpGetSign(a)); - } - else { - VpAddSub(c, a, b, 1); - } + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c, VpGetSign(a)); + } + else { + VpAddSub(c, a, b, 1); + } } return VpCheckGetValue(c); } @@ -1331,17 +1523,17 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a,b); if (mx == (size_t)-1L) { - GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); - VpAddSub(c, a, b, -1); + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, -1); } else { - GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); - if (!mx) { - VpSetInf(c,VpGetSign(a)); - } - else { - VpAddSub(c, a, b, -1); - } + GUARD_OBJ(c, NewZeroWrapLimited(1, mx *(VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c,VpGetSign(a)); + } + else { + VpAddSub(c, a, b, -1); + } } return VpCheckGetValue(c); } @@ -1581,7 +1773,7 @@ BigDecimal_neg(VALUE self) ENTER(5); Real *c, *a; GUARD_OBJ(a, GetVpValue(self, 1)); - GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, a->Prec *(VpBaseFig() + 1))); VpAsgn(c, a, -1); return VpCheckGetValue(c); } @@ -1608,7 +1800,7 @@ BigDecimal_mult(VALUE self, VALUE r) SAVE(b); mx = a->Prec + b->Prec; - GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); VpMult(c, a, b); return VpCheckGetValue(c); } @@ -1655,8 +1847,8 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; - GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx + 1)*2 + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*c), NewZeroWrapNolimit(1, mx + 2*BASE_FIG)); + GUARD_OBJ((*res), NewZeroWrapNolimit(1, (mx + 1)*2 + 2*BASE_FIG)); VpDivd(*c, *res, a, b); return Qnil; @@ -1721,7 +1913,7 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) argc = rb_scan_args(argc, argv, "11", &value, &digits); if (argc > 1) { - n = GetPrecisionInt(digits); + n = check_int_precision(digits); } if (n > 0) { @@ -1811,12 +2003,12 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; - GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); - GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG)); VpDivd(c, res, a, b); mx = c->Prec * BASE_FIG; - GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); VpMult(res, d, b); @@ -1824,10 +2016,10 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { /* result adjustment for negative case */ - res = VpReallocReal(res, d->MaxPrec); + res = rbd_reallocate_struct(res, d->MaxPrec); res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, GetAddSubPrec(c, b) * 2*BASE_FIG)); VpAddSub(d, c, b, 1); *div = res; *mod = d; @@ -1891,17 +2083,17 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) SAVE(b); mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); - GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); - GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(rr, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(ff, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); VpDivd(c, res, a, b); mx = c->Prec *(VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(f, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(f, NewZeroWrapLimited(1, mx)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ @@ -1986,7 +2178,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) } /* div in BigDecimal sense */ - ix = GetPrecisionInt(n); + ix = check_int_precision(n); if (ix == 0) { return BigDecimal_div(self, b); } @@ -1997,7 +2189,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) size_t b_prec = ix; size_t pl = VpSetPrecLimit(0); - GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); + GUARD_OBJ(cv, NewZeroWrapLimited(1, mx + VpBaseFig())); GUARD_OBJ(av, GetVpValue(self, 1)); /* TODO: I want to refactor this precision control for a float value later * by introducing an implicit conversion function instead of @@ -2008,7 +2200,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) GUARD_OBJ(bv, GetVpValueWithPrec(b, b_prec, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; - GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx * 2 + 2)*VpBaseFig())); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); @@ -2091,7 +2283,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_add(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2121,7 +2313,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_sub(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2164,7 +2356,7 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_mult(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2196,7 +2388,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpAsgn(c, a, 1); VpChangeSign(c, 1); return VpCheckGetValue(c); @@ -2219,9 +2411,10 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - n = GetPrecisionInt(nFig) + VpDblFig() + BASE_FIG; + n = check_int_precision(nFig); + n += VpDblFig() + VpBaseFig(); if (mx <= n) mx = n; - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSqrt(c, a); return VpCheckGetValue(c); } @@ -2237,7 +2430,7 @@ BigDecimal_fix(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ return VpCheckGetValue(c); } @@ -2310,7 +2503,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) pl = VpSetPrecLimit(0); GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { @@ -2356,7 +2549,7 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { @@ -2376,7 +2569,7 @@ BigDecimal_frac(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpFrac(c, a); return VpCheckGetValue(c); } @@ -2416,7 +2609,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG @@ -2462,7 +2655,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { @@ -2566,10 +2759,10 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) psz = RSTRING_PTR(str); if (fmt) { - VpToFString(vp, psz, mc, fPlus); + VpToFString(vp, psz, RSTRING_LEN(str), mc, fPlus); } else { - VpToString (vp, psz, mc, fPlus); + VpToString (vp, psz, RSTRING_LEN(str), mc, fPlus); } rb_str_resize(str, strlen(psz)); return str; @@ -2611,7 +2804,7 @@ BigDecimal_split(VALUE self) GUARD_OBJ(vp, GetVpValue(self, 1)); str = rb_str_new(0, VpNumOfChars(vp, "E")); psz1 = RSTRING_PTR(str); - VpSzMantissa(vp, psz1); + VpSzMantissa(vp, psz1, RSTRING_LEN(str)); s = 1; if(psz1[0] == '-') { size_t len = strlen(psz1 + 1); @@ -2660,7 +2853,7 @@ BigDecimal_inspect(VALUE self) nc = VpNumOfChars(vp, "E"); str = rb_str_new(0, nc); - VpToString(vp, RSTRING_PTR(str), 0, 0); + VpToString(vp, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); rb_str_resize(str, strlen(RSTRING_PTR(str))); return str; } @@ -2770,7 +2963,7 @@ bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); + return VpCheckGetValue(NewOneWrapLimited(1, n)); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2808,9 +3001,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); if (VpIsNaN(x)) { - y = VpCreateRbObject(n, "0", true); - RB_GC_GUARD(y->obj); - VpSetNaN(y); + y = NewZeroWrapLimited(1, n); + VpSetNaN(y); + RB_GC_GUARD(y->obj); return VpCheckGetValue(y); } @@ -2879,136 +3072,126 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } if (VpIsZero(x)) { - if (is_negative(vexp)) { - y = VpCreateRbObject(n, "#0", true); - RB_GC_GUARD(y->obj); - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-0) ** (-even_integer) -> Infinity */ - VpSetPosInf(y); - } - else { - /* (-0) ** (-odd_integer) -> -Infinity */ - VpSetNegInf(y); - } - } - else { - /* (-0) ** (-non_integer) -> Infinity */ - VpSetPosInf(y); - } - } - else { - /* (+0) ** (-num) -> Infinity */ - VpSetPosInf(y); - } + if (is_negative(vexp)) { + y = NewZeroWrapNolimit(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-0) ** (-even_integer) -> Infinity */ + VpSetPosInf(y); + } + else { + /* (-0) ** (-odd_integer) -> -Infinity */ + VpSetNegInf(y); + } + } + else { + /* (-0) ** (-non_integer) -> Infinity */ + VpSetPosInf(y); + } + } + else { + /* (+0) ** (-num) -> Infinity */ + VpSetPosInf(y); + } + RB_GC_GUARD(y->obj); return VpCheckGetValue(y); - } - else if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } + } + else if (is_zero(vexp)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } } if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); + return VpCheckGetValue(NewOneWrapLimited(1, n)); } else if (is_one(vexp)) { - return self; + return self; } if (VpIsInf(x)) { - if (is_negative(vexp)) { - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-Infinity) ** (-even_integer) -> +0 */ - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - else { - /* (-Infinity) ** (-odd_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - } - else { - /* (-Infinity) ** (-non_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } - else { - y = VpCreateRbObject(n, "0", true); - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - VpSetPosInf(y); - } - else { - VpSetNegInf(y); - } - } - else { - /* TODO: support complex */ - rb_raise(rb_eMathDomainError, - "a non-integral exponent for a negative base"); - } - } - else { - VpSetPosInf(y); - } + if (is_negative(vexp)) { + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-Infinity) ** (-even_integer) -> +0 */ + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + else { + /* (-Infinity) ** (-odd_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + /* (-Infinity) ** (-non_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + y = NewZeroWrapLimited(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + VpSetPosInf(y); + } + else { + VpSetNegInf(y); + } + } + else { + /* TODO: support complex */ + rb_raise(rb_eMathDomainError, + "a non-integral exponent for a negative base"); + } + } + else { + VpSetPosInf(y); + } return VpCheckGetValue(y); - } + } } if (exp != NULL) { - return bigdecimal_power_by_bigdecimal(x, exp, n); + return bigdecimal_power_by_bigdecimal(x, exp, n); } else if (RB_TYPE_P(vexp, T_BIGNUM)) { - VALUE abs_value = BigDecimal_abs(self); - if (is_one(abs_value)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); - } - else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { - if (is_negative(vexp)) { - y = VpCreateRbObject(n, "0", true); - if (is_even(vexp)) { - VpSetInf(y, VpGetSign(x)); - } - else { - VpSetInf(y, -VpGetSign(x)); - } + VALUE abs_value = BigDecimal_abs(self); + if (is_one(abs_value)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { + if (is_negative(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); return VpCheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } - else { - if (is_positive(vexp)) { - y = VpCreateRbObject(n, "0", true); - if (is_even(vexp)) { - VpSetInf(y, VpGetSign(x)); - } - else { - VpSetInf(y, -VpGetSign(x)); - } + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + if (is_positive(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); return VpCheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } } int_exp = FIX2LONG(vexp); @@ -3017,15 +3200,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (ma == 0) ma = 1; if (VpIsDef(x)) { - mp = x->Prec * (VpBaseFig() + 1); - GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0", true)); + mp = x->Prec * (VpBaseFig() + 1); + GUARD_OBJ(y, NewZeroWrapLimited(1, mp * (ma + 1))); } else { - GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); + GUARD_OBJ(y, NewZeroWrapLimited(1, 1)); } VpPowerByInt(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { - VpMidRound(y, VpGetRoundMode(), n); + VpMidRound(y, VpGetRoundMode(), n); } return VpCheckGetValue(y); } @@ -3114,7 +3297,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r Real *vp; if (uval == 0) { - vp = VpAllocReal(1); + vp = rbd_allocate_struct(1); vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; @@ -3122,7 +3305,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = 0; } else if (uval < BASE) { - vp = VpAllocReal(1); + vp = rbd_allocate_struct(1); vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; @@ -3148,7 +3331,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r } const size_t exp = len + ntz; - vp = VpAllocReal(len); + vp = rbd_allocate_struct(len); vp->MaxPrec = len; vp->Prec = len; vp->exponent = exp; @@ -3776,18 +3959,16 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); } else { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real* vy = NewZeroWrapNolimit(1, prec); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); } } else if (nan) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); - VpSetNaN(vy); - RB_GC_GUARD(vy->obj); + Real* vy = NewZeroWrapNolimit(1, prec); + VpSetNaN(vy); + RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); } else if (vx == NULL) { @@ -3805,7 +3986,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + one = VpCheckGetValue(NewOneWrapLimited(1, 1)); y = one; d = y; i = 1; @@ -3932,15 +4113,13 @@ get_vp_value: break; } if (infinite && !negative) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real *vy = NewZeroWrapNolimit(1, prec); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); return VpCheckGetValue(vy); } else if (nan) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real* vy = NewZeroWrapNolimit(1, prec); RB_GC_GUARD(vy->obj); VpSetNaN(vy); return VpCheckGetValue(vy); @@ -3954,7 +4133,7 @@ get_vp_value: } x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + RB_GC_GUARD(one) = VpCheckGetValue(NewOneWrapLimited(1, 1)); RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); n = prec + BIGDECIMAL_DOUBLE_FIGURES; @@ -4419,20 +4598,31 @@ Init_bigdecimal(void) rb_define_singleton_method(rb_mBigMath, "exp", BigMath_s_exp, 2); rb_define_singleton_method(rb_mBigMath, "log", BigMath_s_log, 2); - id_up = rb_intern_const("up"); - id_down = rb_intern_const("down"); - id_truncate = rb_intern_const("truncate"); - id_half_up = rb_intern_const("half_up"); - id_default = rb_intern_const("default"); - id_half_down = rb_intern_const("half_down"); - id_half_even = rb_intern_const("half_even"); - id_banker = rb_intern_const("banker"); - id_ceiling = rb_intern_const("ceiling"); - id_ceil = rb_intern_const("ceil"); - id_floor = rb_intern_const("floor"); +#define ROUNDING_MODE(i, name, value) \ + id_##name = rb_intern_const(#name); \ + rbd_rounding_modes[i].id = id_##name; \ + rbd_rounding_modes[i].mode = value; + + ROUNDING_MODE(0, up, RBD_ROUND_UP); + ROUNDING_MODE(1, down, RBD_ROUND_DOWN); + ROUNDING_MODE(2, half_up, RBD_ROUND_HALF_UP); + ROUNDING_MODE(3, half_down, RBD_ROUND_HALF_DOWN); + ROUNDING_MODE(4, ceil, RBD_ROUND_CEIL); + ROUNDING_MODE(5, floor, RBD_ROUND_FLOOR); + ROUNDING_MODE(6, half_even, RBD_ROUND_HALF_EVEN); + + ROUNDING_MODE(7, default, RBD_ROUND_DEFAULT); + ROUNDING_MODE(8, truncate, RBD_ROUND_TRUNCATE); + ROUNDING_MODE(9, banker, RBD_ROUND_BANKER); + ROUNDING_MODE(10, ceiling, RBD_ROUND_CEILING); + +#undef ROUNDING_MODE + id_to_r = rb_intern_const("to_r"); id_eq = rb_intern_const("=="); id_half = rb_intern_const("half"); + + (void)VPrint; /* suppress unused warning */ } /* @@ -4452,7 +4642,7 @@ static int gfCheckVal = 1; /* Value checking flag in VpNmlz() */ #endif /* BIGDECIMAL_DEBUG */ static Real *VpConstOne; /* constant 1.0 */ -static Real *VpPt5; /* constant 0.5 */ +static Real *VpConstPt5; /* constant 0.5 */ #define maxnr 100UL /* Maximum iterations for calculating sqrt. */ /* used in VpSqrt() */ @@ -4483,42 +4673,6 @@ static int VpRdup(Real *m, size_t ind_m); static int gnAlloc = 0; /* Memory allocation counter */ #endif /* BIGDECIMAL_DEBUG */ -VP_EXPORT void * -VpMemAlloc(size_t mb) -{ - void *p = xmalloc(mb); - memset(p, 0, mb); -#ifdef BIGDECIMAL_DEBUG - gnAlloc++; /* Count allocation call */ -#endif /* BIGDECIMAL_DEBUG */ - return p; -} - -VP_EXPORT void * -VpMemRealloc(void *ptr, size_t mb) -{ - return xrealloc(ptr, mb); -} - -VP_EXPORT void -VpFree(Real *pv) -{ - if (pv != NULL) { - xfree(pv); -#ifdef BIGDECIMAL_DEBUG - gnAlloc--; /* Decrement allocation count */ - if (gnAlloc == 0) { - printf(" *************** All memories allocated freed ****************\n"); - /*getchar();*/ - } - if (gnAlloc < 0) { - printf(" ??????????? Too many memory free calls(%d) ?????????????\n", gnAlloc); - /*getchar();*/ - } -#endif /* BIGDECIMAL_DEBUG */ - } -} - /* * EXCEPTION Handling. */ @@ -4907,9 +5061,13 @@ VpInit(DECDIG BaseVal) /* Setup +/- Inf NaN -0 */ VpGetDoubleNegZero(); - /* Allocates Vp constants. */ - VpConstOne = VpAlloc(1UL, "1", 1, 1); - VpPt5 = VpAlloc(1UL, ".5", 1, 1); + /* Const 1.0 */ + VpConstOne = NewOneNolimit(1, 1); + + /* Const 0.5 */ + VpConstPt5 = NewOneNolimit(1, 1); + VpConstPt5->exponent = 0; + VpConstPt5->frac[0] = 5*BASE1; #ifdef BIGDECIMAL_DEBUG gnAlloc = 0; @@ -4998,7 +5156,7 @@ bigdecimal_parse_special_string(const char *str) p = str + table[i].len; while (*p && ISSPACE(*p)) ++p; if (*p == '\0') { - Real *vp = VpAllocReal(1); + Real *vp = rbd_allocate_struct(1); vp->MaxPrec = 1; switch (table[i].sign) { default: @@ -5022,11 +5180,11 @@ bigdecimal_parse_special_string(const char *str) /* * Allocates variable. * [Input] - * mx ... allocation unit, if zero then mx is determined by szVal. - * The mx is the number of effective digits can to be stored. - * szVal ... value assigned(char). If szVal==NULL,then zero is assumed. - * If szVal[0]=='#' then Max. Prec. will not be considered(1.1.7), - * full precision specified by szVal is allocated. + * mx ... The number of decimal digits to be allocated, if zero then mx is determined by szVal. + * The mx will be the number of significant digits can to be stored. + * szVal ... The value assigned(char). If szVal==NULL, then zero is assumed. + * If szVal[0]=='#' then MaxPrec is not affected by the precision limit + * so that the full precision specified by szVal is allocated. * * [Returns] * Pointer to the newly allocated variable, or @@ -5037,48 +5195,40 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) { const char *orig_szVal = szVal; size_t i, j, ni, ipf, nf, ipe, ne, dot_seen, exp_seen, nalloc; + size_t len; char v, *psz; int sign=1; Real *vp = NULL; - size_t mf = VpGetPrecLimit(); VALUE buf; - mx = (mx + BASE_FIG - 1) / BASE_FIG; /* Determine allocation unit. */ - if (mx == 0) ++mx; - - if (szVal) { - /* Skipping leading spaces */ - while (ISSPACE(*szVal)) szVal++; - - /* Processing the leading one `#` */ - if (*szVal != '#') { - if (mf) { - mf = (mf + BASE_FIG - 1) / BASE_FIG + 2; /* Needs 1 more for div */ - if (mx > mf) { - mx = mf; - } - } - } - else { - ++szVal; - } - } - else { + if (szVal == NULL) { return_zero: /* necessary to be able to store */ /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ - vp = VpAllocReal(mx); - vp->MaxPrec = mx; /* set max precision */ + vp = rbd_allocate_struct(mx); + vp->MaxPrec = rbd_calculate_internal_digits(mx, false); /* Must false */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; } + /* Skipping leading spaces */ + while (ISSPACE(*szVal)) szVal++; + /* Check on Inf & NaN */ if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { return vp; } + /* Processing the leading one `#` */ + if (*szVal != '#') { + len = rbd_calculate_internal_digits(mx, true); + } + else { + len = rbd_calculate_internal_digits(mx, false); + ++szVal; + } + /* Scanning digits */ /* A buffer for keeping scanned digits */ @@ -5240,11 +5390,11 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = (ni + nf + BASE_FIG - 1) / BASE_FIG + 1; /* set effective allocation */ /* units for szVal[] */ - if (mx == 0) mx = 1; - nalloc = Max(nalloc, mx); - mx = nalloc; - vp = VpAllocReal(mx); - vp->MaxPrec = mx; /* set max precision */ + if (len == 0) len = 1; + nalloc = Max(nalloc, len); + len = nalloc; + vp = rbd_allocate_struct(len); + vp->MaxPrec = len; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); rb_str_resize(buf, 0); @@ -5809,7 +5959,7 @@ VpMult(Real *c, Real *a, Real *b) if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */ w = c; - c = VpAlloc((size_t)((MxIndAB + 1) * BASE_FIG), "#0", 1, 1); + c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG)); MxIndC = MxIndAB; } @@ -5817,8 +5967,8 @@ VpMult(Real *c, Real *a, Real *b) c->exponent = a->exponent; /* set exponent */ if (!AddExponent(c, b->exponent)) { - if (w) VpFree(c); - return 0; + if (w) rbd_free_struct(c); + return 0; } VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ carry = 0; @@ -5868,10 +6018,10 @@ VpMult(Real *c, Real *a, Real *b) } } if (w != NULL) { /* free work variable */ - VpNmlz(c); - VpAsgn(w, c, 1); - VpFree(c); - c = w; + VpNmlz(c); + VpAsgn(w, c, 1); + rbd_free_struct(c); + c = w; } else { VpLimitRound(c,0); @@ -6240,7 +6390,6 @@ Exit: * Note: % must not appear more than once * a ... VP variable to be printed */ -#ifdef BIGDECIMAL_ENABLE_VPRINT static int VPrint(FILE *fp, const char *cntl_chr, Real *a) { @@ -6253,95 +6402,94 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) /* nc : number of characters printed */ ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ while (*(cntl_chr + j)) { - if (*(cntl_chr + j) == '%' && *(cntl_chr + j + 1) != '%') { - nc = 0; - if (VpIsNaN(a)) { - fprintf(fp, SZ_NaN); - nc += 8; - } - else if (VpIsPosInf(a)) { - fprintf(fp, SZ_INF); - nc += 8; - } - else if (VpIsNegInf(a)) { - fprintf(fp, SZ_NINF); - nc += 9; - } - else if (!VpIsZero(a)) { - if (BIGDECIMAL_NEGATIVE_P(a)) { - fprintf(fp, "-"); - ++nc; - } - nc += fprintf(fp, "0."); - switch (*(cntl_chr + j + 1)) { - default: - break; + if (*(cntl_chr + j) == '%' && *(cntl_chr + j + 1) != '%') { + nc = 0; + if (VpIsNaN(a)) { + fprintf(fp, SZ_NaN); + nc += 8; + } + else if (VpIsPosInf(a)) { + fprintf(fp, SZ_INF); + nc += 8; + } + else if (VpIsNegInf(a)) { + fprintf(fp, SZ_NINF); + nc += 9; + } + else if (!VpIsZero(a)) { + if (BIGDECIMAL_NEGATIVE_P(a)) { + fprintf(fp, "-"); + ++nc; + } + nc += fprintf(fp, "0."); + switch (*(cntl_chr + j + 1)) { + default: + break; - case '0': case 'z': - ZeroSup = 0; - ++j; - sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; - break; - } - for (i = 0; i < a->Prec; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - nc += fprintf(fp, "%lu", (unsigned long)nn); /* The leading zero(s) */ - /* as 0.00xx will not */ - /* be printed. */ - ++nd; - ZeroSup = 0; /* Set to print succeeding zeros */ - } - if (nd >= sep) { /* print ' ' after every 10 digits */ - nd = 0; - nc += fprintf(fp, " "); - } - e = e - nn * m; - m /= 10; - } - } - nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); - nc += fprintf(fp, " (%"PRIdVALUE", %lu, %lu)", a->exponent, a->Prec, a->MaxPrec); - } - else { - nc += fprintf(fp, "0.0"); - } - } - else { - ++nc; - if (*(cntl_chr + j) == '\\') { - switch (*(cntl_chr + j + 1)) { - case 'n': - fprintf(fp, "\n"); - ++j; - break; - case 't': - fprintf(fp, "\t"); - ++j; - break; - case 'b': - fprintf(fp, "\n"); - ++j; - break; - default: - fprintf(fp, "%c", *(cntl_chr + j)); - break; - } - } - else { - fprintf(fp, "%c", *(cntl_chr + j)); - if (*(cntl_chr + j) == '%') ++j; - } - } - j++; + case '0': case 'z': + ZeroSup = 0; + ++j; + sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; + break; + } + for (i = 0; i < a->Prec; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + nc += fprintf(fp, "%lu", (unsigned long)nn); /* The leading zero(s) */ + /* as 0.00xx will not */ + /* be printed. */ + ++nd; + ZeroSup = 0; /* Set to print succeeding zeros */ + } + if (nd >= sep) { /* print ' ' after every 10 digits */ + nd = 0; + nc += fprintf(fp, " "); + } + e = e - nn * m; + m /= 10; + } + } + nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); + nc += fprintf(fp, " (%"PRIdVALUE", %lu, %lu)", a->exponent, a->Prec, a->MaxPrec); + } + else { + nc += fprintf(fp, "0.0"); + } + } + else { + ++nc; + if (*(cntl_chr + j) == '\\') { + switch (*(cntl_chr + j + 1)) { + case 'n': + fprintf(fp, "\n"); + ++j; + break; + case 't': + fprintf(fp, "\t"); + ++j; + break; + case 'b': + fprintf(fp, "\n"); + ++j; + break; + default: + fprintf(fp, "%c", *(cntl_chr + j)); + break; + } + } + else { + fprintf(fp, "%c", *(cntl_chr + j)); + if (*(cntl_chr + j) == '%') ++j; + } + } + j++; } return (int)nc; } -#endif static void VpFormatSt(char *psz, size_t fFmt) @@ -6386,188 +6534,254 @@ VpExponent10(Real *a) } VP_EXPORT void -VpSzMantissa(Real *a,char *psz) +VpSzMantissa(Real *a, char *buf, size_t buflen) { size_t i, n, ZeroSup; DECDIG_DBL m, e, nn; if (VpIsNaN(a)) { - sprintf(psz, SZ_NaN); - return; + snprintf(buf, buflen, SZ_NaN); + return; } if (VpIsPosInf(a)) { - sprintf(psz, SZ_INF); + snprintf(buf, buflen, SZ_INF); return; } if (VpIsNegInf(a)) { - sprintf(psz, SZ_NINF); + snprintf(buf, buflen, SZ_NINF); return; } ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ if (!VpIsZero(a)) { - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - n = a->Prec; - for (i = 0; i < n; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - sprintf(psz, "%lu", (unsigned long)nn); /* The leading zero(s) */ - psz += strlen(psz); - /* as 0.00xx will be ignored. */ - ZeroSup = 0; /* Set to print succeeding zeros */ - } - e = e - nn * m; - m /= 10; - } - } - *psz = 0; - while (psz[-1] == '0') *(--psz) = 0; + if (BIGDECIMAL_NEGATIVE_P(a)) *buf++ = '-'; + n = a->Prec; + for (i = 0; i < n; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + snprintf(buf, buflen, "%lu", (unsigned long)nn); /* The leading zero(s) */ + buf += strlen(buf); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } + } + *buf = 0; + while (buf[-1] == '0') *(--buf) = 0; } else { - if (VpIsPosZero(a)) sprintf(psz, "0"); - else sprintf(psz, "-0"); + if (VpIsPosZero(a)) snprintf(buf, buflen, "0"); + else snprintf(buf, buflen, "-0"); } } VP_EXPORT int -VpToSpecialString(Real *a,char *psz,int fPlus) +VpToSpecialString(Real *a, char *buf, size_t buflen, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { if (VpIsNaN(a)) { - sprintf(psz,SZ_NaN); - return 1; + snprintf(buf, buflen, SZ_NaN); + return 1; } if (VpIsPosInf(a)) { - if (fPlus == 1) { - *psz++ = ' '; - } - else if (fPlus == 2) { - *psz++ = '+'; - } - sprintf(psz, SZ_INF); - return 1; + if (fPlus == 1) { + *buf++ = ' '; + } + else if (fPlus == 2) { + *buf++ = '+'; + } + snprintf(buf, buflen, SZ_INF); + return 1; } if (VpIsNegInf(a)) { - sprintf(psz, SZ_NINF); - return 1; + snprintf(buf, buflen, SZ_NINF); + return 1; } if (VpIsZero(a)) { - if (VpIsPosZero(a)) { - if (fPlus == 1) sprintf(psz, " 0.0"); - else if (fPlus == 2) sprintf(psz, "+0.0"); - else sprintf(psz, "0.0"); - } - else sprintf(psz, "-0.0"); - return 1; + if (VpIsPosZero(a)) { + if (fPlus == 1) snprintf(buf, buflen, " 0.0"); + else if (fPlus == 2) snprintf(buf, buflen, "+0.0"); + else snprintf(buf, buflen, "0.0"); + } + else snprintf(buf, buflen, "-0.0"); + return 1; } return 0; } VP_EXPORT void -VpToString(Real *a, char *psz, size_t fFmt, int fPlus) +VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n, ZeroSup; DECDIG shift, m, e, nn; - char *pszSav = psz; + char *p = buf; + size_t plen = buflen; ssize_t ex; - if (VpToSpecialString(a, psz, fPlus)) return; + if (VpToSpecialString(a, buf, buflen, fPlus)) return; ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - else if (fPlus == 1) *psz++ = ' '; - else if (fPlus == 2) *psz++ = '+'; +#define ADVANCE(n) do { \ + if (plen < n) goto overflow; \ + p += n; \ + plen -= n; \ +} while (0) + + if (BIGDECIMAL_NEGATIVE_P(a)) { + *p = '-'; + ADVANCE(1); + } + else if (fPlus == 1) { + *p = ' '; + ADVANCE(1); + } + else if (fPlus == 2) { + *p = '+'; + ADVANCE(1); + } + + *p = '0'; ADVANCE(1); + *p = '.'; ADVANCE(1); - *psz++ = '0'; - *psz++ = '.'; n = a->Prec; for (i = 0; i < n; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - sprintf(psz, "%lu", (unsigned long)nn); /* The reading zero(s) */ - psz += strlen(psz); - /* as 0.00xx will be ignored. */ - ZeroSup = 0; /* Set to print succeeding zeros */ - } - e = e - nn * m; - m /= 10; - } + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + /* The reading zero(s) */ + size_t n = (size_t)snprintf(p, plen, "%lu", (unsigned long)nn); + if (n > plen) goto overflow; + ADVANCE(n); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } } + ex = a->exponent * (ssize_t)BASE_FIG; shift = BASE1; while (a->frac[0] / shift == 0) { - --ex; - shift /= 10; + --ex; + shift /= 10; } - while (psz[-1] == '0') { - *(--psz) = 0; + while (p - 1 > buf && p[-1] == '0') { + *(--p) = '\0'; + ++plen; } - sprintf(psz, "e%"PRIdSIZE, ex); - if (fFmt) VpFormatSt(pszSav, fFmt); + snprintf(p, plen, "e%"PRIdSIZE, ex); + if (fFmt) VpFormatSt(buf, fFmt); + + overflow: + return; +#undef ADVANCE } VP_EXPORT void -VpToFString(Real *a, char *psz, size_t fFmt, int fPlus) +VpToFString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n; DECDIG m, e, nn; - char *pszSav = psz; + char *p = buf; + size_t plen = buflen; ssize_t ex; - if (VpToSpecialString(a, psz, fPlus)) return; + if (VpToSpecialString(a, buf, buflen, fPlus)) return; - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - else if (fPlus == 1) *psz++ = ' '; - else if (fPlus == 2) *psz++ = '+'; +#define ADVANCE(n) do { \ + if (plen < n) goto overflow; \ + p += n; \ + plen -= n; \ +} while (0) + + + if (BIGDECIMAL_NEGATIVE_P(a)) { + *p = '-'; + ADVANCE(1); + } + else if (fPlus == 1) { + *p = ' '; + ADVANCE(1); + } + else if (fPlus == 2) { + *p = '+'; + ADVANCE(1); + } n = a->Prec; ex = a->exponent; if (ex <= 0) { - *psz++ = '0';*psz++ = '.'; - while (ex < 0) { - for (i=0; i < BASE_FIG; ++i) *psz++ = '0'; - ++ex; - } - ex = -1; + *p = '0'; ADVANCE(1); + *p = '.'; ADVANCE(1); + while (ex < 0) { + for (i=0; i < BASE_FIG; ++i) { + *p = '0'; ADVANCE(1); + } + ++ex; + } + ex = -1; } for (i = 0; i < n; ++i) { - --ex; - if (i == 0 && ex >= 0) { - sprintf(psz, "%lu", (unsigned long)a->frac[i]); - psz += strlen(psz); - } - else { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - *psz++ = (char)(nn + '0'); - e = e - nn * m; - m /= 10; - } - } - if (ex == 0) *psz++ = '.'; + --ex; + if (i == 0 && ex >= 0) { + size_t n = snprintf(p, plen, "%lu", (unsigned long)a->frac[i]); + if (n > plen) goto overflow; + ADVANCE(n); + } + else { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + *p = (char)(nn + '0'); + ADVANCE(1); + e = e - nn * m; + m /= 10; + } + } + if (ex == 0) { + *p = '.'; + ADVANCE(1); + } } while (--ex>=0) { - m = BASE; - while (m /= 10) *psz++ = '0'; - if (ex == 0) *psz++ = '.'; + m = BASE; + while (m /= 10) { + *p = '0'; + ADVANCE(1); + } + if (ex == 0) { + *p = '.'; + ADVANCE(1); + } + } + + *p = '\0'; + while (p - 1 > buf && p[-1] == '0') { + *(--p) = '\0'; + ++plen; + } + if (p - 1 > buf && p[-1] == '.') { + snprintf(p, plen, "0"); } - *psz = 0; - while (psz[-1] == '0') *(--psz) = 0; - if (psz[-1] == '.') sprintf(psz, "0"); - if (fFmt) VpFormatSt(pszSav, fFmt); + if (fFmt) VpFormatSt(buf, fFmt); + + overflow: + return; +#undef ADVANCE } /* @@ -6976,8 +7190,9 @@ VpSqrt(Real *y, Real *x) if (x->MaxPrec > (size_t)n) n = (ssize_t)x->MaxPrec; /* allocate temporally variables */ - f = VpAlloc(y->MaxPrec * (BASE_FIG + 2), "#1", 1, 1); - r = VpAlloc((n + n) * (BASE_FIG + 2), "#1", 1, 1); + /* TODO: reconsider MaxPrec of f and r */ + f = NewOneNolimit(1, y->MaxPrec * (BASE_FIG + 2)); + r = NewOneNolimit(1, (n + n) * (BASE_FIG + 2)); nr = 0; y_prec = y->MaxPrec; @@ -7002,16 +7217,21 @@ VpSqrt(Real *y, Real *x) f->MaxPrec = y->MaxPrec + 1; n = (SIGNED_VALUE)(y_prec * BASE_FIG); if (n < (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; + + /* + * Perform: y_{n+1} = (y_n - x/y_n) / 2 + */ do { - y->MaxPrec *= 2; - if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; - f->MaxPrec = y->MaxPrec; - VpDivd(f, r, x, y); /* f = x/y */ - VpAddSub(r, f, y, -1); /* r = f - y */ - VpMult(f, VpPt5, r); /* f = 0.5*r */ - if (VpIsZero(f)) goto converge; - VpAddSub(r, f, y, 1); /* r = y + f */ - VpAsgn(y, r, 1); /* y = r */ + y->MaxPrec *= 2; + if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; + f->MaxPrec = y->MaxPrec; + VpDivd(f, r, x, y); /* f = x/y */ + VpAddSub(r, f, y, -1); /* r = f - y */ + VpMult(f, VpConstPt5, r); /* f = 0.5*r */ + if (VpIsZero(f)) + goto converge; + VpAddSub(r, f, y, 1); /* r = y + f */ + VpAsgn(y, r, 1); /* y = r */ } while (++nr < n); #ifdef BIGDECIMAL_DEBUG @@ -7036,8 +7256,8 @@ converge: y->MaxPrec = y_prec; Exit: - VpFree(f); - VpFree(r); + rbd_free_struct(f); + rbd_free_struct(r); return 1; } @@ -7428,9 +7648,10 @@ VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) } /* Allocate working variables */ + /* TODO: reconsider MaxPrec of w1 and w2 */ + w1 = NewZeroNolimit(1, (y->MaxPrec + 2) * BASE_FIG); + w2 = NewZeroNolimit(1, (w1->MaxPrec * 2 + 1) * BASE_FIG); - w1 = VpAlloc((y->MaxPrec + 2) * BASE_FIG, "#0", 1, 1); - w2 = VpAlloc((w1->MaxPrec * 2 + 1) * BASE_FIG, "#0", 1, 1); /* calculation start */ VpAsgn(y, x, 1); @@ -7459,8 +7680,8 @@ Exit: printf(" n=%"PRIdVALUE"\n", n); } #endif /* BIGDECIMAL_DEBUG */ - VpFree(w2); - VpFree(w1); + rbd_free_struct(w2); + rbd_free_struct(w1); return 1; } diff --git a/ext/bigdecimal/bigdecimal.gemspec b/ext/bigdecimal/bigdecimal.gemspec index 1feed332f6..d215757188 100644 --- a/ext/bigdecimal/bigdecimal.gemspec +++ b/ext/bigdecimal/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.2" + s.version = "3.1.3" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index bd1c46743e..54fed811fb 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -102,7 +102,7 @@ extern VALUE rb_cBigDecimal; */ #define VP_EXPORT static -/* Exception codes */ +/* Exception mode */ #define VP_EXCEPTION_ALL ((unsigned short)0x00FF) #define VP_EXCEPTION_INFINITY ((unsigned short)0x0001) #define VP_EXCEPTION_NaN ((unsigned short)0x0002) @@ -115,18 +115,36 @@ extern VALUE rb_cBigDecimal; #define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U -/* Computation mode */ +/* This is used in BigDecimal#mode */ #define VP_ROUND_MODE ((unsigned short)0x0100) -#define VP_ROUND_UP 1 -#define VP_ROUND_DOWN 2 -#define VP_ROUND_HALF_UP 3 -#define VP_ROUND_HALF_DOWN 4 -#define VP_ROUND_CEIL 5 -#define VP_ROUND_FLOOR 6 -#define VP_ROUND_HALF_EVEN 7 + +/* Rounding mode */ +#define VP_ROUND_UP RBD_ROUND_UP +#define VP_ROUND_DOWN RBD_ROUND_DOWN +#define VP_ROUND_HALF_UP RBD_ROUND_HALF_UP +#define VP_ROUND_HALF_DOWN RBD_ROUND_HALF_DOWN +#define VP_ROUND_CEIL RBD_ROUND_CEIL +#define VP_ROUND_FLOOR RBD_ROUND_FLOOR +#define VP_ROUND_HALF_EVEN RBD_ROUND_HALF_EVEN + +enum rbd_rounding_mode { + RBD_ROUND_UP = 1, + RBD_ROUND_DOWN = 2, + RBD_ROUND_HALF_UP = 3, + RBD_ROUND_HALF_DOWN = 4, + RBD_ROUND_CEIL = 5, + RBD_ROUND_FLOOR = 6, + RBD_ROUND_HALF_EVEN = 7, + + RBD_ROUND_DEFAULT = RBD_ROUND_HALF_UP, + RBD_ROUND_TRUNCATE = RBD_ROUND_DOWN, + RBD_ROUND_BANKER = RBD_ROUND_HALF_EVEN, + RBD_ROUND_CEILING = RBD_ROUND_CEIL +}; #define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP +/* Sign flag */ #define VP_SIGN_NaN 0 /* NaN */ #define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ #define VP_SIGN_NEGATIVE_ZERO -1 /* Negative zero */ @@ -135,6 +153,7 @@ extern VALUE rb_cBigDecimal; #define VP_SIGN_POSITIVE_INFINITE 3 /* Positive infinite number */ #define VP_SIGN_NEGATIVE_INFINITE -3 /* Negative infinite number */ +/* The size of fraction part array */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #define FLEXIBLE_ARRAY_SIZE /* */ #elif defined(__GNUC__) && !defined(__STRICT_ANSI__) @@ -205,9 +224,6 @@ VP_EXPORT int VpIsNegDoubleZero(double v); #endif VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); VP_EXPORT size_t VpInit(DECDIG BaseVal); -VP_EXPORT void *VpMemAlloc(size_t mb); -VP_EXPORT void *VpMemRealloc(void *ptr, size_t mb); -VP_EXPORT void VpFree(Real *pv); VP_EXPORT Real *VpAlloc(size_t mx, const char *szVal, int strict_p, int exc); VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); @@ -215,10 +231,10 @@ VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); VP_EXPORT size_t VpDivd(Real *c,Real *r,Real *a,Real *b); VP_EXPORT int VpComp(Real *a,Real *b); VP_EXPORT ssize_t VpExponent10(Real *a); -VP_EXPORT void VpSzMantissa(Real *a,char *psz); -VP_EXPORT int VpToSpecialString(Real *a,char *psz,int fPlus); -VP_EXPORT void VpToString(Real *a, char *psz, size_t fFmt, int fPlus); -VP_EXPORT void VpToFString(Real *a, char *psz, size_t fFmt, int fPlus); +VP_EXPORT void VpSzMantissa(Real *a, char *buf, size_t bufsize); +VP_EXPORT int VpToSpecialString(Real *a, char *buf, size_t bufsize, int fPlus); +VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); +VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); VP_EXPORT void VpDtoV(Real *m,double d); diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 307147c0fd..325554b5f5 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -35,10 +35,10 @@ extern "C" { #endif /* RB_UNUSED_VAR */ #if defined(_MSC_VER) && _MSC_VER >= 1310 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #endif #ifndef UNREACHABLE diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index c8229ae8fe..4578de54e4 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -131,7 +131,7 @@ rb_coverage_setup(int argc, VALUE *argv, VALUE klass) * Start/resume the coverage measurement. * * Caveat: Currently, only process-global coverage measurement is supported. - * You cannot measure per-thread covearge. If your process has multiple thread, + * You cannot measure per-thread coverage. If your process has multiple thread, * using Coverage.resume/suspend to capture code coverage executed from only * a limited code block, may yield misleading results. */ @@ -470,7 +470,7 @@ rb_coverage_running(VALUE klass) * This feature is experimental, so these APIs may be changed in future. * * Caveat: Currently, only process-global coverage measurement is supported. - * You cannot measure per-thread covearge. + * You cannot measure per-thread coverage. * * = Usage * diff --git a/ext/coverage/depend b/ext/coverage/depend index 57d368d3f5..e7fab16484 100644 --- a/ext/coverage/depend +++ b/ext/coverage/depend @@ -165,9 +165,12 @@ coverage.o: $(top_srcdir)/ccan/check_type/check_type.h coverage.o: $(top_srcdir)/ccan/container_of/container_of.h coverage.o: $(top_srcdir)/ccan/list/list.h coverage.o: $(top_srcdir)/ccan/str/str.h +coverage.o: $(top_srcdir)/constant.h coverage.o: $(top_srcdir)/gc.h +coverage.o: $(top_srcdir)/id_table.h coverage.o: $(top_srcdir)/internal.h coverage.o: $(top_srcdir)/internal/array.h +coverage.o: $(top_srcdir)/internal/basic_operators.h coverage.o: $(top_srcdir)/internal/compilers.h coverage.o: $(top_srcdir)/internal/gc.h coverage.o: $(top_srcdir)/internal/hash.h @@ -176,12 +179,14 @@ coverage.o: $(top_srcdir)/internal/sanitizers.h coverage.o: $(top_srcdir)/internal/serial.h coverage.o: $(top_srcdir)/internal/static_assert.h coverage.o: $(top_srcdir)/internal/thread.h +coverage.o: $(top_srcdir)/internal/variable.h coverage.o: $(top_srcdir)/internal/vm.h coverage.o: $(top_srcdir)/internal/warnings.h coverage.o: $(top_srcdir)/method.h coverage.o: $(top_srcdir)/node.h coverage.o: $(top_srcdir)/ruby_assert.h coverage.o: $(top_srcdir)/ruby_atomic.h +coverage.o: $(top_srcdir)/shape.h coverage.o: $(top_srcdir)/thread_pthread.h coverage.o: $(top_srcdir)/vm_core.h coverage.o: $(top_srcdir)/vm_opts.h diff --git a/ext/date/date.gemspec b/ext/date/date.gemspec index eecbf786a3..660353ebc5 100644 --- a/ext/date/date.gemspec +++ b/ext/date/date.gemspec @@ -10,15 +10,22 @@ Gem::Specification.new do |s| s.summary = "A subclass of Object includes Comparable module for handling dates." s.description = "A subclass of Object includes Comparable module for handling dates." - s.require_path = %w{lib} - s.files = [ - "README.md", - "lib/date.rb", "ext/date/date_core.c", "ext/date/date_parse.c", "ext/date/date_strftime.c", - "ext/date/date_strptime.c", "ext/date/date_tmx.h", "ext/date/extconf.rb", "ext/date/prereq.mk", - "ext/date/zonetab.h", "ext/date/zonetab.list" - ] - s.extensions = "ext/date/extconf.rb" - s.required_ruby_version = ">= 2.4.0" + if Gem::Platform === s.platform and s.platform =~ 'java' or RUBY_ENGINE == 'jruby' + s.platform = 'java' + # No files shipped, no require path, no-op for now on JRuby + else + s.require_path = %w{lib} + + s.files = [ + "README.md", + "lib/date.rb", "ext/date/date_core.c", "ext/date/date_parse.c", "ext/date/date_strftime.c", + "ext/date/date_strptime.c", "ext/date/date_tmx.h", "ext/date/extconf.rb", "ext/date/prereq.mk", + "ext/date/zonetab.h", "ext/date/zonetab.list" + ] + s.extensions = "ext/date/extconf.rb" + end + + 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 83d493c794..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); } @@ -761,6 +766,8 @@ c_valid_civil_p(int y, int m, int d, double sg, if (m < 0) m += 13; + if (m < 1 || m > 12) + return 0; if (d < 0) { if (!c_find_ldom(y, m, sg, rjd, ns)) return 0; @@ -4377,7 +4384,7 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} * * For other formats, see - * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. @@ -4406,7 +4413,7 @@ date_s__strptime(int argc, VALUE *argv, VALUE klass) * Date.strptime('sat3feb01', '%a%d%b%y') # => #<Date: 2001-02-03> * * For other formats, see - * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. @@ -4506,7 +4513,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] * * If +string+ does not specify a valid date, * the result is unpredictable; @@ -4541,7 +4548,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] * If +string+ does not specify a valid date, * the result is unpredictable; * consider using Date._strptime instead. @@ -4606,7 +4613,7 @@ VALUE date__jisx0301(VALUE); * Date._iso8601(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should contain - * an {ISO 8601 formatted date}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4633,7 +4640,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should contain - * an {ISO 8601 formatted date}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4676,7 +4683,7 @@ date_s_iso8601(int argc, VALUE *argv, VALUE klass) * Date._rfc3339(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 3339 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4704,7 +4711,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 3339 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4816,7 +4823,7 @@ date_s_xmlschema(int argc, VALUE *argv, VALUE klass) * Date._rfc2822(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 2822 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4846,7 +4853,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 2822 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4890,7 +4897,7 @@ date_s_rfc2822(int argc, VALUE *argv, VALUE klass) * Date._httpdate(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {HTTP date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-HTTP+Format]: + * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4916,7 +4923,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {HTTP date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-HTTP+Format]: + * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4958,7 +4965,7 @@ date_s_httpdate(int argc, VALUE *argv, VALUE klass) * Date._jisx0301(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {JIS X 0301 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-JIS+X+0301+Format]: + * {JIS X 0301 date format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -4984,7 +4991,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * * Returns a new \Date object with values parsed from +string+, - * which should be a valid {JIS X 0301 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-JIS+X+0301+Format]: + * which should be a valid {JIS X 0301 format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -6971,7 +6978,7 @@ static VALUE strftimev(const char *, VALUE, * to_s -> string * * Returns a string representation of the date in +self+ - * in {ISO 8601 extended date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications] + * in {ISO 8601 extended date format}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications] * (<tt>'%Y-%m-%d'</tt>): * * Date.new(2001, 2, 3).to_s # => "2001-02-03" @@ -7252,7 +7259,7 @@ date_strftime_internal(int argc, VALUE *argv, VALUE self, * Date.new(2001, 2, 3).strftime # => "2001-02-03" * * For other formats, see - * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. * */ static VALUE @@ -7284,7 +7291,7 @@ strftimev(const char *fmt, VALUE self, * asctime -> string * * Equivalent to #strftime with argument <tt>'%a %b %e %T %Y'</tt> - * (or its {shorthand form}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * <tt>'%c'</tt>): * * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" @@ -7304,7 +7311,7 @@ d_lite_asctime(VALUE self) * iso8601 -> string * * Equivalent to #strftime with argument <tt>'%Y-%m-%d'</tt> - * (or its {shorthand form}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * <tt>'%F'</tt>); * * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" @@ -7322,7 +7329,7 @@ d_lite_iso8601(VALUE self) * rfc3339 -> string * * Equivalent to #strftime with argument <tt>'%FT%T%:z'</tt>; - * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" * @@ -7338,7 +7345,7 @@ d_lite_rfc3339(VALUE self) * rfc2822 -> string * * Equivalent to #strftime with argument <tt>'%a, %-d %b %Y %T %z'</tt>; - * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" * @@ -7355,7 +7362,7 @@ d_lite_rfc2822(VALUE self) * httpdate -> string * * Equivalent to #strftime with argument <tt>'%a, %d %b %Y %T GMT'</tt>; - * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" * @@ -7430,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 @@ -8738,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) @@ -9368,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 @@ -9392,7 +9541,7 @@ Init_date_core(void) * calendar dates. * * Consider using - * {class Time}[https://docs.ruby-lang.org/en/master/Time.html] + * {class Time}[rdoc-ref:Time] * instead of class \Date if: * * - You need both dates and times; \Date handles only dates. @@ -9444,7 +9593,7 @@ Init_date_core(void) * Date.strptime('fri31dec99', '%a%d%b%y') # => #<Date: 1999-12-31> * * See also the specialized methods in - * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] * * == Argument +limit+ * @@ -9689,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 @@ -9899,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_parse.c b/ext/date/date_parse.c index 7349c75d84..c6f26ecb91 100644 --- a/ext/date/date_parse.c +++ b/ext/date/date_parse.c @@ -486,14 +486,14 @@ date_zone_to_diff(VALUE str) #define out_of_range(v, min, max) ((v) < (min) || (max) < (v)) hour = STRTOUL(s, &p, 10); if (*p == ':') { - if (out_of_range(sec, 0, 59)) return Qnil; + if (out_of_range(hour, 0, 23)) return Qnil; s = ++p; min = STRTOUL(s, &p, 10); if (out_of_range(min, 0, 59)) return Qnil; if (*p == ':') { s = ++p; sec = STRTOUL(s, &p, 10); - if (out_of_range(hour, 0, 23)) return Qnil; + if (out_of_range(sec, 0, 59)) return Qnil; } } else if (*p == ',' || *p == '.') { 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 1899611440..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.2.3" # :nodoc: + VERSION = "3.3.3" # :nodoc: # call-seq: # infinite? -> false diff --git a/ext/digest/digest_conf.rb b/ext/digest/digest_conf.rb index 1b929d8732..36a7d75289 100644 --- a/ext/digest/digest_conf.rb +++ b/ext/digest/digest_conf.rb @@ -3,7 +3,7 @@ def digest_conf(name) unless with_config("bundled-#{name}") cc = with_config("common-digest") - if cc == true or /\b#{name}\b/ =~ cc + if cc != false or /\b#{name}\b/ =~ cc if File.exist?("#$srcdir/#{name}cc.h") and have_header("CommonCrypto/CommonDigest.h") $defs << "-D#{name.upcase}_USE_COMMONDIGEST" diff --git a/ext/digest/lib/digest/version.rb b/ext/digest/lib/digest/version.rb index 79e6aeee99..42fd7acf6e 100644 --- a/ext/digest/lib/digest/version.rb +++ b/ext/digest/lib/digest/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Digest - VERSION = "3.1.0" + VERSION = "3.1.1" end diff --git a/ext/digest/md5/depend b/ext/digest/md5/depend index 0353e7a40d..ea1ceec7fd 100644 --- a/ext/digest/md5/depend +++ b/ext/digest/md5/depend @@ -326,5 +326,6 @@ md5init.o: $(hdrdir)/ruby/subst.h md5init.o: $(srcdir)/../defs.h md5init.o: $(srcdir)/../digest.h md5init.o: md5.h +md5init.o: md5cc.h md5init.o: md5init.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/digest/sha1/depend b/ext/digest/sha1/depend index a4e454d214..48aaef158b 100644 --- a/ext/digest/sha1/depend +++ b/ext/digest/sha1/depend @@ -326,5 +326,6 @@ sha1init.o: $(hdrdir)/ruby/subst.h sha1init.o: $(srcdir)/../defs.h sha1init.o: $(srcdir)/../digest.h sha1init.o: sha1.h +sha1init.o: sha1cc.h sha1init.o: sha1init.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/digest/sha2/depend b/ext/digest/sha2/depend index 2fb598aa48..47a859068c 100644 --- a/ext/digest/sha2/depend +++ b/ext/digest/sha2/depend @@ -325,5 +325,6 @@ sha2init.o: $(hdrdir)/ruby/st.h sha2init.o: $(hdrdir)/ruby/subst.h sha2init.o: $(srcdir)/../digest.h sha2init.o: sha2.h +sha2init.o: sha2cc.h sha2init.o: sha2init.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c new file mode 100644 index 0000000000..67b2d1ef34 --- /dev/null +++ b/ext/erb/escape/escape.c @@ -0,0 +1,95 @@ +#include "ruby.h" +#include "ruby/encoding.h" + +static VALUE rb_cERB, rb_mEscape, rb_cCGI; +static ID id_escapeHTML; + +#define HTML_ESCAPE_MAX_LEN 6 + +static const struct { + uint8_t len; + char str[HTML_ESCAPE_MAX_LEN+1]; +} html_escape_table[UCHAR_MAX+1] = { +#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str} + HTML_ESCAPE('\'', "'"), + HTML_ESCAPE('&', "&"), + HTML_ESCAPE('"', """), + HTML_ESCAPE('<', "<"), + HTML_ESCAPE('>', ">"), +#undef HTML_ESCAPE +}; + +static inline void +preserve_original_state(VALUE orig, VALUE dest) +{ + rb_enc_associate(dest, rb_enc_get(orig)); +} + +static inline long +escaped_length(VALUE str) +{ + const long len = RSTRING_LEN(str); + if (len >= LONG_MAX / HTML_ESCAPE_MAX_LEN) { + ruby_malloc_size_overflow(len, HTML_ESCAPE_MAX_LEN); + } + return len * HTML_ESCAPE_MAX_LEN; +} + +static VALUE +optimized_escape_html(VALUE str) +{ + VALUE vbuf; + char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); + const char *cstr = RSTRING_PTR(str); + const char *end = cstr + RSTRING_LEN(str); + + char *dest = buf; + while (cstr < end) { + const unsigned char c = *cstr++; + uint8_t len = html_escape_table[c].len; + if (len) { + memcpy(dest, html_escape_table[c].str, len); + dest += len; + } + else { + *dest++ = c; + } + } + + VALUE escaped = str; + if (RSTRING_LEN(str) < (dest - buf)) { + escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); + } + ALLOCV_END(vbuf); + return escaped; +} + +// ERB::Util.html_escape is different from CGI.escapeHTML in the following two parts: +// * ERB::Util.html_escape converts an argument with #to_s first (only if it's not T_STRING) +// * ERB::Util.html_escape does not allocate a new string when nothing needs to be escaped +static VALUE +erb_escape_html(VALUE self, VALUE str) +{ + if (!RB_TYPE_P(str, T_STRING)) { + str = rb_convert_type(str, T_STRING, "String", "to_s"); + } + + if (rb_enc_str_asciicompat_p(str)) { + return optimized_escape_html(str); + } + else { + return rb_funcall(rb_cCGI, id_escapeHTML, 1, str); + } +} + +void +Init_escape(void) +{ + rb_cERB = rb_define_class("ERB", rb_cObject); + rb_mEscape = rb_define_module_under(rb_cERB, "Escape"); + rb_define_module_function(rb_mEscape, "html_escape", erb_escape_html, 1); + + rb_cCGI = rb_define_class("CGI", rb_cObject); + id_escapeHTML = rb_intern("escapeHTML"); +} diff --git a/ext/erb/escape/extconf.rb b/ext/erb/escape/extconf.rb new file mode 100644 index 0000000000..c1002548ad --- /dev/null +++ b/ext/erb/escape/extconf.rb @@ -0,0 +1,7 @@ +require 'mkmf' + +if RUBY_ENGINE == 'truffleruby' + File.write('Makefile', dummy_makefile($srcdir).join) +else + create_makefile 'erb/escape' +end diff --git a/ext/etc/etc.c b/ext/etc/etc.c index c355fe117a..6c7145b40b 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -56,7 +56,7 @@ static VALUE sGroup; #endif char *getlogin(); -#define RUBY_ETC_VERSION "1.4.0" +#define RUBY_ETC_VERSION "1.4.2" #ifdef HAVE_RB_DEPRECATE_CONSTANT void rb_deprecate_constant(VALUE mod, const char *name); diff --git a/ext/extmk.rb b/ext/extmk.rb index 939eb73565..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 @@ -144,7 +152,7 @@ def extmake(target, basedir = 'ext', maybestatic = true) d = target until (d = File.dirname(d)) == '.' if File.exist?("#{$top_srcdir}/#{basedir}/#{d}/extconf.rb") - parent = (/^all:\s*install/ =~ IO.read("#{d}/Makefile") rescue false) + parent = (/^all:\s*install/ =~ File.read("#{d}/Makefile") rescue false) break end end @@ -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] @@ -447,9 +455,8 @@ if $extstatic end for dir in ["ext", File::join($top_srcdir, "ext")] setup = File::join(dir, CONFIG['setup']) - if File.file? setup - f = open(setup) - while line = f.gets() + if (f = File.stat(setup) and f.file? rescue next) + File.foreach(setup) do |line| line.chomp! line.sub!(/#.*$/, '') next if /^\s*$/ =~ line @@ -466,7 +473,6 @@ for dir in ["ext", File::join($top_srcdir, "ext")] end MTIMES << f.mtime $setup = setup - f.close break end end unless $extstatic @@ -536,9 +542,14 @@ extend Module.new { def timestamp_file(name, target_prefix = nil) if @gemname and name == '$(TARGET_SO_DIR)' - name = "$(arch)/gems/#{@gemname}#{target_prefix}" + gem = true + name = "$(gem_platform)/$(ruby_version)/gems/#{@gemname}#{target_prefix}" end - super.sub(%r[/\.extout\.(?:-\.)?], '/.') + path = super.sub(%r[/\.extout\.(?:-\.)?], '/.') + if gem + nil while path.sub!(%r[/\.(gem_platform|ruby_version)\.-(?=\.)], '/$(\1)/') + end + path end def configuration(srcdir) @@ -546,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/[^/]+(?=/))) { @@ -716,6 +733,8 @@ begin mf.puts "ECHO1 = $(V:1=@:)" mf.puts "ECHO = $(ECHO1:0=@echo)" mf.puts "MFLAGS = -$(MAKEFLAGS)" if $nmake + mf.puts "override MFLAGS := $(filter-out -j%,$(MFLAGS))" if $gnumake + mf.puts "ext_build_dir = #{File.dirname($command_output)}" mf.puts def mf.macro(name, values, max = 70) @@ -758,6 +777,7 @@ begin mf.macro "SUBMAKEOPTS", submakeopts mf.macro "NOTE_MESG", %w[$(RUBY) $(top_srcdir)/tool/lib/colorize.rb skip] mf.macro "NOTE_NAME", %w[$(RUBY) $(top_srcdir)/tool/lib/colorize.rb fail] + %w[RM RMDIRS RMDIR RMALL].each {|w| mf.macro w, [RbConfig::CONFIG[w]]} mf.puts targets = %w[all install static install-so install-rb clean distclean realclean] targets.each do |tgt| @@ -792,16 +812,20 @@ begin exts.each do |d| d = d[0..-2] t = "#{d}#{tgt}" - if /^(dist|real)?clean$/ =~ tgt + if clean = /^(dist|real)?clean$/.match(tgt) deps = exts.select {|e|e.start_with?(d)}.map {|e|"#{e[0..-2]}#{tgt}"} - [t] - pd = ' ' + deps.join(' ') unless deps.empty? + pd = [' clean-local', *deps].join(' ') else pext = File.dirname(d) pd = " #{pext}/#{tgt}" if exts.include?("#{pext}/.") end mf.puts "#{t}:#{pd}\n\t$(Q)#{submake} $(MFLAGS) V=$(V) $(@F)" + if clean and clean.begin(1) + mf.puts "\t$(Q)$(RM) $(ext_build_dir)/exts.mk\n\t$(Q)$(RMDIRS) -p $(@D)" + end end end + mf.puts "\n""clean-local:\n\t$(Q)$(RM) $(ext_build_dir)/*~ $(ext_build_dir)/*.bak $(ext_build_dir)/core" mf.puts "\n""extso:\n" mf.puts "\t@echo EXTSO=$(EXTSO)" diff --git a/ext/fcntl/fcntl.gemspec b/ext/fcntl/fcntl.gemspec index 048e101aa5..09d3fc2568 100644 --- a/ext/fcntl/fcntl.gemspec +++ b/ext/fcntl/fcntl.gemspec @@ -3,7 +3,7 @@ Gem::Specification.new do |spec| spec.name = "fcntl" - spec.version = "1.0.1" + spec.version = "1.0.2" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/ext/fiddle/closure.c b/ext/fiddle/closure.c index a74cc53b78..892f522a62 100644 --- a/ext/fiddle/closure.c +++ b/ext/fiddle/closure.c @@ -56,6 +56,8 @@ closure_memsize(const void * ptr) const rb_data_type_t closure_data_type = { "fiddle/closure", {0, dealloc, closure_memsize,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY, }; struct callback_args { @@ -74,7 +76,7 @@ with_gvl_callback(void *ptr) VALUE rbargs = rb_iv_get(self, "@args"); VALUE ctype = rb_iv_get(self, "@ctype"); int argc = RARRAY_LENINT(rbargs); - VALUE params = rb_ary_hidden_new(argc); + VALUE params = rb_ary_tmp_new(argc); VALUE ret; VALUE cPointer; int i, type; @@ -90,7 +92,7 @@ with_gvl_callback(void *ptr) case TYPE_INT: rb_ary_push(params, INT2NUM(*(int *)x->args[i])); break; - case -TYPE_INT: + case TYPE_UINT: rb_ary_push(params, UINT2NUM(*(unsigned int *)x->args[i])); break; case TYPE_VOIDP: @@ -101,19 +103,19 @@ with_gvl_callback(void *ptr) case TYPE_LONG: rb_ary_push(params, LONG2NUM(*(long *)x->args[i])); break; - case -TYPE_LONG: + case TYPE_ULONG: rb_ary_push(params, ULONG2NUM(*(unsigned long *)x->args[i])); break; case TYPE_CHAR: rb_ary_push(params, INT2NUM(*(signed char *)x->args[i])); break; - case -TYPE_CHAR: + case TYPE_UCHAR: rb_ary_push(params, UINT2NUM(*(unsigned char *)x->args[i])); break; case TYPE_SHORT: rb_ary_push(params, INT2NUM(*(signed short *)x->args[i])); break; - case -TYPE_SHORT: + case TYPE_USHORT: rb_ary_push(params, UINT2NUM(*(unsigned short *)x->args[i])); break; case TYPE_DOUBLE: @@ -126,7 +128,7 @@ with_gvl_callback(void *ptr) case TYPE_LONG_LONG: rb_ary_push(params, LL2NUM(*(LONG_LONG *)x->args[i])); break; - case -TYPE_LONG_LONG: + case TYPE_ULONG_LONG: rb_ary_push(params, ULL2NUM(*(unsigned LONG_LONG *)x->args[i])); break; #endif @@ -149,7 +151,7 @@ with_gvl_callback(void *ptr) case TYPE_LONG: *(long *)x->resp = NUM2LONG(ret); break; - case -TYPE_LONG: + case TYPE_ULONG: *(unsigned long *)x->resp = NUM2ULONG(ret); break; case TYPE_CHAR: @@ -157,9 +159,9 @@ with_gvl_callback(void *ptr) case TYPE_INT: *(ffi_sarg *)x->resp = NUM2INT(ret); break; - case -TYPE_CHAR: - case -TYPE_SHORT: - case -TYPE_INT: + case TYPE_UCHAR: + case TYPE_USHORT: + case TYPE_UINT: *(ffi_arg *)x->resp = NUM2UINT(ret); break; case TYPE_VOIDP: @@ -175,7 +177,7 @@ with_gvl_callback(void *ptr) case TYPE_LONG_LONG: *(LONG_LONG *)x->resp = NUM2LL(ret); break; - case -TYPE_LONG_LONG: + case TYPE_ULONG_LONG: *(unsigned LONG_LONG *)x->resp = NUM2ULL(ret); break; #endif @@ -224,9 +226,27 @@ allocate(VALUE klass) return i; } +static fiddle_closure * +get_raw(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + if (!closure) { + rb_raise(rb_eArgError, "already freed: %+"PRIsVALUE, self); + } + return closure; +} + +typedef struct { + VALUE self; + int argc; + VALUE *argv; +} initialize_data; + static VALUE -initialize(int rbargc, VALUE argv[], VALUE self) +initialize_body(VALUE user_data) { + initialize_data *data = (initialize_data *)user_data; VALUE ret; VALUE args; VALUE normalized_args; @@ -237,14 +257,14 @@ initialize(int rbargc, VALUE argv[], VALUE self) ffi_status result; int i, argc; - if (2 == rb_scan_args(rbargc, argv, "21", &ret, &args, &abi)) - abi = INT2NUM(FFI_DEFAULT_ABI); + if (2 == rb_scan_args(data->argc, data->argv, "21", &ret, &args, &abi)) + abi = INT2NUM(FFI_DEFAULT_ABI); Check_Type(args, T_ARRAY); argc = RARRAY_LENINT(args); - TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl); + TypedData_Get_Struct(data->self, fiddle_closure, &closure_data_type, cl); cl->argv = (ffi_type **)xcalloc(argc + 1, sizeof(ffi_type *)); @@ -257,8 +277,8 @@ initialize(int rbargc, VALUE argv[], VALUE self) cl->argv[argc] = NULL; ret = rb_fiddle_type_ensure(ret); - rb_iv_set(self, "@ctype", ret); - rb_iv_set(self, "@args", normalized_args); + rb_iv_set(data->self, "@ctype", ret); + rb_iv_set(data->self, "@args", normalized_args); cif = &cl->cif; pcl = cl->pcl; @@ -269,38 +289,75 @@ initialize(int rbargc, VALUE argv[], VALUE self) rb_fiddle_int_to_ffi_type(NUM2INT(ret)), cl->argv); - if (FFI_OK != result) - rb_raise(rb_eRuntimeError, "error prepping CIF %d", result); + if (FFI_OK != result) { + rb_raise(rb_eRuntimeError, "error prepping CIF %d", result); + } #if USE_FFI_CLOSURE_ALLOC result = ffi_prep_closure_loc(pcl, cif, callback, - (void *)self, cl->code); + (void *)(data->self), cl->code); #else - result = ffi_prep_closure(pcl, cif, callback, (void *)self); + result = ffi_prep_closure(pcl, cif, callback, (void *)(data->self)); cl->code = (void *)pcl; i = mprotect(pcl, sizeof(*pcl), PROT_READ | PROT_EXEC); if (i) { - rb_sys_fail("mprotect"); + rb_sys_fail("mprotect"); } #endif - if (FFI_OK != result) - rb_raise(rb_eRuntimeError, "error prepping closure %d", result); + if (FFI_OK != result) { + rb_raise(rb_eRuntimeError, "error prepping closure %d", result); + } - return self; + return data->self; } static VALUE -to_i(VALUE self) +initialize_rescue(VALUE user_data, VALUE exception) { - fiddle_closure * cl; - void *code; + initialize_data *data = (initialize_data *)user_data; + dealloc(RTYPEDDATA_DATA(data->self)); + RTYPEDDATA_DATA(data->self) = NULL; + rb_exc_raise(exception); + return data->self; +} - TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl); +static VALUE +initialize(int argc, VALUE *argv, VALUE self) +{ + initialize_data data; + data.self = self; + data.argc = argc; + data.argv = argv; + return rb_rescue(initialize_body, (VALUE)&data, + initialize_rescue, (VALUE)&data); +} - code = cl->code; +static VALUE +to_i(VALUE self) +{ + fiddle_closure *closure = get_raw(self); + return PTR2NUM(closure->code); +} - return PTR2NUM(code); +static VALUE +closure_free(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + if (closure) { + dealloc(closure); + RTYPEDDATA_DATA(self) = NULL; + } + return RUBY_Qnil; +} + +static VALUE +closure_freed_p(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + return closure ? RUBY_Qfalse : RUBY_Qtrue; } void @@ -353,8 +410,24 @@ Init_fiddle_closure(void) /* * Document-method: to_i * - * Returns the memory address for this closure + * Returns the memory address for this closure. */ rb_define_method(cFiddleClosure, "to_i", to_i, 0); + + /* + * Document-method: free + * + * Free this closure explicitly. You can't use this closure anymore. + * + * If this closure is already freed, this does nothing. + */ + rb_define_method(cFiddleClosure, "free", closure_free, 0); + + /* + * Document-method: freed? + * + * Whether this closure was freed explicitly. + */ + rb_define_method(cFiddleClosure, "freed?", closure_freed_p, 0); } /* vim: set noet sw=4 sts=4 */ diff --git a/ext/fiddle/conversions.c b/ext/fiddle/conversions.c index 6e0ce36378..3b70f7de4c 100644 --- a/ext/fiddle/conversions.c +++ b/ext/fiddle/conversions.c @@ -211,32 +211,32 @@ rb_fiddle_value_to_generic(int type, VALUE *src, fiddle_generic *dst) case TYPE_CHAR: dst->schar = (signed char)NUM2INT(*src); break; - case -TYPE_CHAR: + case TYPE_UCHAR: dst->uchar = (unsigned char)NUM2UINT(*src); break; case TYPE_SHORT: dst->sshort = (unsigned short)NUM2INT(*src); break; - case -TYPE_SHORT: + case TYPE_USHORT: dst->sshort = (signed short)NUM2UINT(*src); break; case TYPE_INT: dst->sint = NUM2INT(*src); break; - case -TYPE_INT: + case TYPE_UINT: dst->uint = NUM2UINT(*src); break; case TYPE_LONG: dst->slong = NUM2LONG(*src); break; - case -TYPE_LONG: + case TYPE_ULONG: dst->ulong = NUM2ULONG(*src); break; #if HAVE_LONG_LONG case TYPE_LONG_LONG: dst->slong_long = NUM2LL(*src); break; - case -TYPE_LONG_LONG: + case TYPE_ULONG_LONG: dst->ulong_long = NUM2ULL(*src); break; #endif @@ -283,24 +283,24 @@ rb_fiddle_generic_to_value(VALUE rettype, fiddle_generic retval) PTR2NUM((void *)retval.pointer)); case TYPE_CHAR: return INT2NUM((signed char)retval.fffi_sarg); - case -TYPE_CHAR: + case TYPE_UCHAR: return INT2NUM((unsigned char)retval.fffi_arg); case TYPE_SHORT: return INT2NUM((signed short)retval.fffi_sarg); - case -TYPE_SHORT: + case TYPE_USHORT: return INT2NUM((unsigned short)retval.fffi_arg); case TYPE_INT: return INT2NUM((signed int)retval.fffi_sarg); - case -TYPE_INT: + case TYPE_UINT: return UINT2NUM((unsigned int)retval.fffi_arg); case TYPE_LONG: return LONG2NUM(retval.slong); - case -TYPE_LONG: + case TYPE_ULONG: return ULONG2NUM(retval.ulong); #if HAVE_LONG_LONG case TYPE_LONG_LONG: return LL2NUM(retval.slong_long); - case -TYPE_LONG_LONG: + case TYPE_ULONG_LONG: return ULL2NUM(retval.ulong_long); #endif case TYPE_FLOAT: diff --git a/ext/fiddle/extconf.rb b/ext/fiddle/extconf.rb index 93b4f9d4fa..cf8b5223bb 100644 --- a/ext/fiddle/extconf.rb +++ b/ext/fiddle/extconf.rb @@ -46,7 +46,7 @@ end libffi_version = nil have_libffi = false -bundle = enable_config('bundled-libffi') +bundle = with_config("libffi-source-dir") unless bundle dir_config 'libffi' @@ -67,27 +67,11 @@ unless bundle end unless have_libffi - # for https://github.com/ruby/fiddle - extlibs_rb = File.expand_path("../../bin/extlibs.rb", $srcdir) - if bundle && File.exist?(extlibs_rb) - require "fileutils" - require_relative "../../bin/extlibs" - extlibs = ExtLibs.new - cache_dir = File.expand_path("../../tmp/.download_cache", $srcdir) - ext_dir = File.expand_path("../../ext", $srcdir) - Dir.glob("#{$srcdir}/libffi-*/").each{|dir| FileUtils.rm_rf(dir)} - extlibs.run(["--cache=#{cache_dir}", ext_dir]) - end - if bundle != false - libffi_package_name = Dir.glob("#{$srcdir}/libffi-*/") - .map {|n| File.basename(n)} - .max_by {|n| n.scan(/\d+/).map(&:to_i)} - end - unless libffi_package_name - raise "missing libffi. Please install libffi." + if bundle + libffi_srcdir = libffi_package_name = bundle + else + raise "missing libffi. Please install libffi or use --with-libffi-source-dir with libffi source location." end - - libffi_srcdir = "#{$srcdir}/#{libffi_package_name}" ffi_header = 'ffi.h' libffi = Struct.new(*%I[dir srcdir builddir include lib a cflags ldflags opt arch]).new libffi.dir = libffi_package_name @@ -167,7 +151,7 @@ if libffi_version libffi_version = libffi_version.gsub(/-rc\d+/, '') libffi_version = (libffi_version.split('.').map(&:to_i) + [0,0])[0,3] $defs.push(%{-DRUBY_LIBFFI_MODVERSION=#{ '%d%03d%03d' % libffi_version }}) - puts "libffi_version: #{libffi_version.join('.')}" + warn "libffi_version: #{libffi_version.join('.')}" end case @@ -226,7 +210,7 @@ types.each do |type, signed| end if libffi - $LOCAL_LIBS.prepend("./#{libffi.a} ").strip! # to exts.mk + $LOCAL_LIBS.prepend("#{libffi.a} ").strip! # to exts.mk $INCFLAGS.gsub!(/-I#{libffi.dir}/, '-I$(LIBFFI_DIR)') end create_makefile 'fiddle' do |conf| diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index a8b5123269..c06cd5634a 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -164,137 +164,193 @@ Init_fiddle(void) */ rb_eFiddleDLError = rb_define_class_under(mFiddle, "DLError", rb_eFiddleError); - /* Document-const: TYPE_VOID + VALUE mFiddleTypes = rb_define_module_under(mFiddle, "Types"); + + /* Document-const: Fiddle::Types::VOID * * C type - void */ - rb_define_const(mFiddle, "TYPE_VOID", INT2NUM(TYPE_VOID)); + rb_define_const(mFiddleTypes, "VOID", INT2NUM(TYPE_VOID)); - /* Document-const: TYPE_VOIDP + /* Document-const: Fiddle::Types::VOIDP * * C type - void* */ - rb_define_const(mFiddle, "TYPE_VOIDP", INT2NUM(TYPE_VOIDP)); + rb_define_const(mFiddleTypes, "VOIDP", INT2NUM(TYPE_VOIDP)); - /* Document-const: TYPE_CHAR + /* Document-const: Fiddle::Types::CHAR * * C type - char */ - rb_define_const(mFiddle, "TYPE_CHAR", INT2NUM(TYPE_CHAR)); + rb_define_const(mFiddleTypes, "CHAR", INT2NUM(TYPE_CHAR)); - /* Document-const: TYPE_SHORT + /* Document-const: Fiddle::Types::UCHAR + * + * C type - unsigned char + */ + rb_define_const(mFiddleTypes, "UCHAR", INT2NUM(TYPE_UCHAR)); + + /* Document-const: Fiddle::Types::SHORT * * C type - short */ - rb_define_const(mFiddle, "TYPE_SHORT", INT2NUM(TYPE_SHORT)); + rb_define_const(mFiddleTypes, "SHORT", INT2NUM(TYPE_SHORT)); - /* Document-const: TYPE_INT + /* Document-const: Fiddle::Types::USHORT + * + * C type - unsigned short + */ + rb_define_const(mFiddleTypes, "USHORT", INT2NUM(TYPE_USHORT)); + + /* Document-const: Fiddle::Types::INT * * C type - int */ - rb_define_const(mFiddle, "TYPE_INT", INT2NUM(TYPE_INT)); + rb_define_const(mFiddleTypes, "INT", INT2NUM(TYPE_INT)); + + /* Document-const: Fiddle::Types::UINT + * + * C type - unsigned int + */ + rb_define_const(mFiddleTypes, "UINT", INT2NUM(TYPE_UINT)); + + /* Document-const: Fiddle::Types::LONG + * + * C type - long + */ + rb_define_const(mFiddleTypes, "LONG", INT2NUM(TYPE_LONG)); - /* Document-const: TYPE_LONG + /* Document-const: Fiddle::Types::ULONG * * C type - long */ - rb_define_const(mFiddle, "TYPE_LONG", INT2NUM(TYPE_LONG)); + rb_define_const(mFiddleTypes, "ULONG", INT2NUM(TYPE_ULONG)); #if HAVE_LONG_LONG - /* Document-const: TYPE_LONG_LONG + /* Document-const: Fiddle::Types::LONG_LONG + * + * C type - long long + */ + rb_define_const(mFiddleTypes, "LONG_LONG", INT2NUM(TYPE_LONG_LONG)); + + /* Document-const: Fiddle::Types::ULONG_LONG * * C type - long long */ - rb_define_const(mFiddle, "TYPE_LONG_LONG", INT2NUM(TYPE_LONG_LONG)); + rb_define_const(mFiddleTypes, "ULONG_LONG", INT2NUM(TYPE_ULONG_LONG)); #endif #ifdef TYPE_INT8_T - /* Document-const: TYPE_INT8_T + /* Document-const: Fiddle::Types::INT8_T * * C type - int8_t */ - rb_define_const(mFiddle, "TYPE_INT8_T", INT2NUM(TYPE_INT8_T)); + rb_define_const(mFiddleTypes, "INT8_T", INT2NUM(TYPE_INT8_T)); + + /* Document-const: Fiddle::Types::UINT8_T + * + * C type - uint8_t + */ + rb_define_const(mFiddleTypes, "UINT8_T", INT2NUM(TYPE_UINT8_T)); #endif #ifdef TYPE_INT16_T - /* Document-const: TYPE_INT16_T + /* Document-const: Fiddle::Types::INT16_T * * C type - int16_t */ - rb_define_const(mFiddle, "TYPE_INT16_T", INT2NUM(TYPE_INT16_T)); + rb_define_const(mFiddleTypes, "INT16_T", INT2NUM(TYPE_INT16_T)); + + /* Document-const: Fiddle::Types::UINT16_T + * + * C type - uint16_t + */ + rb_define_const(mFiddleTypes, "UINT16_T", INT2NUM(TYPE_UINT16_T)); #endif #ifdef TYPE_INT32_T - /* Document-const: TYPE_INT32_T + /* Document-const: Fiddle::Types::INT32_T * * C type - int32_t */ - rb_define_const(mFiddle, "TYPE_INT32_T", INT2NUM(TYPE_INT32_T)); + rb_define_const(mFiddleTypes, "INT32_T", INT2NUM(TYPE_INT32_T)); + + /* Document-const: Fiddle::Types::UINT32_T + * + * C type - uint32_t + */ + rb_define_const(mFiddleTypes, "UINT32_T", INT2NUM(TYPE_UINT32_T)); #endif #ifdef TYPE_INT64_T - /* Document-const: TYPE_INT64_T + /* Document-const: Fiddle::Types::INT64_T * * C type - int64_t */ - rb_define_const(mFiddle, "TYPE_INT64_T", INT2NUM(TYPE_INT64_T)); + rb_define_const(mFiddleTypes, "INT64_T", INT2NUM(TYPE_INT64_T)); + + /* Document-const: Fiddle::Types::UINT64_T + * + * C type - uint64_t + */ + rb_define_const(mFiddleTypes, "UINT64_T", INT2NUM(TYPE_UINT64_T)); #endif - /* Document-const: TYPE_FLOAT + /* Document-const: Fiddle::Types::FLOAT * * C type - float */ - rb_define_const(mFiddle, "TYPE_FLOAT", INT2NUM(TYPE_FLOAT)); + rb_define_const(mFiddleTypes, "FLOAT", INT2NUM(TYPE_FLOAT)); - /* Document-const: TYPE_DOUBLE + /* Document-const: Fiddle::Types::DOUBLE * * C type - double */ - rb_define_const(mFiddle, "TYPE_DOUBLE", INT2NUM(TYPE_DOUBLE)); + rb_define_const(mFiddleTypes, "DOUBLE", INT2NUM(TYPE_DOUBLE)); #ifdef HAVE_FFI_PREP_CIF_VAR - /* Document-const: TYPE_VARIADIC + /* Document-const: Fiddle::Types::VARIADIC * * C type - ... */ - rb_define_const(mFiddle, "TYPE_VARIADIC", INT2NUM(TYPE_VARIADIC)); + rb_define_const(mFiddleTypes, "VARIADIC", INT2NUM(TYPE_VARIADIC)); #endif - /* Document-const: TYPE_CONST_STRING + /* Document-const: Fiddle::Types::CONST_STRING * * C type - const char* ('\0' terminated const char*) */ - rb_define_const(mFiddle, "TYPE_CONST_STRING", INT2NUM(TYPE_CONST_STRING)); + rb_define_const(mFiddleTypes, "CONST_STRING", INT2NUM(TYPE_CONST_STRING)); - /* Document-const: TYPE_SIZE_T + /* Document-const: Fiddle::Types::SIZE_T * * C type - size_t */ - rb_define_const(mFiddle, "TYPE_SIZE_T", INT2NUM(TYPE_SIZE_T)); + rb_define_const(mFiddleTypes, "SIZE_T", INT2NUM(TYPE_SIZE_T)); - /* Document-const: TYPE_SSIZE_T + /* Document-const: Fiddle::Types::SSIZE_T * * C type - ssize_t */ - rb_define_const(mFiddle, "TYPE_SSIZE_T", INT2NUM(TYPE_SSIZE_T)); + rb_define_const(mFiddleTypes, "SSIZE_T", INT2NUM(TYPE_SSIZE_T)); - /* Document-const: TYPE_PTRDIFF_T + /* Document-const: Fiddle::Types::PTRDIFF_T * * C type - ptrdiff_t */ - rb_define_const(mFiddle, "TYPE_PTRDIFF_T", INT2NUM(TYPE_PTRDIFF_T)); + rb_define_const(mFiddleTypes, "PTRDIFF_T", INT2NUM(TYPE_PTRDIFF_T)); - /* Document-const: TYPE_INTPTR_T + /* Document-const: Fiddle::Types::INTPTR_T * * C type - intptr_t */ - rb_define_const(mFiddle, "TYPE_INTPTR_T", INT2NUM(TYPE_INTPTR_T)); + rb_define_const(mFiddleTypes, "INTPTR_T", INT2NUM(TYPE_INTPTR_T)); - /* Document-const: TYPE_UINTPTR_T + /* Document-const: Fiddle::Types::UINTPTR_T * * C type - uintptr_t */ - rb_define_const(mFiddle, "TYPE_UINTPTR_T", INT2NUM(TYPE_UINTPTR_T)); + rb_define_const(mFiddleTypes, "UINTPTR_T", INT2NUM(TYPE_UINTPTR_T)); /* Document-const: ALIGN_VOIDP * @@ -422,30 +478,60 @@ Init_fiddle(void) */ rb_define_const(mFiddle, "SIZEOF_CHAR", INT2NUM(sizeof(char))); + /* Document-const: SIZEOF_UCHAR + * + * size of a unsigned char + */ + rb_define_const(mFiddle, "SIZEOF_UCHAR", INT2NUM(sizeof(unsigned char))); + /* Document-const: SIZEOF_SHORT * * size of a short */ rb_define_const(mFiddle, "SIZEOF_SHORT", INT2NUM(sizeof(short))); + /* Document-const: SIZEOF_USHORT + * + * size of a unsigned short + */ + rb_define_const(mFiddle, "SIZEOF_USHORT", INT2NUM(sizeof(unsigned short))); + /* Document-const: SIZEOF_INT * * size of an int */ rb_define_const(mFiddle, "SIZEOF_INT", INT2NUM(sizeof(int))); + /* Document-const: SIZEOF_UINT + * + * size of an unsigned int + */ + rb_define_const(mFiddle, "SIZEOF_UINT", INT2NUM(sizeof(unsigned int))); + /* Document-const: SIZEOF_LONG * * size of a long */ rb_define_const(mFiddle, "SIZEOF_LONG", INT2NUM(sizeof(long))); + /* Document-const: SIZEOF_ULONG + * + * size of a unsigned long + */ + rb_define_const(mFiddle, "SIZEOF_ULONG", INT2NUM(sizeof(unsigned long))); + #if HAVE_LONG_LONG /* Document-const: SIZEOF_LONG_LONG * * size of a long long */ rb_define_const(mFiddle, "SIZEOF_LONG_LONG", INT2NUM(sizeof(LONG_LONG))); + + /* Document-const: SIZEOF_ULONG_LONG + * + * size of a unsigned long long + */ + rb_define_const(mFiddle, "SIZEOF_ULONG_LONG", INT2NUM(sizeof(unsigned LONG_LONG))); #endif /* Document-const: SIZEOF_INT8_T @@ -454,24 +540,48 @@ Init_fiddle(void) */ rb_define_const(mFiddle, "SIZEOF_INT8_T", INT2NUM(sizeof(int8_t))); + /* Document-const: SIZEOF_UINT8_T + * + * size of a uint8_t + */ + rb_define_const(mFiddle, "SIZEOF_UINT8_T", INT2NUM(sizeof(uint8_t))); + /* Document-const: SIZEOF_INT16_T * * size of a int16_t */ rb_define_const(mFiddle, "SIZEOF_INT16_T", INT2NUM(sizeof(int16_t))); + /* Document-const: SIZEOF_UINT16_T + * + * size of a uint16_t + */ + rb_define_const(mFiddle, "SIZEOF_UINT16_T", INT2NUM(sizeof(uint16_t))); + /* Document-const: SIZEOF_INT32_T * * size of a int32_t */ rb_define_const(mFiddle, "SIZEOF_INT32_T", INT2NUM(sizeof(int32_t))); + /* Document-const: SIZEOF_UINT32_T + * + * size of a uint32_t + */ + rb_define_const(mFiddle, "SIZEOF_UINT32_T", INT2NUM(sizeof(uint32_t))); + /* Document-const: SIZEOF_INT64_T * * size of a int64_t */ rb_define_const(mFiddle, "SIZEOF_INT64_T", INT2NUM(sizeof(int64_t))); + /* Document-const: SIZEOF_UINT64_T + * + * size of a uint64_t + */ + rb_define_const(mFiddle, "SIZEOF_UINT64_T", INT2NUM(sizeof(uint64_t))); + /* Document-const: SIZEOF_FLOAT * * size of a float @@ -540,6 +650,30 @@ Init_fiddle(void) rb_define_module_function(mFiddle, "realloc", rb_fiddle_realloc, 2); rb_define_module_function(mFiddle, "free", rb_fiddle_free, 1); + /* Document-const: Qtrue + * + * The value of Qtrue + */ + rb_define_const(mFiddle, "Qtrue", INT2NUM(Qtrue)); + + /* Document-const: Qfalse + * + * The value of Qfalse + */ + rb_define_const(mFiddle, "Qfalse", INT2NUM(Qfalse)); + + /* Document-const: Qnil + * + * The value of Qnil + */ + rb_define_const(mFiddle, "Qnil", INT2NUM(Qnil)); + + /* Document-const: Qundef + * + * The value of Qundef + */ + rb_define_const(mFiddle, "Qundef", INT2NUM(Qundef)); + Init_fiddle_function(); Init_fiddle_closure(); Init_fiddle_handle(); diff --git a/ext/fiddle/fiddle.gemspec b/ext/fiddle/fiddle.gemspec index a9c0ec4026..878109395b 100644 --- a/ext/fiddle/fiddle.gemspec +++ b/ext/fiddle/fiddle.gemspec @@ -20,15 +20,12 @@ Gem::Specification.new do |spec| "LICENSE.txt", "README.md", "Rakefile", - "bin/downloader.rb", - "bin/extlibs.rb", "ext/fiddle/closure.c", "ext/fiddle/closure.h", "ext/fiddle/conversions.c", "ext/fiddle/conversions.h", "ext/fiddle/depend", "ext/fiddle/extconf.rb", - "ext/fiddle/extlibs", "ext/fiddle/fiddle.c", "ext/fiddle/fiddle.h", "ext/fiddle/function.c", diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 9de62a58cc..10eb9ceedb 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -111,11 +111,16 @@ #define TYPE_VOID 0 #define TYPE_VOIDP 1 #define TYPE_CHAR 2 +#define TYPE_UCHAR -TYPE_CHAR #define TYPE_SHORT 3 +#define TYPE_USHORT -TYPE_SHORT #define TYPE_INT 4 +#define TYPE_UINT -TYPE_INT #define TYPE_LONG 5 +#define TYPE_ULONG -TYPE_LONG #if HAVE_LONG_LONG #define TYPE_LONG_LONG 6 +#define TYPE_ULONG_LONG -TYPE_LONG_LONG #endif #define TYPE_FLOAT 7 #define TYPE_DOUBLE 8 @@ -123,11 +128,18 @@ #define TYPE_CONST_STRING 10 #define TYPE_INT8_T TYPE_CHAR +#define TYPE_UINT8_T -TYPE_INT8_T + #if SIZEOF_SHORT == 2 # define TYPE_INT16_T TYPE_SHORT #elif SIZEOF_INT == 2 # define TYPE_INT16_T TYPE_INT #endif + +#ifdef TYPE_INT16_T +# define TYPE_UINT16_T -TYPE_INT16_T +#endif + #if SIZEOF_SHORT == 4 # define TYPE_INT32_T TYPE_SHORT #elif SIZEOF_INT == 4 @@ -135,6 +147,11 @@ #elif SIZEOF_LONG == 4 # define TYPE_INT32_T TYPE_LONG #endif + +#ifdef TYPE_INT32_T +#define TYPE_UINT32_T -TYPE_INT32_T +#endif + #if SIZEOF_INT == 8 # define TYPE_INT64_T TYPE_INT #elif SIZEOF_LONG == 8 @@ -143,6 +160,10 @@ # define TYPE_INT64_T TYPE_LONG_LONG #endif +#ifdef TYPE_INT64_T +#define TYPE_UINT64_T -TYPE_INT64_T +#endif + #ifndef TYPE_SSIZE_T # if SIZEOF_SIZE_T == SIZEOF_INT # define TYPE_SSIZE_T TYPE_INT diff --git a/ext/fiddle/handle.c b/ext/fiddle/handle.c index 76b90909d3..ae8cc3a581 100644 --- a/ext/fiddle/handle.c +++ b/ext/fiddle/handle.c @@ -321,8 +321,10 @@ rb_fiddle_handle_s_sym(VALUE self, VALUE sym) return fiddle_handle_sym(RTLD_NEXT, sym); } -static VALUE -fiddle_handle_sym(void *handle, VALUE symbol) +typedef void (*fiddle_void_func)(void); + +static fiddle_void_func +fiddle_handle_find_func(void *handle, VALUE symbol) { #if defined(HAVE_DLERROR) const char *err; @@ -330,13 +332,13 @@ fiddle_handle_sym(void *handle, VALUE symbol) #else # define CHECK_DLERROR #endif - void (*func)(); + fiddle_void_func func; const char *name = StringValueCStr(symbol); #ifdef HAVE_DLERROR dlerror(); #endif - func = (void (*)())(VALUE)dlsym(handle, name); + func = (fiddle_void_func)(VALUE)dlsym(handle, name); CHECK_DLERROR; #if defined(FUNC_STDCALL) if( !func ){ @@ -379,6 +381,53 @@ fiddle_handle_sym(void *handle, VALUE symbol) xfree(name_n); } #endif + + return func; +} + +static VALUE +rb_fiddle_handle_s_sym_defined(VALUE self, VALUE sym) +{ + fiddle_void_func func; + + func = fiddle_handle_find_func(RTLD_NEXT, sym); + + if( func ) { + return PTR2NUM(func); + } + else { + return Qnil; + } +} + +static VALUE +rb_fiddle_handle_sym_defined(VALUE self, VALUE sym) +{ + struct dl_handle *fiddle_handle; + fiddle_void_func func; + + TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle); + if( ! fiddle_handle->open ){ + rb_raise(rb_eFiddleDLError, "closed handle"); + } + + func = fiddle_handle_find_func(fiddle_handle->ptr, sym); + + if( func ) { + return PTR2NUM(func); + } + else { + return Qnil; + } +} + +static VALUE +fiddle_handle_sym(void *handle, VALUE symbol) +{ + fiddle_void_func func; + + func = fiddle_handle_find_func(handle, symbol); + if( !func ){ rb_raise(rb_eFiddleDLError, "unknown symbol \"%"PRIsVALUE"\"", symbol); } @@ -468,6 +517,7 @@ Init_fiddle_handle(void) rb_cHandle = rb_define_class_under(mFiddle, "Handle", rb_cObject); rb_define_alloc_func(rb_cHandle, rb_fiddle_handle_s_allocate); rb_define_singleton_method(rb_cHandle, "sym", rb_fiddle_handle_s_sym, 1); + rb_define_singleton_method(rb_cHandle, "sym_defined?", rb_fiddle_handle_s_sym_defined, 1); rb_define_singleton_method(rb_cHandle, "[]", rb_fiddle_handle_s_sym, 1); /* Document-const: NEXT @@ -526,6 +576,7 @@ Init_fiddle_handle(void) rb_define_method(rb_cHandle, "close", rb_fiddle_handle_close, 0); rb_define_method(rb_cHandle, "sym", rb_fiddle_handle_sym, 1); rb_define_method(rb_cHandle, "[]", rb_fiddle_handle_sym, 1); + rb_define_method(rb_cHandle, "sym_defined?", rb_fiddle_handle_sym_defined, 1); rb_define_method(rb_cHandle, "file_name", rb_fiddle_handle_file_name, 0); rb_define_method(rb_cHandle, "disable_close", rb_fiddle_handle_disable_close, 0); rb_define_method(rb_cHandle, "enable_close", rb_fiddle_handle_enable_close, 0); diff --git a/ext/fiddle/lib/fiddle.rb b/ext/fiddle/lib/fiddle.rb index 4512989310..6137c487c6 100644 --- a/ext/fiddle/lib/fiddle.rb +++ b/ext/fiddle/lib/fiddle.rb @@ -58,7 +58,36 @@ module Fiddle # # See Fiddle::Handle.new for more. def dlopen library - Fiddle::Handle.new library + begin + Fiddle::Handle.new(library) + rescue DLError => error + case RUBY_PLATFORM + when /linux/ + case error.message + when /\A(\/.+?): (?:invalid ELF header|file too short)/ + # This may be a linker script: + # https://sourceware.org/binutils/docs/ld.html#Scripts + path = $1 + else + raise + end + else + raise + end + + File.open(path) do |input| + input.each_line do |line| + case line + when /\A\s*(?:INPUT|GROUP)\s*\(\s*([^\s,\)]+)/ + # TODO: Should we support multiple files? + return dlopen($1) + end + end + end + + # Not found + raise + end end module_function :dlopen @@ -67,4 +96,8 @@ module Fiddle RTLD_GLOBAL = Handle::RTLD_GLOBAL # :nodoc: RTLD_LAZY = Handle::RTLD_LAZY # :nodoc: RTLD_NOW = Handle::RTLD_NOW # :nodoc: + + Fiddle::Types.constants.each do |type| + const_set "TYPE_#{type}", Fiddle::Types.const_get(type) + end end diff --git a/ext/fiddle/lib/fiddle/closure.rb b/ext/fiddle/lib/fiddle/closure.rb index c865a63c20..7e0077ea52 100644 --- a/ext/fiddle/lib/fiddle/closure.rb +++ b/ext/fiddle/lib/fiddle/closure.rb @@ -1,6 +1,31 @@ # frozen_string_literal: true module Fiddle class Closure + class << self + # Create a new closure. If a block is given, the created closure + # is automatically freed after the given block is executed. + # + # The all given arguments are passed to Fiddle::Closure.new. So + # using this method without block equals to Fiddle::Closure.new. + # + # == Example + # + # Fiddle::Closure.create(TYPE_INT, [TYPE_INT]) do |closure| + # # closure is freed automatically when this block is finished. + # end + def create(*args) + if block_given? + closure = new(*args) + begin + yield(closure) + ensure + closure.free + end + else + new(*args) + end + end + end # the C type of the return of the FFI closure attr_reader :ctype diff --git a/ext/fiddle/lib/fiddle/cparser.rb b/ext/fiddle/lib/fiddle/cparser.rb index 93a05513c9..9a70402953 100644 --- a/ext/fiddle/lib/fiddle/cparser.rb +++ b/ext/fiddle/lib/fiddle/cparser.rb @@ -164,23 +164,23 @@ module Fiddle unless Fiddle.const_defined?(:TYPE_LONG_LONG) raise(RuntimeError, "unsupported type: #{ty}") end - return -TYPE_LONG_LONG + return TYPE_ULONG_LONG when /\A(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?\z/ return TYPE_LONG when /\Aunsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?\z/ - return -TYPE_LONG + return TYPE_ULONG when /\A(?:signed\s+)?int(?:\s+\w+)?\z/ return TYPE_INT when /\A(?:unsigned\s+int|uint)(?:\s+\w+)?\z/ - return -TYPE_INT + return TYPE_UINT when /\A(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?\z/ return TYPE_SHORT when /\Aunsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?\z/ - return -TYPE_SHORT + return TYPE_USHORT when /\A(?:signed\s+)?char(?:\s+\w+)?\z/ return TYPE_CHAR when /\Aunsigned\s+char(?:\s+\w+)?\z/ - return -TYPE_CHAR + return TYPE_UCHAR when /\Aint8_t(?:\s+\w+)?\z/ unless Fiddle.const_defined?(:TYPE_INT8_T) raise(RuntimeError, "unsupported type: #{ty}") @@ -190,7 +190,7 @@ module Fiddle unless Fiddle.const_defined?(:TYPE_INT8_T) raise(RuntimeError, "unsupported type: #{ty}") end - return -TYPE_INT8_T + return TYPE_UINT8_T when /\Aint16_t(?:\s+\w+)?\z/ unless Fiddle.const_defined?(:TYPE_INT16_T) raise(RuntimeError, "unsupported type: #{ty}") @@ -200,7 +200,7 @@ module Fiddle unless Fiddle.const_defined?(:TYPE_INT16_T) raise(RuntimeError, "unsupported type: #{ty}") end - return -TYPE_INT16_T + return TYPE_UINT16_T when /\Aint32_t(?:\s+\w+)?\z/ unless Fiddle.const_defined?(:TYPE_INT32_T) raise(RuntimeError, "unsupported type: #{ty}") @@ -210,7 +210,7 @@ module Fiddle unless Fiddle.const_defined?(:TYPE_INT32_T) raise(RuntimeError, "unsupported type: #{ty}") end - return -TYPE_INT32_T + return TYPE_UINT32_T when /\Aint64_t(?:\s+\w+)?\z/ unless Fiddle.const_defined?(:TYPE_INT64_T) raise(RuntimeError, "unsupported type: #{ty}") @@ -220,7 +220,7 @@ module Fiddle unless Fiddle.const_defined?(:TYPE_INT64_T) raise(RuntimeError, "unsupported type: #{ty}") end - return -TYPE_INT64_T + return TYPE_UINT64_T when /\Afloat(?:\s+\w+)?\z/ return TYPE_FLOAT when /\Adouble(?:\s+\w+)?\z/ diff --git a/ext/fiddle/lib/fiddle/pack.rb b/ext/fiddle/lib/fiddle/pack.rb index eb99fe090d..545b985d50 100644 --- a/ext/fiddle/lib/fiddle/pack.rb +++ b/ext/fiddle/lib/fiddle/pack.rb @@ -11,10 +11,10 @@ module Fiddle TYPE_LONG => ALIGN_LONG, TYPE_FLOAT => ALIGN_FLOAT, TYPE_DOUBLE => ALIGN_DOUBLE, - -TYPE_CHAR => ALIGN_CHAR, - -TYPE_SHORT => ALIGN_SHORT, - -TYPE_INT => ALIGN_INT, - -TYPE_LONG => ALIGN_LONG, + TYPE_UCHAR => ALIGN_CHAR, + TYPE_USHORT => ALIGN_SHORT, + TYPE_UINT => ALIGN_INT, + TYPE_ULONG => ALIGN_LONG, } PACK_MAP = { @@ -25,10 +25,10 @@ module Fiddle TYPE_LONG => "l!", TYPE_FLOAT => "f", TYPE_DOUBLE => "d", - -TYPE_CHAR => "C", - -TYPE_SHORT => "S!", - -TYPE_INT => "I!", - -TYPE_LONG => "L!", + TYPE_UCHAR => "C", + TYPE_USHORT => "S!", + TYPE_UINT => "I!", + TYPE_ULONG => "L!", } SIZE_MAP = { @@ -39,16 +39,16 @@ module Fiddle TYPE_LONG => SIZEOF_LONG, TYPE_FLOAT => SIZEOF_FLOAT, TYPE_DOUBLE => SIZEOF_DOUBLE, - -TYPE_CHAR => SIZEOF_CHAR, - -TYPE_SHORT => SIZEOF_SHORT, - -TYPE_INT => SIZEOF_INT, - -TYPE_LONG => SIZEOF_LONG, + TYPE_UCHAR => SIZEOF_CHAR, + TYPE_USHORT => SIZEOF_SHORT, + TYPE_UINT => SIZEOF_INT, + TYPE_ULONG => SIZEOF_LONG, } if defined?(TYPE_LONG_LONG) - ALIGN_MAP[TYPE_LONG_LONG] = ALIGN_MAP[-TYPE_LONG_LONG] = ALIGN_LONG_LONG + ALIGN_MAP[TYPE_LONG_LONG] = ALIGN_MAP[TYPE_ULONG_LONG] = ALIGN_LONG_LONG PACK_MAP[TYPE_LONG_LONG] = "q" - PACK_MAP[-TYPE_LONG_LONG] = "Q" - SIZE_MAP[TYPE_LONG_LONG] = SIZE_MAP[-TYPE_LONG_LONG] = SIZEOF_LONG_LONG + PACK_MAP[TYPE_ULONG_LONG] = "Q" + SIZE_MAP[TYPE_LONG_LONG] = SIZE_MAP[TYPE_ULONG_LONG] = SIZEOF_LONG_LONG PACK_MAP[TYPE_VOIDP] = "Q" if SIZEOF_LONG_LONG == SIZEOF_VOIDP end diff --git a/ext/fiddle/lib/fiddle/version.rb b/ext/fiddle/lib/fiddle/version.rb index db6504b650..719dc62e37 100644 --- a/ext/fiddle/lib/fiddle/version.rb +++ b/ext/fiddle/lib/fiddle/version.rb @@ -1,3 +1,3 @@ module Fiddle - VERSION = "1.1.0" + VERSION = "1.1.1" end diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 4ec24178c4..21454a73fa 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -75,7 +75,7 @@ getattr(int fd, conmode *t) #define SET_LAST_ERROR (0) #endif -static ID id_getc, id_console, id_close, id_min, id_time, id_intr; +static ID id_getc, id_console, id_close; #if ENABLE_IO_GETPASS static ID id_gets, id_chomp_bang; #endif @@ -112,18 +112,34 @@ rb_f_send(int argc, VALUE *argv, VALUE recv) } #endif +enum rawmode_opt_ids { + kwd_min, + kwd_time, + kwd_intr, + rawmode_opt_id_count +}; +static ID rawmode_opt_ids[rawmode_opt_id_count]; + typedef struct { int vmin; int vtime; int intr; } rawmode_arg_t; +#ifndef UNDEF_P +# define UNDEF_P(obj) ((obj) == Qundef) +#endif +#ifndef NIL_OR_UNDEF_P +# define NIL_OR_UNDEF_P(obj) (NIL_P(obj) || UNDEF_P(obj)) +#endif + static rawmode_arg_t * rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts) { int argc = *argcp; rawmode_arg_t *optp = NULL; VALUE vopts = Qnil; + VALUE optvals[rawmode_opt_id_count]; #ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS argc = rb_scan_args(argc, argv, "*:", NULL, &vopts); #else @@ -138,19 +154,20 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t * } #endif rb_check_arity(argc, min_argc, max_argc); - if (!NIL_P(vopts)) { - VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min)); - VALUE vtime = rb_hash_aref(vopts, ID2SYM(id_time)); - VALUE intr = rb_hash_aref(vopts, ID2SYM(id_intr)); + if (rb_get_kwargs(vopts, rawmode_opt_ids, + 0, rawmode_opt_id_count, optvals)) { + VALUE vmin = optvals[kwd_min]; + VALUE vtime = optvals[kwd_time]; + VALUE intr = optvals[kwd_intr]; /* default values by `stty raw` */ opts->vmin = 1; opts->vtime = 0; opts->intr = 0; - if (!NIL_P(vmin)) { + if (!NIL_OR_UNDEF_P(vmin)) { opts->vmin = NUM2INT(vmin); optp = opts; } - if (!NIL_P(vtime)) { + if (!NIL_OR_UNDEF_P(vtime)) { VALUE v10 = INT2FIX(10); vtime = rb_funcall3(vtime, '*', 1, &v10); opts->vtime = NUM2INT(vtime); @@ -165,6 +182,7 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t * opts->intr = 0; optp = opts; break; + case Qundef: case Qnil: break; default: @@ -1633,9 +1651,11 @@ Init_console(void) #endif id_console = rb_intern("console"); id_close = rb_intern("close"); - id_min = rb_intern("min"); - id_time = rb_intern("time"); - id_intr = rb_intern("intr"); +#define init_rawmode_opt_id(name) \ + rawmode_opt_ids[kwd_##name] = rb_intern(#name) + init_rawmode_opt_id(min); + init_rawmode_opt_id(time); + init_rawmode_opt_id(intr); #ifndef HAVE_RB_F_SEND id___send__ = rb_intern("__send__"); #endif 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/nonblock/io-nonblock.gemspec b/ext/io/nonblock/io-nonblock.gemspec index f81d4fda0a..d6df21a84d 100644 --- a/ext/io/nonblock/io-nonblock.gemspec +++ b/ext/io/nonblock/io-nonblock.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "io-nonblock" - spec.version = "0.1.1" + spec.version = "0.2.0" spec.authors = ["Nobu Nakada"] spec.email = ["nobu@ruby-lang.org"] 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/VERSION b/ext/json/VERSION index 097a15a2af..ec1cf33c3f 100644 --- a/ext/json/VERSION +++ b/ext/json/VERSION @@ -1 +1 @@ -2.6.2 +2.6.3 diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 9bedb65fa7..3d4326d836 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module JSON # JSON version - VERSION = '2.6.2' + VERSION = '2.6.3' VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc: VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: 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/nkf/nkf.gemspec b/ext/nkf/nkf.gemspec index 2d77c71ff8..7f3bd4a4b1 100644 --- a/ext/nkf/nkf.gemspec +++ b/ext/nkf/nkf.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "nkf" - spec.version = "0.1.1" + spec.version = "0.1.2" spec.authors = ["NARUSE Yui"] spec.email = ["naruse@airemix.jp"] diff --git a/ext/objspace/depend b/ext/objspace/depend index c4da8031cc..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,9 +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) @@ -533,9 +549,13 @@ objspace_dump.o: $(top_srcdir)/ccan/check_type/check_type.h objspace_dump.o: $(top_srcdir)/ccan/container_of/container_of.h objspace_dump.o: $(top_srcdir)/ccan/list/list.h objspace_dump.o: $(top_srcdir)/ccan/str/str.h +objspace_dump.o: $(top_srcdir)/constant.h objspace_dump.o: $(top_srcdir)/gc.h +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 @@ -544,12 +564,15 @@ objspace_dump.o: $(top_srcdir)/internal/sanitizers.h objspace_dump.o: $(top_srcdir)/internal/serial.h objspace_dump.o: $(top_srcdir)/internal/static_assert.h objspace_dump.o: $(top_srcdir)/internal/string.h +objspace_dump.o: $(top_srcdir)/internal/variable.h objspace_dump.o: $(top_srcdir)/internal/vm.h objspace_dump.o: $(top_srcdir)/internal/warnings.h objspace_dump.o: $(top_srcdir)/method.h objspace_dump.o: $(top_srcdir)/node.h objspace_dump.o: $(top_srcdir)/ruby_assert.h objspace_dump.o: $(top_srcdir)/ruby_atomic.h +objspace_dump.o: $(top_srcdir)/shape.h +objspace_dump.o: $(top_srcdir)/symbol.h objspace_dump.o: $(top_srcdir)/thread_pthread.h objspace_dump.o: $(top_srcdir)/vm_core.h objspace_dump.o: $(top_srcdir)/vm_opts.h diff --git a/ext/objspace/lib/objspace.rb b/ext/objspace/lib/objspace.rb index 0298b0646c..6865fdda4c 100644 --- a/ext/objspace/lib/objspace.rb +++ b/ext/objspace/lib/objspace.rb @@ -6,14 +6,15 @@ module ObjectSpace class << self private :_dump private :_dump_all + private :_dump_shapes end 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. # @@ -42,38 +43,88 @@ 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. + # + # _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+. # - # Dump the contents of the ruby heap as JSON. + # 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. # - # _since_ must be a non-negative integer or +nil+. + # If _since_ is omitted or is +nil+, all objects are dumped. # - # 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. + # _shapes_ must be a boolean or a non-negative integer. # - # Objects that were allocated without object allocation tracing enabled - # are ignored. See ::trace_object_allocations for more information and - # examples. + # 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 _since_ is omitted or is +nil+, all objects 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) + # + # 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 + require 'tempfile' + Tempfile.create(%w(rubyheap .json)) + when :stdout + STDOUT + when :string + +'' + when IO + output + else + raise ArgumentError, "wrong output option: #{output.inspect}" + end + + shapes = 0 if shapes == true + ret = _dump_all(out, full, since, shapes) + return nil if output == :stdout + ret + 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..." + # + # 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 <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. # 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) + def dump_shapes(output: :file, since: 0) out = case output when :file, nil require 'tempfile' - Tempfile.create(%w(rubyheap .json)) + Tempfile.create(%w(rubyshapes .json)) when :stdout STDOUT when :string @@ -84,7 +135,7 @@ module ObjectSpace raise ArgumentError, "wrong output option: #{output.inspect}" end - ret = _dump_all(out, full, since) + ret = _dump_shapes(out, since) return nil if output == :stdout ret end 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.c b/ext/objspace/objspace.c index 0b1b094325..ca08604c95 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -493,6 +493,7 @@ count_nodes(int argc, VALUE *argv, VALUE os) COUNT_NODE(NODE_ARYPTN); COUNT_NODE(NODE_FNDPTN); COUNT_NODE(NODE_HSHPTN); + COUNT_NODE(NODE_ERROR); #undef COUNT_NODE case NODE_LAST: break; } diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 2917d49331..c3cc9a1e7b 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -13,10 +13,15 @@ **********************************************************************/ #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" +#include "symbol.h" +#include "shape.h" #include "node.h" #include "objspace.h" #include "ruby/debug.h" @@ -41,6 +46,7 @@ struct dump_config { unsigned int full_heap: 1; unsigned int partial_dump; size_t since; + size_t shapes_since; unsigned long buffer_len; char buffer[BUFFER_CAPACITY]; }; @@ -349,6 +355,20 @@ dump_append_string_content(struct dump_config *dc, VALUE obj) } } +static inline void +dump_append_id(struct dump_config *dc, ID id) +{ + if (is_instance_id(id)) { + dump_append_string_value(dc, rb_sym2str(ID2SYM(id))); + } + else { + dump_append(dc, "\"ID_INTERNAL("); + dump_append_sizet(dc, rb_id_to_serial(id)); + dump_append(dc, ")\""); + } +} + + static void dump_object(VALUE obj, struct dump_config *dc) { @@ -365,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; @@ -381,6 +405,10 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, obj_type(obj)); dump_append(dc, "\""); + size_t shape_id = rb_shape_get_shape_id(obj); + 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); @@ -456,7 +484,7 @@ dump_object(VALUE obj, struct dump_config *dc) case T_ARRAY: dump_append(dc, ", \"length\":"); dump_append_ld(dc, RARRAY_LEN(obj)); - if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED)) + if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_SHARED_FLAG)) dump_append(dc, ", \"shared\":true"); if (FL_TEST(obj, RARRAY_EMBED_FLAG)) dump_append(dc, ", \"embedded\":true"); @@ -470,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\":"); @@ -514,7 +545,10 @@ dump_object(VALUE obj, struct dump_config *dc) case T_OBJECT: dump_append(dc, ", \"ivars\":"); - dump_append_lu(dc, ROBJECT_NUMIV(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: @@ -618,7 +652,7 @@ root_obj_i(const char *category, VALUE obj, void *data) } static void -dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) +dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since, VALUE shapes) { dc->full_heap = 0; @@ -644,6 +678,8 @@ dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) else { dc->partial_dump = 0; } + + dc->shapes_since = RTEST(shapes) ? NUM2SIZET(shapes) : 0; } static VALUE @@ -660,6 +696,7 @@ dump_result(struct dump_config *dc) } } +/* :nodoc: */ static VALUE objspace_dump(VALUE os, VALUE obj, VALUE output) { @@ -668,18 +705,87 @@ objspace_dump(VALUE os, VALUE obj, VALUE output) dc.cur_page_slot_size = rb_gc_obj_slot_size(obj); } - dump_output(&dc, output, Qnil, Qnil); + dump_output(&dc, output, Qnil, Qnil, Qnil); dump_object(obj, &dc); return dump_result(&dc); } +static void +shape_i(rb_shape_t *shape, void *data) +{ + struct dump_config *dc = (struct dump_config *)data; + + size_t shape_id = rb_shape_id(shape); + if (shape_id < dc->shapes_since) { + return; + } + + dump_append(dc, "{\"address\":"); + dump_append_ref(dc, (VALUE)shape); + + dump_append(dc, ", \"type\":\"SHAPE\", \"id\":"); + dump_append_sizet(dc, shape_id); + + if (shape->type != SHAPE_ROOT) { + dump_append(dc, ", \"parent_id\":"); + dump_append_lu(dc, shape->parent_id); + } + + dump_append(dc, ", \"depth\":"); + dump_append_sizet(dc, rb_shape_depth(shape)); + + dump_append(dc, ", \"shape_type\":"); + switch((enum shape_type)shape->type) { + case SHAPE_ROOT: + dump_append(dc, "\"ROOT\""); + break; + case SHAPE_IVAR: + dump_append(dc, "\"IVAR\""); + + dump_append(dc, ",\"edge_name\":"); + dump_append_id(dc, shape->edge_name); + + break; + case SHAPE_FROZEN: + dump_append(dc, "\"FROZEN\""); + break; + case SHAPE_CAPACITY_CHANGE: + dump_append(dc, "\"CAPACITY_CHANGE\""); + dump_append(dc, ", \"capacity\":"); + dump_append_sizet(dc, shape->capacity); + break; + case SHAPE_INITIAL_CAPACITY: + dump_append(dc, "\"INITIAL_CAPACITY\""); + dump_append(dc, ", \"capacity\":"); + dump_append_sizet(dc, shape->capacity); + break; + 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"); + } + + dump_append(dc, ", \"edges\":"); + dump_append_sizet(dc, rb_shape_edges_count(shape)); + + dump_append(dc, ", \"memsize\":"); + dump_append_sizet(dc, rb_shape_memsize(shape)); + + dump_append(dc, "}\n"); +} + +/* :nodoc: */ static VALUE -objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since) +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) { struct dump_config dc = {0,}; - dump_output(&dc, output, full, since); + dump_output(&dc, output, full, since, shapes); if (!dc.partial_dump || dc.since == 0) { /* dump roots */ @@ -687,12 +793,29 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since) if (dc.roots) dump_append(&dc, "]}\n"); } + if (RTEST(shapes)) { + rb_shape_each_shape(shape_i, &dc); + } + /* dump all objects */ rb_objspace_each_objects(heap_i, &dc); return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +{ + struct dump_config dc = {0,}; + dump_output(&dc, output, Qfalse, Qnil, shapes); + + if (RTEST(shapes)) { + rb_shape_each_shape(shape_i, &dc); + } + return dump_result(&dc); +} + void Init_objspace_dump(VALUE rb_mObjSpace) { @@ -702,7 +825,8 @@ Init_objspace_dump(VALUE rb_mObjSpace) #endif rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); - rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 3); + rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 4); + rb_define_module_function(rb_mObjSpace, "_dump_shapes", objspace_dump_shapes, 2); /* force create static IDs */ rb_obj_gc_flags(rb_mObjSpace, 0, 0); diff --git a/ext/openssl/History.md b/ext/openssl/History.md index 479ec3b4a2..1e0df7dd87 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,77 @@ +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 +============= + +Merged changes in 2.1.4 and 2.2.2. Additionally, the following issues are fixed +by this release. + +Bug fixes +--------- + +* Add missing type check in OpenSSL::PKey::PKey#sign's optional parameters. + [[GitHub #531]](https://github.com/ruby/openssl/pull/531) +* Work around OpenSSL 3.0's HMAC issues with a zero-length key. + [[GitHub #538]](https://github.com/ruby/openssl/pull/538) +* Fix a regression in OpenSSL::PKey::DSA.generate's default of 'q' size. + [[GitHub #483]](https://github.com/ruby/openssl/issues/483) + [[GitHub #539]](https://github.com/ruby/openssl/pull/539) +* Restore OpenSSL::PKey.read's ability to decode "openssl ecparam -genkey" + output when linked against OpenSSL 3.0. + [[GitHub #535]](https://github.com/ruby/openssl/pull/535) + [[GitHub #540]](https://github.com/ruby/openssl/pull/540) +* Restore error checks in OpenSSL::PKey::EC#{to_der,to_pem}. + [[GitHub #541]](https://github.com/ruby/openssl/pull/541) + + Version 3.0.0 ============= @@ -100,6 +174,27 @@ 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 +============= + +Merged changes in 2.1.4. + + Version 2.2.1 ============= @@ -194,6 +289,16 @@ Notable changes [[GitHub #297]](https://github.com/ruby/openssl/pull/297) +Version 2.1.4 +============= + +Bug fixes +--------- + +* Do not use pkg-config if --with-openssl-dir option is specified. + [[GitHub #486]](https://github.com/ruby/openssl/pull/486) + + Version 2.1.3 ============= diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index cc2b1f8ba2..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") @@ -120,8 +121,13 @@ if is_libressl && ($mswin || $mingw) end Logging::message "=== Checking for OpenSSL features... ===\n" +evp_h = "openssl/evp.h".freeze +x509_h = "openssl/x509.h".freeze +ts_h = "openssl/ts.h".freeze +ssl_h = "openssl/ssl.h".freeze + # compile options -have_func("RAND_egd") +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| @@ -132,65 +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") -have_func("EVP_MD_CTX_free") -have_func("EVP_MD_CTX_pkey_ctx") -have_func("X509_STORE_get_ex_data") -have_func("X509_STORE_set_ex_data") -have_func("X509_STORE_get_ex_new_index") -have_func("X509_CRL_get0_signature") -have_func("X509_REQ_get0_signature") -have_func("X509_REVOKED_get0_serialNumber") -have_func("X509_REVOKED_get0_revocationDate") -have_func("X509_get0_tbs_sigalg") -have_func("X509_STORE_CTX_get0_untrusted") -have_func("X509_STORE_CTX_get0_cert") -have_func("X509_STORE_CTX_get0_chain") -have_func("OCSP_SINGLERESP_get0_id") -have_func("SSL_CTX_get_ciphers") -have_func("X509_up_ref") -have_func("X509_CRL_up_ref") -have_func("X509_STORE_up_ref") -have_func("SSL_SESSION_up_ref") -have_func("EVP_PKEY_up_ref") -have_func("SSL_CTX_set_min_proto_version(NULL, 0)", "openssl/ssl.h") -have_func("SSL_CTX_get_security_level") -have_func("X509_get0_notBefore") -have_func("SSL_SESSION_get_protocol_version") -have_func("TS_STATUS_INFO_get0_status") -have_func("TS_STATUS_INFO_get0_text") -have_func("TS_STATUS_INFO_get0_failure_info") -have_func("TS_VERIFY_CTS_set_certs(NULL, NULL)", "openssl/ts.h") -have_func("TS_VERIFY_CTX_set_store") -have_func("TS_VERIFY_CTX_add_flags") -have_func("TS_RESP_CTX_set_time_cb") -have_func("EVP_PBE_scrypt") -have_func("SSL_CTX_set_post_handshake_auth") +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(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(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(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") -have_func("SSL_CTX_set_ciphersuites") +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") -have_func("ERR_get_error_all") -have_func("TS_VERIFY_CTX_set_certs(NULL, NULL)", "openssl/ts.h") -have_func("SSL_CTX_load_verify_file") -have_func("BN_check_prime") -have_func("EVP_MD_CTX_get0_md") -have_func("EVP_MD_CTX_get_pkey_ctx") -have_func("EVP_PKEY_eq") -have_func("EVP_PKEY_dup") +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(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/pkey.rb b/ext/openssl/lib/openssl/pkey.rb index c3e0629091..0414658a10 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -167,8 +167,16 @@ module OpenSSL::PKey # +size+:: # The desired key size in bits. def generate(size, &blk) + # FIPS 186-4 specifies four (L,N) pairs: (1024,160), (2048,224), + # (2048,256), and (3072,256). + # + # q size is derived here with compatibility with + # DSA_generator_parameters_ex() which previous versions of ruby/openssl + # used to call. + qsize = size >= 2048 ? 256 : 160 dsaparams = OpenSSL::PKey.generate_parameters("DSA", { "dsa_paramgen_bits" => size, + "dsa_paramgen_q_bits" => qsize, }, &blk) OpenSSL::PKey.generate_key(dsaparams) end @@ -355,7 +363,8 @@ module OpenSSL::PKey # rsa.private_encrypt(string, padding) -> String # # Encrypt +string+ with the private key. +padding+ defaults to - # PKCS1_PADDING. The encrypted string output can be decrypted using + # PKCS1_PADDING, which is known to be insecure but is kept for backwards + # compatibility. The encrypted string output can be decrypted using # #public_decrypt. # # <b>Deprecated in version 3.0</b>. @@ -378,7 +387,8 @@ module OpenSSL::PKey # rsa.public_decrypt(string, padding) -> String # # Decrypt +string+, which has been encrypted with the private key, with the - # public key. +padding+ defaults to PKCS1_PADDING. + # public key. +padding+ defaults to PKCS1_PADDING which is known to be + # insecure but is kept for backwards compatibility. # # <b>Deprecated in version 3.0</b>. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and @@ -399,7 +409,8 @@ module OpenSSL::PKey # rsa.public_encrypt(string, padding) -> String # # Encrypt +string+ with the public key. +padding+ defaults to - # PKCS1_PADDING. The encrypted string output can be decrypted using + # PKCS1_PADDING, which is known to be insecure but is kept for backwards + # compatibility. The encrypted string output can be decrypted using # #private_decrypt. # # <b>Deprecated in version 3.0</b>. @@ -420,7 +431,8 @@ module OpenSSL::PKey # rsa.private_decrypt(string, padding) -> String # # Decrypt +string+, which has been encrypted with the public key, with the - # private key. +padding+ defaults to PKCS1_PADDING. + # private key. +padding+ defaults to PKCS1_PADDING, which is known to be + # insecure but is kept for backwards compatibility. # # <b>Deprecated in version 3.0</b>. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. 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 5e60604353..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.0.0" + VERSION = "3.1.0" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index c6cd818336..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.0.0" + 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_bn.c b/ext/openssl/ossl_bn.c index 56fa0ec302..bf2bac3679 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -577,22 +577,33 @@ BIGNUM_2c(gcd) */ BIGNUM_2c(mod_sqr) +#define BIGNUM_2cr(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ + ossl_raise(eBNError, NULL); \ + SetBN(obj, result); \ + return obj; \ + } + /* + * Document-method: OpenSSL::BN#mod_sqrt + * call-seq: + * bn.mod_sqrt(bn2) => aBN + */ +BIGNUM_2cr(mod_sqrt) + +/* + * Document-method: OpenSSL::BN#mod_inverse * call-seq: * bn.mod_inverse(bn2) => aBN */ -static VALUE -ossl_bn_mod_inverse(VALUE self, VALUE other) -{ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; - VALUE obj; - GetBN(self, bn1); - obj = NewBN(rb_obj_class(self)); - if (!(result = BN_mod_inverse(NULL, bn1, bn2, ossl_bn_ctx))) - ossl_raise(eBNError, "BN_mod_inverse"); - SetBN(obj, result); - return obj; -} +BIGNUM_2cr(mod_inverse) /* * call-seq: @@ -1234,6 +1245,7 @@ Init_ossl_bn(void) rb_define_method(cBN, "mod_sub", ossl_bn_mod_sub, 2); rb_define_method(cBN, "mod_mul", ossl_bn_mod_mul, 2); rb_define_method(cBN, "mod_sqr", ossl_bn_mod_sqr, 1); + rb_define_method(cBN, "mod_sqrt", ossl_bn_mod_sqrt, 1); rb_define_method(cBN, "**", ossl_bn_exp, 1); rb_define_method(cBN, "mod_exp", ossl_bn_mod_exp, 2); rb_define_method(cBN, "gcd", ossl_bn_gcd, 1); 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_hmac.c b/ext/openssl/ossl_hmac.c index bfe3a74b12..1a5f471a27 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -97,11 +97,19 @@ ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) GetHMAC(self, ctx); StringValue(key); +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, + (unsigned char *)RSTRING_PTR(key), + RSTRING_LENINT(key)); + if (!pkey) + ossl_raise(eHMACError, "EVP_PKEY_new_raw_private_key"); +#else pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, (unsigned char *)RSTRING_PTR(key), RSTRING_LENINT(key)); if (!pkey) ossl_raise(eHMACError, "EVP_PKEY_new_mac_key"); +#endif if (EVP_DigestSignInit(ctx, NULL, ossl_evp_get_digestbyname(digest), NULL, pkey) != 1) { EVP_PKEY_free(pkey); 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 24d0da4683..476256679b 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -99,17 +99,56 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) /* First check DER */ if (OSSL_DECODER_from_bio(dctx, bio) == 1) goto out; + OSSL_BIO_reset(bio); /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */ - OSSL_BIO_reset(bio); if (OSSL_DECODER_CTX_set_input_type(dctx, "PEM") != 1) goto out; - while (OSSL_DECODER_from_bio(dctx, bio) != 1) { - if (BIO_eof(bio)) + /* + * First check for private key formats. This is to keep compatibility with + * ruby/openssl < 3.0 which decoded the following as a private key. + * + * $ openssl ecparam -name prime256v1 -genkey -outform PEM + * -----BEGIN EC PARAMETERS----- + * BggqhkjOPQMBBw== + * -----END EC PARAMETERS----- + * -----BEGIN EC PRIVATE KEY----- + * MHcCAQEEIAG8ugBbA5MHkqnZ9ujQF93OyUfL9tk8sxqM5Wv5tKg5oAoGCCqGSM49 + * AwEHoUQDQgAEVcjhJfkwqh5C7kGuhAf8XaAjVuG5ADwb5ayg/cJijCgs+GcXeedj + * 86avKpGH84DXUlB23C/kPt+6fXYlitUmXQ== + * -----END EC PRIVATE KEY----- + * + * While the first PEM block is a proper encoding of ECParameters, thus + * OSSL_DECODER_from_bio() would pick it up, ruby/openssl used to return + * the latter instead. Existing applications expect this behavior. + * + * Note that normally, the input is supposed to contain a single decodable + * PEM block only, so this special handling should not create a new problem. + */ + OSSL_DECODER_CTX_set_selection(dctx, EVP_PKEY_KEYPAIR); + while (1) { + if (OSSL_DECODER_from_bio(dctx, bio) == 1) goto out; + if (BIO_eof(bio)) + break; pos2 = BIO_tell(bio); if (pos2 < 0 || pos2 <= pos) + break; + ossl_clear_error(); + pos = pos2; + } + + OSSL_BIO_reset(bio); + OSSL_DECODER_CTX_set_selection(dctx, 0); + while (1) { + if (OSSL_DECODER_from_bio(dctx, bio) == 1) goto out; + if (BIO_eof(bio)) + break; + pos2 = BIO_tell(bio); + if (pos2 < 0 || pos2 <= pos) + break; + ossl_clear_error(); pos = pos2; } @@ -200,6 +239,7 @@ static VALUE pkey_ctx_apply_options0(VALUE args_v) { VALUE *args = (VALUE *)args_v; + Check_Type(args[1], T_HASH); rb_block_call(args[1], rb_intern("each"), 0, NULL, pkey_ctx_apply_options_i, args[0]); @@ -911,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); @@ -1016,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 08972e92a1..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,9 +411,11 @@ 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) + ossl_raise(eECError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(argc, argv, self, 0); else @@ -429,9 +431,11 @@ 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) + ossl_raise(eECError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(0, NULL, self, 1); else @@ -479,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; @@ -664,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"); + } } /* @@ -1228,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; } /* @@ -1249,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; @@ -1270,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; @@ -1293,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; @@ -1312,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; } @@ -1330,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; } @@ -1462,7 +1482,7 @@ static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self) "use #mul(bn) form instead"); num = RARRAY_LEN(arg1); - bns_tmp = rb_ary_hidden_new(num); + bns_tmp = rb_ary_tmp_new(num); bignums = ALLOCV_N(const BIGNUM *, tmp_b, num); for (i = 0; i < num; i++) { VALUE item = RARRAY_AREF(arg1, i); 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 6e1a50fd6d..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; @@ -49,7 +52,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, - id_i_verify_hostname; + id_i_verify_hostname, id_i_keylog_cb; static ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_vcb_idx; @@ -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 */ @@ -441,6 +444,54 @@ ossl_sslctx_session_new_cb(SSL *ssl, SSL_SESSION *sess) return 0; } +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) +/* + * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements + * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see + * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). + */ + +struct ossl_call_keylog_cb_args { + VALUE ssl_obj; + const char * line; +}; + +static VALUE +ossl_call_keylog_cb(VALUE args_v) +{ + VALUE sslctx_obj, cb, line_v; + struct ossl_call_keylog_cb_args *args = (struct ossl_call_keylog_cb_args *) args_v; + + sslctx_obj = rb_attr_get(args->ssl_obj, id_i_context); + + cb = rb_attr_get(sslctx_obj, id_i_keylog_cb); + if (NIL_P(cb)) return Qnil; + + line_v = rb_str_new_cstr(args->line); + + return rb_funcall(cb, id_call, 2, args->ssl_obj, line_v); +} + +static void +ossl_sslctx_keylog_cb(const SSL *ssl, const char *line) +{ + VALUE ssl_obj; + struct ossl_call_keylog_cb_args args; + int state = 0; + + OSSL_Debug("SSL keylog callback entered"); + + ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + args.ssl_obj = ssl_obj; + args.line = line; + + rb_protect(ossl_call_keylog_cb, (VALUE)&args, &state); + if (state) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); + } +} +#endif + static VALUE ossl_call_session_remove_cb(VALUE ary) { @@ -655,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) @@ -852,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); @@ -911,6 +962,18 @@ ossl_sslctx_setup(VALUE self) OSSL_Debug("SSL TLSEXT servername callback added"); } +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) + /* + * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements + * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see + * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). + */ + if (RTEST(rb_attr_get(self, id_i_keylog_cb))) { + SSL_CTX_set_keylog_callback(ctx, ossl_sslctx_keylog_cb); + OSSL_Debug("SSL keylog callback added"); + } +#endif + return Qtrue; } @@ -1478,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) { @@ -1641,11 +1703,16 @@ no_exception_p(VALUE opts) return 0; } +// Provided by Ruby 3.2.0 and later in order to support the default IO#timeout. +#ifndef RUBY_IO_TIMEOUT_DEFAULT +#define RUBY_IO_TIMEOUT_DEFAULT Qnil +#endif + static void io_wait_writable(rb_io_t *fptr) { #ifdef HAVE_RB_IO_MAYBE_WAIT - rb_io_maybe_wait_writable(errno, fptr->self, Qnil); + rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT); #else rb_io_wait_writable(fptr->fd); #endif @@ -1655,7 +1722,7 @@ static void io_wait_readable(rb_io_t *fptr) { #ifdef HAVE_RB_IO_MAYBE_WAIT - rb_io_maybe_wait_readable(errno, fptr->self, Qnil); + rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT); #else rb_io_wait_readable(fptr->fd); #endif @@ -2381,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 @@ -2431,6 +2498,49 @@ ossl_ssl_alpn_protocol(VALUE self) /* * call-seq: + * session.export_keying_material(label, length) -> String + * + * Enables use of shared session key material in accordance with RFC 5705. + */ +static VALUE +ossl_ssl_export_keying_material(int argc, VALUE *argv, VALUE self) +{ + SSL *ssl; + VALUE str; + VALUE label; + VALUE length; + VALUE context; + unsigned char *p; + size_t len; + int use_ctx = 0; + unsigned char *ctx = NULL; + size_t ctx_len = 0; + int ret; + + rb_scan_args(argc, argv, "21", &label, &length, &context); + StringValue(label); + + GetSSL(self, ssl); + + len = (size_t)NUM2LONG(length); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if (!NIL_P(context)) { + use_ctx = 1; + StringValue(context); + ctx = (unsigned char *)RSTRING_PTR(context); + ctx_len = RSTRING_LEN(context); + } + ret = SSL_export_keying_material(ssl, p, len, (char *)RSTRING_PTR(label), + RSTRING_LENINT(label), ctx, ctx_len, use_ctx); + if (ret == 0 || ret == -1) { + ossl_raise(eSSLError, "SSL_export_keying_material"); + } + return str; +} + +/* + * call-seq: * ssl.tmp_key => PKey or nil * * Returns the ephemeral key used in case of forward secrecy cipher. @@ -2458,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"); @@ -2480,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. @@ -2652,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. @@ -2674,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 @@ -2736,6 +2835,29 @@ Init_ossl_ssl(void) */ rb_attr(cSSLContext, rb_intern_const("alpn_select_cb"), 1, 1, Qfalse); + /* + * A callback invoked when TLS key material is generated or received, in + * order to allow applications to store this keying material for debugging + * purposes. + * + * The callback is invoked with an SSLSocket and a string containing the + * key material in the format used by NSS for its SSLKEYLOGFILE debugging + * output. + * + * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements + * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see + * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). + * + * === Example + * + * context.keylog_cb = proc do |_sock, line| + * File.open('ssl_keylog_file', "a") do |f| + * f.write("#{line}\n") + * end + * end + */ + rb_attr(cSSLContext, rb_intern_const("keylog_cb"), 1, 1, Qfalse); + rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); rb_define_private_method(cSSLContext, "set_minmax_proto_version", @@ -2821,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"); @@ -2856,10 +2973,10 @@ Init_ossl_ssl(void) rb_define_method(cSSLSocket, "peer_finished_message", ossl_ssl_get_peer_finished, 0); rb_define_method(cSSLSocket, "tmp_key", ossl_ssl_tmp_key, 0); rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); -# ifndef OPENSSL_NO_NEXTPROTONEG + rb_define_method(cSSLSocket, "export_keying_material", ossl_ssl_export_keying_material, -1); +# 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)); @@ -3016,8 +3133,10 @@ Init_ossl_ssl(void) DefIVarID(alpn_select_cb); DefIVarID(servername_cb); DefIVarID(verify_hostname); + DefIVarID(keylog_cb); 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/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 996f184170..9443541645 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -642,12 +642,12 @@ ossl_x509_set_extensions(VALUE self, VALUE ary) OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509(self, x509); - while ((ext = X509_delete_ext(x509, 0))) - X509_EXTENSION_free(ext); + for (i = X509_get_ext_count(x509); i > 0; i--) + X509_EXTENSION_free(X509_delete_ext(x509, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, "X509_add_ext"); } } diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 863f0286c0..6c1d915370 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -474,12 +474,12 @@ ossl_x509crl_set_extensions(VALUE self, VALUE ary) OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509CRL(self, crl); - while ((ext = X509_CRL_delete_ext(crl, 0))) - X509_EXTENSION_free(ext); + for (i = X509_CRL_get_ext_count(crl); i > 0; i--) + X509_EXTENSION_free(X509_CRL_delete_ext(crl, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); /* NO NEED TO DUP */ if (!X509_CRL_add_ext(crl, ext, -1)) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, "X509_CRL_add_ext"); } } diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index 6eb91e9c2f..77a7d3f2ff 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -380,13 +380,13 @@ ossl_x509req_set_attributes(VALUE self, VALUE ary) OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Attr); } GetX509Req(self, req); - while ((attr = X509_REQ_delete_attr(req, 0))) - X509_ATTRIBUTE_free(attr); + for (i = X509_REQ_get_attr_count(req); i > 0; i--) + X509_ATTRIBUTE_free(X509_REQ_delete_attr(req, 0)); for (i=0;i<RARRAY_LEN(ary); i++) { item = RARRAY_AREF(ary, i); attr = GetX509AttrPtr(item); if (!X509_REQ_add1_attr(req, attr)) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, "X509_REQ_add1_attr"); } } return ary; diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c index 5fe6853430..10b8aa4ad6 100644 --- a/ext/openssl/ossl_x509revoked.c +++ b/ext/openssl/ossl_x509revoked.c @@ -223,13 +223,13 @@ ossl_x509revoked_set_extensions(VALUE self, VALUE ary) OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509Rev(self, rev); - while ((ext = X509_REVOKED_delete_ext(rev, 0))) - X509_EXTENSION_free(ext); + for (i = X509_REVOKED_get_ext_count(rev); i > 0; i--) + X509_EXTENSION_free(X509_REVOKED_delete_ext(rev, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { item = RARRAY_AREF(ary, i); ext = GetX509ExtPtr(item); if(!X509_REVOKED_add_ext(rev, ext, -1)) { - ossl_raise(eX509RevError, NULL); + ossl_raise(eX509RevError, "X509_REVOKED_add_ext"); } } diff --git a/ext/pathname/lib/pathname.rb b/ext/pathname/lib/pathname.rb index 9a297529ca..7bdfd0eb39 100644 --- a/ext/pathname/lib/pathname.rb +++ b/ext/pathname/lib/pathname.rb @@ -338,6 +338,8 @@ class Pathname # # Appends a pathname fragment to +self+ to produce a new Pathname object. + # Since +other+ is considered as a path relative to +self+, if +other+ is + # an absolute path, the new Pathname object is created from just +other+. # # p1 = Pathname.new("/usr") # Pathname:/usr # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby @@ -399,6 +401,8 @@ class Pathname # # Joins the given pathnames onto +self+ to create a new Pathname object. + # This is effectively the same as using Pathname#+ to append +self+ and + # all arguments sequentially. # # path0 = Pathname.new("/usr") # Pathname:/usr # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby diff --git a/ext/pathname/pathname.gemspec b/ext/pathname/pathname.gemspec index c9c0b84e69..92bc02b0db 100644 --- a/ext/pathname/pathname.gemspec +++ b/ext/pathname/pathname.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "pathname" - spec.version = "0.2.0" + spec.version = "0.2.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] diff --git a/ext/psych/extconf.rb b/ext/psych/extconf.rb index 6d03870436..41daf8c238 100644 --- a/ext/psych/extconf.rb +++ b/ext/psych/extconf.rb @@ -6,39 +6,9 @@ if $mswin or $mingw or $cygwin $CPPFLAGS << " -DYAML_DECLARE_STATIC" end -yaml_source = with_config("libyaml-source-dir") || enable_config("bundled-libyaml", false) -unless yaml_source # default to pre-installed libyaml - pkg_config('yaml-0.1') - dir_config('libyaml') - unless find_header('yaml.h') && find_library('yaml', 'yaml_get_version') - yaml_source = true # fallback to the bundled source if exists - end -end - -if yaml_source == true - # search the latest libyaml source under $srcdir - yaml_source = Dir.glob("#{$srcdir}/yaml{,-*}/").max_by {|n| File.basename(n).scan(/\d+/).map(&:to_i)} - unless yaml_source - download_failure = "failed to download libyaml source. Try manually installing libyaml?" - begin - require_relative '../../tool/extlibs.rb' - rescue LoadError - # When running in ruby/ruby, we use miniruby and don't have stdlib. - # Avoid LoadError because it aborts the whole build. Usually when - # stdlib extension fail to configure we skip it and continue. - raise download_failure - end - extlibs = ExtLibs.new(cache_dir: File.expand_path("../../tmp/download_cache", $srcdir)) - unless extlibs.process_under($srcdir) - raise download_failure - end - yaml_source, = Dir.glob("#{$srcdir}/yaml-*/") - raise "libyaml not found" unless yaml_source - end -elsif yaml_source - yaml_source = yaml_source.gsub(/\$\((\w+)\)|\$\{(\w+)\}/) {ENV[$1||$2]} -end +yaml_source = with_config("libyaml-source-dir") if yaml_source + yaml_source = yaml_source.gsub(/\$\((\w+)\)|\$\{(\w+)\}/) {ENV[$1||$2]} yaml_source = yaml_source.chomp("/") yaml_configure = "#{File.expand_path(yaml_source)}/configure" unless File.exist?(yaml_configure) @@ -66,6 +36,11 @@ if yaml_source libyaml = "libyaml.#$LIBEXT" $cleanfiles << libyaml $LOCAL_LIBS.prepend("$(LIBYAML) ") +else # default to pre-installed libyaml + pkg_config('yaml-0.1') + dir_config('libyaml') + find_header('yaml.h') or abort "yaml.h not found" + find_library('yaml', 'yaml_get_version') or abort "libyaml not found" end create_makefile 'psych' do |mk| diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index a9585c887f..a592a6916c 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,7 +2,7 @@ module Psych # The version of Psych you are using - VERSION = '5.0.0.dev' + VERSION = '5.0.1' if RUBY_ENGINE == 'jruby' DEFAULT_SNAKEYAML_VERSION = '1.33'.freeze diff --git a/ext/pty/depend b/ext/pty/depend index c43d3dcf9a..f251caae3f 100644 --- a/ext/pty/depend +++ b/ext/pty/depend @@ -181,5 +181,6 @@ pty.o: $(top_srcdir)/internal/process.h pty.o: $(top_srcdir)/internal/signal.h pty.o: $(top_srcdir)/internal/static_assert.h pty.o: $(top_srcdir)/internal/warnings.h +pty.o: $(top_srcdir)/shape.h pty.o: pty.c # AUTOGENERATED DEPENDENCIES END 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/depend b/ext/ripper/depend index 15c557a8ef..856283e177 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -20,8 +20,6 @@ static: check ripper.y: $(srcdir)/tools/preproc.rb $(srcdir)/tools/dsl.rb $(top_srcdir)/parse.y $(top_srcdir)/defs/id.def $(ECHO) extracting $@ from $(top_srcdir)/parse.y $(Q) $(RUBY) $(top_srcdir)/tool/id2token.rb $(top_srcdir)/parse.y > ripper.tmp.y - $(Q) $(RUBY) $(top_srcdir)/tool/pure_parser.rb ripper.tmp.y $(BISON) - $(Q) $(RM) ripper.tmp.y.bak $(Q) $(RUBY) $(srcdir)/tools/preproc.rb ripper.tmp.y --output=$@ $(Q) $(RM) ripper.tmp.y @@ -233,6 +231,7 @@ ripper.o: $(top_srcdir)/internal/bits.h ripper.o: $(top_srcdir)/internal/compile.h ripper.o: $(top_srcdir)/internal/compilers.h ripper.o: $(top_srcdir)/internal/complex.h +ripper.o: $(top_srcdir)/internal/encoding.h ripper.o: $(top_srcdir)/internal/error.h ripper.o: $(top_srcdir)/internal/fixnum.h ripper.o: $(top_srcdir)/internal/gc.h @@ -254,6 +253,7 @@ ripper.o: $(top_srcdir)/internal/warnings.h ripper.o: $(top_srcdir)/node.h ripper.o: $(top_srcdir)/regenc.h ripper.o: $(top_srcdir)/ruby_assert.h +ripper.o: $(top_srcdir)/shape.h ripper.o: $(top_srcdir)/symbol.h ripper.o: ../../probes.h ripper.o: eventids2.c diff --git a/ext/ripper/eventids2.c b/ext/ripper/eventids2.c index ac38663f2d..05687497ac 100644 --- a/ext/ripper/eventids2.c +++ b/ext/ripper/eventids2.c @@ -1,22 +1,3 @@ -enum { - tIGNORED_NL = tLAST_TOKEN + 1, -# define tIGNORED_NL ((enum yytokentype)tIGNORED_NL) - tCOMMENT, -# define tCOMMENT ((enum yytokentype)tCOMMENT) - tEMBDOC_BEG, -# define tEMBDOC_BEG ((enum yytokentype)tEMBDOC_BEG) - tEMBDOC, -# define tEMBDOC ((enum yytokentype)tEMBDOC) - tEMBDOC_END, -# define tEMBDOC_END ((enum yytokentype)tEMBDOC_END) - tHEREDOC_BEG, -# define tHEREDOC_BEG ((enum yytokentype)tHEREDOC_BEG) - tHEREDOC_END, -# define tHEREDOC_END ((enum yytokentype)tHEREDOC_END) - k__END__, -# define k__END__ ((enum yytokentype)k__END__) -}; - typedef struct { ID ripper_id_backref; ID ripper_id_backtick; 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/ripper/tools/preproc.rb b/ext/ripper/tools/preproc.rb index b838a78db7..cd85a5da61 100644 --- a/ext/ripper/tools/preproc.rb +++ b/ext/ripper/tools/preproc.rb @@ -47,7 +47,7 @@ def prelude(f, out) when /\A%%/ out << "%%\n" return - when /\A%token/ + when /\A%token/, /\A} <node>/ out << line.sub(/<\w+>/, '<val>') when /\A%type/ out << line.sub(/<\w+>/, '<val>') diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c index 071e3323bb..7406177de2 100644 --- a/ext/socket/ancdata.c +++ b/ext/socket/ancdata.c @@ -1285,7 +1285,7 @@ bsock_sendmsg_internal(VALUE sock, VALUE data, VALUE vflags, if (ss == -1) { int e; - if (!nonblock && rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) { + if (!nonblock && rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { rb_io_check_closed(fptr); goto retry; } @@ -1557,7 +1557,7 @@ bsock_recvmsg_internal(VALUE sock, if (ss == -1) { int e; - if (!nonblock && rb_io_maybe_wait_readable(errno, fptr->self, Qnil)) { + if (!nonblock && rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { rb_io_check_closed(fptr); goto retry; } diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c index 93196c924d..54c369f6fc 100644 --- a/ext/socket/basicsocket.c +++ b/ext/socket/basicsocket.c @@ -601,7 +601,7 @@ rsock_bsock_send(int argc, VALUE *argv, VALUE socket) if (n >= 0) return SSIZET2NUM(n); - if (rb_io_maybe_wait_writable(errno, socket, Qnil)) { + if (rb_io_maybe_wait_writable(errno, socket, RUBY_IO_TIMEOUT_DEFAULT)) { continue; } diff --git a/ext/socket/depend b/ext/socket/depend index ffe2fce844..28c5540cd6 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -197,6 +197,7 @@ ancdata.o: $(top_srcdir)/internal/string.h ancdata.o: $(top_srcdir)/internal/thread.h ancdata.o: $(top_srcdir)/internal/vm.h ancdata.o: $(top_srcdir)/internal/warnings.h +ancdata.o: $(top_srcdir)/shape.h ancdata.o: ancdata.c ancdata.o: constdefs.h ancdata.o: rubysocket.h @@ -388,6 +389,7 @@ basicsocket.o: $(top_srcdir)/internal/string.h basicsocket.o: $(top_srcdir)/internal/thread.h basicsocket.o: $(top_srcdir)/internal/vm.h basicsocket.o: $(top_srcdir)/internal/warnings.h +basicsocket.o: $(top_srcdir)/shape.h basicsocket.o: basicsocket.c basicsocket.o: constdefs.h basicsocket.o: rubysocket.h @@ -579,6 +581,7 @@ constants.o: $(top_srcdir)/internal/string.h constants.o: $(top_srcdir)/internal/thread.h constants.o: $(top_srcdir)/internal/vm.h constants.o: $(top_srcdir)/internal/warnings.h +constants.o: $(top_srcdir)/shape.h constants.o: constants.c constants.o: constdefs.c constants.o: constdefs.h @@ -771,6 +774,7 @@ ifaddr.o: $(top_srcdir)/internal/string.h ifaddr.o: $(top_srcdir)/internal/thread.h ifaddr.o: $(top_srcdir)/internal/vm.h ifaddr.o: $(top_srcdir)/internal/warnings.h +ifaddr.o: $(top_srcdir)/shape.h ifaddr.o: constdefs.h ifaddr.o: ifaddr.c ifaddr.o: rubysocket.h @@ -962,6 +966,7 @@ init.o: $(top_srcdir)/internal/string.h init.o: $(top_srcdir)/internal/thread.h init.o: $(top_srcdir)/internal/vm.h init.o: $(top_srcdir)/internal/warnings.h +init.o: $(top_srcdir)/shape.h init.o: constdefs.h init.o: init.c init.o: rubysocket.h @@ -1153,6 +1158,7 @@ ipsocket.o: $(top_srcdir)/internal/string.h ipsocket.o: $(top_srcdir)/internal/thread.h ipsocket.o: $(top_srcdir)/internal/vm.h ipsocket.o: $(top_srcdir)/internal/warnings.h +ipsocket.o: $(top_srcdir)/shape.h ipsocket.o: constdefs.h ipsocket.o: ipsocket.c ipsocket.o: rubysocket.h @@ -1344,6 +1350,7 @@ option.o: $(top_srcdir)/internal/string.h option.o: $(top_srcdir)/internal/thread.h option.o: $(top_srcdir)/internal/vm.h option.o: $(top_srcdir)/internal/warnings.h +option.o: $(top_srcdir)/shape.h option.o: constdefs.h option.o: option.c option.o: rubysocket.h @@ -1535,6 +1542,7 @@ raddrinfo.o: $(top_srcdir)/internal/string.h raddrinfo.o: $(top_srcdir)/internal/thread.h raddrinfo.o: $(top_srcdir)/internal/vm.h raddrinfo.o: $(top_srcdir)/internal/warnings.h +raddrinfo.o: $(top_srcdir)/shape.h raddrinfo.o: constdefs.h raddrinfo.o: raddrinfo.c raddrinfo.o: rubysocket.h @@ -1726,6 +1734,7 @@ socket.o: $(top_srcdir)/internal/string.h socket.o: $(top_srcdir)/internal/thread.h socket.o: $(top_srcdir)/internal/vm.h socket.o: $(top_srcdir)/internal/warnings.h +socket.o: $(top_srcdir)/shape.h socket.o: constdefs.h socket.o: rubysocket.h socket.o: socket.c @@ -1917,6 +1926,7 @@ sockssocket.o: $(top_srcdir)/internal/string.h sockssocket.o: $(top_srcdir)/internal/thread.h sockssocket.o: $(top_srcdir)/internal/vm.h sockssocket.o: $(top_srcdir)/internal/warnings.h +sockssocket.o: $(top_srcdir)/shape.h sockssocket.o: constdefs.h sockssocket.o: rubysocket.h sockssocket.o: sockport.h @@ -2108,6 +2118,7 @@ tcpserver.o: $(top_srcdir)/internal/string.h tcpserver.o: $(top_srcdir)/internal/thread.h tcpserver.o: $(top_srcdir)/internal/vm.h tcpserver.o: $(top_srcdir)/internal/warnings.h +tcpserver.o: $(top_srcdir)/shape.h tcpserver.o: constdefs.h tcpserver.o: rubysocket.h tcpserver.o: sockport.h @@ -2299,6 +2310,7 @@ tcpsocket.o: $(top_srcdir)/internal/string.h tcpsocket.o: $(top_srcdir)/internal/thread.h tcpsocket.o: $(top_srcdir)/internal/vm.h tcpsocket.o: $(top_srcdir)/internal/warnings.h +tcpsocket.o: $(top_srcdir)/shape.h tcpsocket.o: constdefs.h tcpsocket.o: rubysocket.h tcpsocket.o: sockport.h @@ -2490,6 +2502,7 @@ udpsocket.o: $(top_srcdir)/internal/string.h udpsocket.o: $(top_srcdir)/internal/thread.h udpsocket.o: $(top_srcdir)/internal/vm.h udpsocket.o: $(top_srcdir)/internal/warnings.h +udpsocket.o: $(top_srcdir)/shape.h udpsocket.o: constdefs.h udpsocket.o: rubysocket.h udpsocket.o: sockport.h @@ -2681,6 +2694,7 @@ unixserver.o: $(top_srcdir)/internal/string.h unixserver.o: $(top_srcdir)/internal/thread.h unixserver.o: $(top_srcdir)/internal/vm.h unixserver.o: $(top_srcdir)/internal/warnings.h +unixserver.o: $(top_srcdir)/shape.h unixserver.o: constdefs.h unixserver.o: rubysocket.h unixserver.o: sockport.h @@ -2872,6 +2886,7 @@ unixsocket.o: $(top_srcdir)/internal/string.h unixsocket.o: $(top_srcdir)/internal/thread.h unixsocket.o: $(top_srcdir)/internal/vm.h unixsocket.o: $(top_srcdir)/internal/warnings.h +unixsocket.o: $(top_srcdir)/shape.h unixsocket.o: constdefs.h unixsocket.o: rubysocket.h unixsocket.o: sockport.h diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index b70a862414..37ff216560 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -316,6 +316,7 @@ end netpacket/packet.h net/ethernet.h sys/un.h + afunix.h ifaddrs.h sys/ioctl.h sys/sockio.h @@ -551,7 +552,7 @@ EOS end if !have_macro("IPPROTO_IPV6", headers) && have_const("IPPROTO_IPV6", headers) - IO.read(File.join(File.dirname(__FILE__), "mkconstants.rb")).sub(/\A.*^__END__$/m, '').split(/\r?\n/).grep(/\AIPPROTO_\w*/){$&}.each {|name| + File.read(File.join(File.dirname(__FILE__), "mkconstants.rb")).sub(/\A.*^__END__$/m, '').split(/\r?\n/).grep(/\AIPPROTO_\w*/){$&}.each {|name| have_const(name, headers) unless $defs.include?("-DHAVE_CONST_#{name.upcase}") } end @@ -655,12 +656,20 @@ EOS end hdr = "netinet6/in6.h" - if /darwin/ =~ RUBY_PLATFORM and !try_compile(<<"SRC", nil, :werror=>true) + /darwin/ =~ RUBY_PLATFORM and + checking_for("if apple's #{hdr} needs s6_addr patch") {!try_compile(<<"SRC", nil, :werror=>true)} and #include <netinet/in.h> int t(struct in6_addr *addr) {return IN6_IS_ADDR_UNSPECIFIED(addr);} SRC - print "fixing apple's netinet6/in6.h ..."; $stdout.flush - in6 = File.read("/usr/include/#{hdr}") + checking_for("fixing apple's #{hdr}", "%s") do + file = xpopen(%w"clang -include netinet/in.h -E -xc -", in: IO::NULL) do |f| + re = %r[^# *\d+ *"(.*/netinet/in\.h)"] + Logging.message " grep(#{re})\n" + f.read[re, 1] + end + Logging.message "Substitute from #{file}\n" + + in6 = File.read(file) if in6.gsub!(/\*\(const\s+__uint32_t\s+\*\)\(const\s+void\s+\*\)\(&(\(\w+\))->s6_addr\[(\d+)\]\)/) do i, r = $2.to_i.divmod(4) if r.zero? @@ -670,12 +679,12 @@ SRC end end FileUtils.mkdir_p(File.dirname(hdr)) - open(hdr, "w") {|f| f.write(in6)} + File.write(hdr, in6) $distcleanfiles << hdr $distcleandirs << File.dirname(hdr) - puts "done" + "done" else - puts "not needed" + "not needed" end end create_makefile("socket") diff --git a/ext/socket/init.c b/ext/socket/init.c index 0cff3d6794..557d4374a5 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -189,7 +189,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) if (slen >= 0) break; - if (!rb_io_maybe_wait_readable(errno, socket, Qnil)) + if (!rb_io_maybe_wait_readable(errno, socket, RUBY_IO_TIMEOUT_DEFAULT)) rb_sys_fail("recvfrom(2)"); } @@ -209,7 +209,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) else return rb_assoc_new(str, Qnil); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case RECV_UNIX: return rb_assoc_new(str, rsock_unixaddr(&arg.buf.un, arg.alen)); #endif @@ -705,7 +705,7 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) retry = 1; goto retry; default: - if (!rb_io_maybe_wait_readable(error, io, Qnil)) break; + if (!rb_io_maybe_wait_readable(error, io, RUBY_IO_TIMEOUT_DEFAULT)) break; retry = 0; goto retry; } diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index d756a32a5a..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. @@ -197,7 +201,7 @@ class Addrinfo sock = Socket.new(self.pfamily, self.socktype, self.protocol) begin sock.ipv6only! if self.ipv6? - sock.setsockopt(:SOCKET, :REUSEADDR, 1) + sock.setsockopt(:SOCKET, :REUSEADDR, 1) unless self.pfamily == Socket::PF_UNIX sock.bind(self) sock.listen(backlog) rescue Exception @@ -606,7 +610,6 @@ class Socket < BasicSocket # _opts_ may have following options: # # [:connect_timeout] specify the timeout in seconds. - # [:resolv_timeout] specify the name resolution timeout in seconds. # # If a block is given, the block is called with the socket. # The value of the block is returned. diff --git a/ext/socket/option.c b/ext/socket/option.c index 2dbe6379c4..0d818d0c70 100644 --- a/ext/socket/option.c +++ b/ext/socket/option.c @@ -670,10 +670,10 @@ rb_if_indextoname(const char *succ_prefix, const char *fail_prefix, unsigned int { #if defined(HAVE_IF_INDEXTONAME) char ifbuf[IFNAMSIZ]; - if (if_indextoname(ifindex, ifbuf) == NULL) - return snprintf(buf, len, "%s%u", fail_prefix, ifindex); - else + if (if_indextoname(ifindex, ifbuf)) return snprintf(buf, len, "%s%s", succ_prefix, ifbuf); + else + return snprintf(buf, len, "%s%u", fail_prefix, ifindex); #else # ifndef IFNAMSIZ # define IFNAMSIZ (sizeof(unsigned int)*3+1) @@ -1229,7 +1229,7 @@ sockopt_inspect(VALUE self) else rb_str_catf(ret, " optname:%d", optname); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN else if (family == AF_UNIX) { rb_str_catf(ret, " level:%d", level); @@ -1393,7 +1393,7 @@ sockopt_inspect(VALUE self) } break; -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: switch (level) { case 0: diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index a4e1ed37a3..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; @@ -618,8 +619,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup) family = rb_str_dup(rb_id2str(id)); } else { - sprintf(pbuf, "unknown:%d", sockaddr->sa_family); - family = rb_str_new2(pbuf); + family = rb_sprintf("unknown:%d", sockaddr->sa_family); } addr1 = Qnil; @@ -645,7 +645,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup) return ary; } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static long unixsocket_len(const struct sockaddr_un *su, socklen_t socklen) { @@ -1018,7 +1018,7 @@ addrinfo_list_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static void init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) { @@ -1147,7 +1147,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) break; } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: /* ["AF_UNIX", "/tmp/sock"] */ { VALUE path = rb_ary_entry(sockaddr_ary, 1); @@ -1287,7 +1287,7 @@ rsock_inspect_sockaddr(struct sockaddr *sockaddr_arg, socklen_t socklen, VALUE r } #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { struct sockaddr_un *addr = &sockaddr->un; @@ -1623,7 +1623,7 @@ addrinfo_mdump(VALUE self) afamily = rb_id2str(id); switch(afamily_int) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { sockaddr = rb_str_new(rai->addr.un.sun_path, rai_unixsocket_len(rai)); @@ -1716,7 +1716,7 @@ addrinfo_mload(VALUE self, VALUE ary) v = rb_ary_entry(ary, 1); switch(afamily) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { struct sockaddr_un uaddr; @@ -2344,7 +2344,7 @@ addrinfo_ipv6_to_ipv4(VALUE self) #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: * addrinfo.unix_path => path @@ -2492,7 +2492,7 @@ addrinfo_s_udp(VALUE self, VALUE host, VALUE port) INT2NUM(PF_UNSPEC), INT2NUM(SOCK_DGRAM), INT2NUM(IPPROTO_UDP), INT2FIX(0)); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: @@ -2630,7 +2630,7 @@ rsock_init_addrinfo(void) rb_define_singleton_method(rb_cAddrinfo, "ip", addrinfo_s_ip, 1); rb_define_singleton_method(rb_cAddrinfo, "tcp", addrinfo_s_tcp, 2); rb_define_singleton_method(rb_cAddrinfo, "udp", addrinfo_s_udp, 2); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_singleton_method(rb_cAddrinfo, "unix", addrinfo_s_unix, -1); #endif @@ -2671,7 +2671,7 @@ rsock_init_addrinfo(void) rb_define_method(rb_cAddrinfo, "ipv6_to_ipv4", addrinfo_ipv6_to_ipv4, 0); #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_method(rb_cAddrinfo, "unix_path", addrinfo_unix_path, 0); #endif diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 9ec893ee8c..5f803ba0da 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -33,6 +33,9 @@ #endif #ifdef _WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +# include <iphlpapi.h> # if defined(_MSC_VER) # undef HAVE_TYPE_STRUCT_SOCKADDR_DL # endif @@ -69,6 +72,11 @@ # include <sys/un.h> #endif +#ifdef HAVE_AFUNIX_H +// Windows doesn't have sys/un.h, but it does have afunix.h just to be special: +# include <afunix.h> +#endif + #if defined(HAVE_FCNTL) # ifdef HAVE_SYS_SELECT_H # include <sys/select.h> @@ -268,7 +276,7 @@ extern VALUE rb_cIPSocket; extern VALUE rb_cTCPSocket; extern VALUE rb_cTCPServer; extern VALUE rb_cUDPSocket; -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN extern VALUE rb_cUNIXSocket; extern VALUE rb_cUNIXServer; #endif @@ -336,7 +344,7 @@ VALUE rsock_sockaddr_obj(struct sockaddr *addr, socklen_t len); int rsock_revlookup_flag(VALUE revlookup, int *norevlookup); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN VALUE rsock_unixpath_str(struct sockaddr_un *sockaddr, socklen_t len); VALUE rsock_unixaddr(struct sockaddr_un *sockaddr, socklen_t len); socklen_t rsock_unix_sockaddr_len(VALUE path); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index b1965deb9e..eb74f7a936 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -28,6 +28,10 @@ rsock_syserr_fail_host_port(int err, const char *mesg, VALUE host, VALUE port) message = rb_sprintf("%s for %+"PRIsVALUE" port % "PRIsVALUE"", mesg, host, port); + if (err == ETIMEDOUT) { + rb_exc_raise(rb_exc_new3(rb_eIOTimeoutError, message)); + } + rb_syserr_fail_str(err, message); } @@ -1379,7 +1383,7 @@ sock_s_unpack_sockaddr_in(VALUE self, VALUE addr) return rb_assoc_new(INT2NUM(ntohs(sockaddr->sin_port)), host); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: @@ -1467,7 +1471,7 @@ sockaddr_len(struct sockaddr *addr) return (socklen_t)sizeof(struct sockaddr_in6); #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: return (socklen_t)sizeof(struct sockaddr_un); #endif @@ -2016,7 +2020,7 @@ Init_socket(void) rb_define_singleton_method(rb_cSocket, "sockaddr_in", sock_s_pack_sockaddr_in, 2); rb_define_singleton_method(rb_cSocket, "pack_sockaddr_in", sock_s_pack_sockaddr_in, 2); rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_in", sock_s_unpack_sockaddr_in, 1); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_singleton_method(rb_cSocket, "sockaddr_un", sock_s_pack_sockaddr_un, 1); rb_define_singleton_method(rb_cSocket, "pack_sockaddr_un", sock_s_pack_sockaddr_un, 1); rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1); diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 3500107972..5224e48a96 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -170,7 +170,7 @@ udp_send_internal(VALUE v) if (n >= 0) return RB_SSIZE2NUM(n); - if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) { + if (rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { goto retry; } } diff --git a/ext/socket/unixserver.c b/ext/socket/unixserver.c index 3a899cca1f..0ea5ac083c 100644 --- a/ext/socket/unixserver.c +++ b/ext/socket/unixserver.c @@ -10,7 +10,7 @@ #include "rubysocket.h" -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: * UNIXServer.new(path) => unixserver @@ -101,7 +101,7 @@ unix_sysaccept(VALUE server) void rsock_init_unixserver(void) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * Document-class: UNIXServer < UNIXSocket * diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index ecffbd4e92..26ab76fc9f 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -10,7 +10,7 @@ #include "rubysocket.h" -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN struct unixsock_arg { struct sockaddr_un *sockaddr; socklen_t sockaddrlen; @@ -43,6 +43,10 @@ unixsock_path_value(VALUE path) } } #endif +#ifdef _WIN32 + /* UNIXSocket requires UTF-8 per spec. */ + path = rb_str_export_to_enc(path, rb_utf8_encoding()); +#endif return rb_get_path(path); } @@ -571,7 +575,7 @@ unix_s_socketpair(int argc, VALUE *argv, VALUE klass) void rsock_init_unixsocket(void) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * Document-class: UNIXSocket < BasicSocket * diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 9ba2cd92a1..0054766dac 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -12,7 +12,7 @@ **********************************************************************/ -#define STRINGIO_VERSION "3.0.3" +#define STRINGIO_VERSION "3.0.4" #include "ruby.h" #include "ruby/io.h" @@ -257,9 +257,20 @@ strio_s_allocate(VALUE klass) } /* - * call-seq: StringIO.new(string=""[, mode]) + * call-seq: + * StringIO.new(string = '', mode = 'r+') -> new_stringio + * + * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. + * + * Returns a new \StringIO instance formed from +string+ and +mode+; + * see {Access Modes}[rdoc-ref:File@Access+Modes]: + * + * strio = StringIO.new # => #<StringIO> + * strio.close + * + * The instance should be closed when no longer needed. * - * Creates new StringIO instance from with _string_ and _mode_. + * Related: StringIO.open (accepts block; closes automatically). */ static VALUE strio_initialize(int argc, VALUE *argv, VALUE self) @@ -392,11 +403,26 @@ strio_finalize(VALUE self) } /* - * call-seq: StringIO.open(string=""[, mode]) {|strio| ...} + * call-seq: + * StringIO.open(string = '', mode = 'r+') {|strio| ... } + * + * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. + * + * Creates a new \StringIO instance formed from +string+ and +mode+; + * see {Access Modes}[rdoc-ref:File@Access+Modes]. + * + * With no block, returns the new instance: + * + * strio = StringIO.open # => #<StringIO> * - * Equivalent to StringIO.new except that when it is called with a block, it - * yields with the new instance and closes it, and returns the result which - * returned from the block. + * With a block, calls the block with the new instance + * and returns the block's value; + * closes the instance on block exit. + * + * StringIO.open {|strio| p strio } + * # => #<StringIO> + * + * Related: StringIO.new. */ static VALUE strio_s_open(int argc, VALUE *argv, VALUE klass) @@ -482,9 +508,23 @@ strio_unimpl(int argc, VALUE *argv, VALUE self) } /* - * call-seq: strio.string -> string + * call-seq: + * string -> string + * + * Returns underlying string: + * + * StringIO.open('foo') do |strio| + * p strio.string + * strio.string = 'bar' + * p strio.string + * end * - * Returns underlying String object, the subject of IO. + * Output: + * + * "foo" + * "bar" + * + * Related: StringIO#string= (assigns the underlying string). */ static VALUE strio_get_string(VALUE self) @@ -494,9 +534,23 @@ strio_get_string(VALUE self) /* * call-seq: - * strio.string = string -> string + * string = other_string -> other_string + * + * Assigns the underlying string as +other_string+, and sets position to zero; + * returns +other_string+: + * + * StringIO.open('foo') do |strio| + * p strio.string + * strio.string = 'bar' + * p strio.string + * end * - * Changes underlying String object, the subject of IO. + * Output: + * + * "foo" + * "bar" + * + * Related: StringIO#string (returns the underlying string). */ static VALUE strio_set_string(VALUE self, VALUE string) @@ -514,10 +568,13 @@ strio_set_string(VALUE self, VALUE string) /* * call-seq: - * strio.close -> nil + * close -> nil + * + * Closes +self+ for both reading and writing. * - * Closes a StringIO. The stream is unavailable for any further data - * operations; an +IOError+ is raised if such an attempt is made. + * Raises IOError if reading or writing is attempted. + * + * Related: StringIO#close_read, StringIO#close_write. */ static VALUE strio_close(VALUE self) @@ -529,10 +586,13 @@ strio_close(VALUE self) /* * call-seq: - * strio.close_read -> nil + * close_read -> nil + * + * Closes +self+ for reading; closed-write setting remains unchanged. * - * Closes the read end of a StringIO. Will raise an +IOError+ if the - * receiver is not readable. + * Raises IOError if reading is attempted. + * + * Related: StringIO#close, StringIO#close_write. */ static VALUE strio_close_read(VALUE self) @@ -547,10 +607,13 @@ strio_close_read(VALUE self) /* * call-seq: - * strio.close_write -> nil + * close_write -> nil + * + * Closes +self+ for writing; closed-read setting remains unchanged. + * + * Raises IOError if writing is attempted. * - * Closes the write end of a StringIO. Will raise an +IOError+ if the - * receiver is not writeable. + * Related: StringIO#close, StringIO#close_read. */ static VALUE strio_close_write(VALUE self) @@ -565,9 +628,10 @@ strio_close_write(VALUE self) /* * call-seq: - * strio.closed? -> true or false + * closed? -> true or false * - * Returns +true+ if the stream is completely closed, +false+ otherwise. + * Returns +true+ if +self+ is closed for both reading and writing, + * +false+ otherwise. */ static VALUE strio_closed(VALUE self) @@ -579,9 +643,9 @@ strio_closed(VALUE self) /* * call-seq: - * strio.closed_read? -> true or false + * closed_read? -> true or false * - * Returns +true+ if the stream is not readable, +false+ otherwise. + * Returns +true+ if +self+ is closed for reading, +false+ otherwise. */ static VALUE strio_closed_read(VALUE self) @@ -593,9 +657,9 @@ strio_closed_read(VALUE self) /* * call-seq: - * strio.closed_write? -> true or false + * closed_write? -> true or false * - * Returns +true+ if the stream is not writable, +false+ otherwise. + * Returns +true+ if +self+ is closed for writing, +false+ otherwise. */ static VALUE strio_closed_write(VALUE self) @@ -615,11 +679,14 @@ strio_to_read(VALUE self) /* * call-seq: - * strio.eof -> true or false - * strio.eof? -> true or false + * eof? -> true or false + * + * Returns +true+ if positioned at end-of-stream, +false+ otherwise; + * see {Position}[rdoc-ref:File@Position]. + * + * Raises IOError if the stream is not opened for reading. * - * Returns true if the stream is at the end of the data (underlying string). - * The stream must be opened for reading or an +IOError+ will be raised. + * StreamIO#eof is an alias for StreamIO#eof?. */ static VALUE strio_eof(VALUE self) @@ -649,13 +716,10 @@ strio_copy(VALUE copy, VALUE orig) /* * call-seq: - * strio.lineno -> integer + * lineno -> current_line_number * - * Returns the current line number. The stream must be - * opened for reading. +lineno+ counts the number of times +gets+ is - * called, rather than the number of newlines encountered. The two - * values will differ if +gets+ is called with a separator other than - * newline. See also the <code>$.</code> variable. + * Returns the current line number in +self+; + * see {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_get_lineno(VALUE self) @@ -665,10 +729,10 @@ strio_get_lineno(VALUE self) /* * call-seq: - * strio.lineno = integer -> integer + * lineno = new_line_number -> new_line_number * - * Manually sets the current line number to the given value. - * <code>$.</code> is updated only on the next read. + * Sets the current line number in +self+ to the given +new_line_number+; + * see {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_set_lineno(VALUE self, VALUE lineno) @@ -679,9 +743,10 @@ strio_set_lineno(VALUE self, VALUE lineno) /* * call-seq: - * strio.binmode -> stringio + * binmode -> self * - * Puts stream into binary mode. See IO#binmode. + * Sets the data mode in +self+ to binary mode; + * see {Data Mode}[rdoc-ref:File@Data+Mode]. * */ static VALUE @@ -705,11 +770,27 @@ strio_binmode(VALUE self) /* * call-seq: - * strio.reopen(other_StrIO) -> strio - * strio.reopen(string, mode) -> strio + * reopen(other, mode = 'r+') -> self + * + * Reinitializes the stream with the given +other+ (string or StringIO) and +mode+; + * see IO.new: + * + * StringIO.open('foo') do |strio| + * p strio.string + * strio.reopen('bar') + * p strio.string + * other_strio = StringIO.new('baz') + * strio.reopen(other_strio) + * p strio.string + * other_strio.close + * end + * + * Output: + * + * "foo" + * "bar" + * "baz" * - * Reinitializes the stream with the given <i>other_StrIO</i> or _string_ - * and _mode_ (see StringIO#new). */ static VALUE strio_reopen(int argc, VALUE *argv, VALUE self) @@ -723,10 +804,12 @@ strio_reopen(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.pos -> integer - * strio.tell -> integer + * pos -> stream_position + * + * Returns the current position (in bytes); + * see {Position}[rdoc-ref:IO@Position]. * - * Returns the current offset (in bytes). + * StringIO#tell is an alias for StringIO#pos. */ static VALUE strio_get_pos(VALUE self) @@ -736,9 +819,10 @@ strio_get_pos(VALUE self) /* * call-seq: - * strio.pos = integer -> integer + * pos = new_position -> new_position * - * Seeks to the given position (in bytes). + * Sets the current position (in bytes); + * see {Position}[rdoc-ref:IO@Position]. */ static VALUE strio_set_pos(VALUE self, VALUE pos) @@ -754,10 +838,11 @@ strio_set_pos(VALUE self, VALUE pos) /* * call-seq: - * strio.rewind -> 0 + * rewind -> 0 * - * Positions the stream to the beginning of input, resetting - * +lineno+ to zero. + * Sets the current position and line number to zero; + * see {Position}[rdoc-ref:IO@Position] + * and {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_rewind(VALUE self) @@ -770,10 +855,11 @@ strio_rewind(VALUE self) /* * call-seq: - * strio.seek(amount, whence=SEEK_SET) -> 0 + * seek(offset, whence = SEEK_SET) -> 0 * - * Seeks to a given offset _amount_ in the stream according to - * the value of _whence_ (see IO#seek). + * Sets the current position to the given integer +offset+ (in bytes), + * with respect to a given constant +whence+; + * see {Position}[rdoc-ref:IO@Position]. */ static VALUE strio_seek(int argc, VALUE *argv, VALUE self) @@ -809,9 +895,9 @@ strio_seek(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.sync -> true + * sync -> true * - * Returns +true+ always. + * Returns +true+; implemented only for compatibility with other stream classes. */ static VALUE strio_get_sync(VALUE self) @@ -826,10 +912,12 @@ strio_get_sync(VALUE self) /* * call-seq: - * strio.each_byte {|byte| block } -> strio - * strio.each_byte -> anEnumerator + * each_byte {|byte| ... } -> self * - * See IO#each_byte. + * With a block given, calls the block with each remaining byte in the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. + * + * With no block given, returns an enumerator. */ static VALUE strio_each_byte(VALUE self) @@ -847,9 +935,10 @@ strio_each_byte(VALUE self) /* * call-seq: - * strio.getc -> string or nil + * getc -> character or nil * - * See IO#getc. + * Reads and returns the next character from the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_getc(VALUE self) @@ -872,9 +961,10 @@ strio_getc(VALUE self) /* * call-seq: - * strio.getbyte -> fixnum or nil + * getbyte -> byte or nil * - * See IO#getbyte. + * Reads and returns the next 8-bit byte from the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_getbyte(VALUE self) @@ -910,12 +1000,10 @@ strio_extend(struct StringIO *ptr, long pos, long len) /* * call-seq: - * strio.ungetc(string) -> nil + * ungetc(character) -> nil * - * Pushes back one character (passed as a parameter) - * such that a subsequent buffered read will return it. There is no - * limitation for multiple pushbacks including pushing back behind the - * beginning of the buffer string. + * Pushes back ("unshifts") a character or integer onto the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_ungetc(VALUE self, VALUE c) @@ -950,9 +1038,10 @@ strio_ungetc(VALUE self, VALUE c) /* * call-seq: - * strio.ungetbyte(fixnum) -> nil + * ungetbyte(byte) -> nil * - * See IO#ungetbyte + * Pushes back ("unshifts") an 8-bit byte onto the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_ungetbyte(VALUE self, VALUE c) @@ -1012,9 +1101,10 @@ strio_unget_bytes(struct StringIO *ptr, const char *cp, long cl) /* * call-seq: - * strio.readchar -> string + * readchar -> string * - * See IO#readchar. + * Like +getc+, but raises an exception if already at end-of-stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_readchar(VALUE self) @@ -1026,9 +1116,10 @@ strio_readchar(VALUE self) /* * call-seq: - * strio.readbyte -> fixnum + * readbyte -> byte * - * See IO#readbyte. + * Like +getbyte+, but raises an exception if already at end-of-stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_readbyte(VALUE self) @@ -1040,10 +1131,12 @@ strio_readbyte(VALUE self) /* * call-seq: - * strio.each_char {|char| block } -> strio - * strio.each_char -> anEnumerator + * each_char {|c| ... } -> self + * + * With a block given, calls the block with each remaining character in the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. * - * See IO#each_char. + * With no block given, returns an enumerator. */ static VALUE strio_each_char(VALUE self) @@ -1060,10 +1153,12 @@ strio_each_char(VALUE self) /* * call-seq: - * strio.each_codepoint {|c| block } -> strio - * strio.each_codepoint -> anEnumerator + * each_codepoint {|codepoint| ... } -> self * - * See IO#each_codepoint. + * With a block given, calls the block with each remaining codepoint in the stream; + * see {Codepoint IO}[rdoc-ref:IO@Codepoint+IO]. + * + * With no block given, returns an enumerator. */ static VALUE strio_each_codepoint(VALUE self) @@ -1245,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; @@ -1273,11 +1369,13 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) /* * call-seq: - * strio.gets(sep=$/, chomp: false) -> string or nil - * strio.gets(limit, chomp: false) -> string or nil - * strio.gets(sep, limit, chomp: false) -> string or nil + * gets(sep = $/, chomp: false) -> string or nil + * gets(limit, chomp: false) -> string or nil + * gets(sep, limit, chomp: false) -> string or nil * - * See IO#gets. + * Reads and returns a line from the stream; + * assigns the return value to <tt>$_</tt>; + * see {Line IO}[rdoc-ref:IO@Line+IO]. */ static VALUE strio_gets(int argc, VALUE *argv, VALUE self) @@ -1297,11 +1395,12 @@ strio_gets(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.readline(sep=$/, chomp: false) -> string - * strio.readline(limit, chomp: false) -> string or nil - * strio.readline(sep, limit, chomp: false) -> string or nil + * readline(sep = $/, chomp: false) -> string + * readline(limit, chomp: false) -> string + * readline(sep, limit, chomp: false) -> string * - * See IO#readline. + * Reads a line as with IO#gets, but raises EOFError if already at end-of-file; + * see {Line IO}[rdoc-ref:IO@Line+IO]. */ static VALUE strio_readline(int argc, VALUE *argv, VALUE self) @@ -1313,17 +1412,16 @@ strio_readline(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.each(sep=$/, chomp: false) {|line| block } -> strio - * strio.each(limit, chomp: false) {|line| block } -> strio - * strio.each(sep, limit, chomp: false) {|line| block } -> strio - * strio.each(...) -> anEnumerator + * each_line(sep = $/, chomp: false) {|line| ... } -> self + * each_line(limit, chomp: false) {|line| ... } -> self + * each_line(sep, limit, chomp: false) {|line| ... } -> self * - * strio.each_line(sep=$/, chomp: false) {|line| block } -> strio - * strio.each_line(limit, chomp: false) {|line| block } -> strio - * strio.each_line(sep, limit, chomp: false) {|line| block } -> strio - * strio.each_line(...) -> anEnumerator + * Calls the block with each remaining line read from the stream; + * does nothing if already at end-of-file; + * returns +self+. + * See {Line IO}[rdoc-ref:IO@Line+IO]. * - * See IO#each. + * StringIO#each is an alias for StringIO#each_line. */ static VALUE strio_each(int argc, VALUE *argv, VALUE self) @@ -1751,24 +1849,16 @@ strio_set_encoding_by_bom(VALUE self) } /* - * Pseudo I/O on String object, with interface corresponding to IO. + * \IO streams for strings, with access similar to + * {IO}[rdoc-ref:IO]; + * see {IO}[rdoc-ref:IO]. * - * Commonly used to simulate <code>$stdio</code> or <code>$stderr</code> + * === About the Examples * - * === Examples + * Examples on this page assume that \StringIO has been required: * * require 'stringio' * - * # Writing stream emulation - * io = StringIO.new - * io.puts "Hello World" - * io.string #=> "Hello World\n" - * - * # Reading stream emulation - * io = StringIO.new "first\nsecond\nlast\n" - * io.getc #=> "f" - * io.gets #=> "irst\n" - * io.read #=> "second\nlast\n" */ void Init_stringio(void) diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index f0ecbf85d8..3c311d2364 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true require 'mkmf' -$INCFLAGS << " -I$(top_srcdir)" if $extmk -have_func("onig_region_memsize", "ruby.h") -create_makefile 'strscan' +if RUBY_ENGINE == 'ruby' + $INCFLAGS << " -I$(top_srcdir)" if $extmk + 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) +end diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index e1426380b4..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.1" +#define STRSCAN_VERSION "3.0.7" /* ======================================================================= Data Type Definitions @@ -435,11 +435,11 @@ strscan_get_pos(VALUE self) * * In short, it's a 0-based index into the string. * - * s = StringScanner.new("abcädeföghi") - * s.charpos # -> 0 - * s.scan_until(/ä/) # -> "abcä" - * s.pos # -> 5 - * s.charpos # -> 4 + * s = StringScanner.new("abc\u00e4def\u00f6ghi") + * s.charpos # -> 0 + * s.scan_until(/\u00e4/) # -> "abc\u00E4" + * s.pos # -> 5 + * s.charpos # -> 4 */ static VALUE strscan_get_charpos(VALUE self) @@ -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) @@ -1458,6 +1488,56 @@ strscan_fixed_anchor_p(VALUE self) return p->fixed_anchor_p ? Qtrue : Qfalse; } +typedef struct { + VALUE self; + VALUE captures; +} named_captures_data; + +static int +named_captures_iter(const OnigUChar *name, + const OnigUChar *name_end, + int back_num, + int *back_refs, + OnigRegex regex, + void *arg) +{ + named_captures_data *data = arg; + + VALUE key = rb_str_new((const char *)name, name_end - name); + VALUE value = RUBY_Qnil; + int i; + for (i = 0; i < back_num; i++) { + value = strscan_aref(data->self, INT2NUM(back_refs[i])); + } + rb_hash_aset(data->captures, key, value); + return 0; +} + +/* + * call-seq: + * scanner.named_captures -> hash + * + * Returns a hash of string variables matching the regular expression. + * + * scan = StringScanner.new('foobarbaz') + * scan.match?(/(?<f>foo)(?<r>bar)(?<z>baz)/) + * scan.named_captures # -> {"f"=>"foo", "r"=>"bar", "z"=>"baz"} + */ +static VALUE +strscan_named_captures(VALUE self) +{ + struct strscanner *p; + GET_SCANNER(self, p); + named_captures_data data; + data.self = self; + data.captures = rb_hash_new(); + if (!RB_NIL_P(p->regex)) { + onig_foreach_name(RREGEXP_PTR(p->regex), named_captures_iter, &data); + } + + return data.captures; +} + /* ======================================================================= Ruby Interface ======================================================================= */ @@ -1468,6 +1548,8 @@ strscan_fixed_anchor_p(VALUE self) * StringScanner provides for lexical scanning operations on a String. Here is * an example of its usage: * + * require 'strscan' + * * s = StringScanner.new('This is an example string') * s.eos? # -> false * @@ -1650,4 +1732,6 @@ Init_strscan(void) rb_define_method(StringScanner, "inspect", strscan_inspect, 0); rb_define_method(StringScanner, "fixed_anchor?", strscan_fixed_anchor_p, 0); + + rb_define_method(StringScanner, "named_captures", strscan_named_captures, 0); } diff --git a/ext/strscan/strscan.gemspec b/ext/strscan/strscan.gemspec index 5d8119ea4c..8a61c7abe6 100644 --- a/ext/strscan/strscan.gemspec +++ b/ext/strscan/strscan.gemspec @@ -16,13 +16,26 @@ Gem::Specification.new do |s| s.summary = "Provides lexical scanning operations on a String." s.description = "Provides lexical scanning operations on a String." - s.require_path = %w{lib} - s.files = %w{ext/strscan/extconf.rb ext/strscan/strscan.c} - s.extensions = %w{ext/strscan/extconf.rb} + files = [ + "COPYING", + "LICENSE.txt", + ] + if RUBY_ENGINE == "jruby" + s.require_paths = %w{ext/jruby/lib lib} + files << "ext/jruby/lib/strscan.rb" + files << "lib/strscan.jar" + s.platform = "java" + else + s.require_paths = %w{lib} + files << "ext/strscan/extconf.rb" + files << "ext/strscan/strscan.c" + s.extensions = %w{ext/strscan/extconf.rb} + end + s.files = files s.required_ruby_version = ">= 2.4.0" - s.authors = ["Minero Aoki", "Sutou Kouhei"] - s.email = [nil, "kou@cozmixng.org"] + s.authors = ["Minero Aoki", "Sutou Kouhei", "Charles Oliver Nutter"] + s.email = [nil, "kou@cozmixng.org", "headius@headius.com"] s.homepage = "https://github.com/ruby/strscan" s.licenses = ["Ruby", "BSD-2-Clause"] end 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 # diff --git a/ext/win32ole/win32ole.gemspec b/ext/win32ole/win32ole.gemspec index 977555c98d..b6ea8e8a55 100644 --- a/ext/win32ole/win32ole.gemspec +++ b/ext/win32ole/win32ole.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "win32ole" - spec.version = "1.8.8" + spec.version = "1.8.9" spec.authors = ["Masaki Suketa"] spec.email = ["suke@ruby-lang.org"] diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 20079a5d4c..aefdba0ebd 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "2.1.1" +#define RUBY_ZLIB_VERSION "3.0.0" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) @@ -115,6 +115,8 @@ int flock(int, int); # define link(f, t) rb_w32_ulink((f), (t)) # undef unlink # define unlink(p) rb_w32_uunlink(p) +# undef readlink +# define readlink(f, t, l) rb_w32_ureadlink((f), (t), (l)) # undef rename # define rename(f, t) rb_w32_urename((f), (t)) # undef symlink @@ -482,41 +484,6 @@ apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg) return LONG2FIX(argc); } -/* - * call-seq: - * path -> filepath - * - * Returns the string filepath used to create +self+: - * - * f = File.new('t.txt') # => #<File:t.txt> - f.path # => "t.txt" - * - * Does not normalize the returned filepath: - * - * f = File.new('../files/t.txt') # => #<File:../files/t.txt> - f.path # => "../files/t.txt" - * - * Raises IOError for a file created using File::Constants::TMPFILE, because it has no filename. - * - * File#to_path is an alias for File#path. - * - */ - -static VALUE -rb_file_path(VALUE obj) -{ - rb_io_t *fptr; - - fptr = RFILE(rb_io_taint_check(obj))->fptr; - rb_io_check_initialized(fptr); - - if (NIL_P(fptr->pathv)) { - rb_raise(rb_eIOError, "File is unnamed (TMPFILE?)"); - } - - return rb_str_dup(fptr->pathv); -} - static size_t stat_memsize(const void *p) { @@ -1763,8 +1730,8 @@ rb_file_socket_p(VALUE obj, VALUE fname) if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISSOCK(st.st_mode)) return Qtrue; - #endif + return Qfalse; } @@ -2873,7 +2840,9 @@ utime_failed(struct apply_arg *aa) #if defined(HAVE_UTIMES) -# if defined(__APPLE__) && \ +# if !defined(HAVE_UTIMENSAT) +/* utimensat() is not found, runtime check is not needed */ +# elif defined(__APPLE__) && \ (!defined(MAC_OS_X_VERSION_13_0) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_13_0)) # if defined(__has_attribute) && __has_attribute(availability) @@ -2892,10 +2861,6 @@ RBIMPL_WARNING_POP() # else /* __API_AVAILABLE macro does nothing on gcc */ __attribute__((weak)) int utimensat(int, const char *, const struct timespec [2], int); # endif - -# define utimensat_available_p() (utimensat != NULL) -# else -# define utimensat_available_p() 1 # endif static int @@ -3154,7 +3119,6 @@ rb_file_s_readlink(VALUE klass, VALUE path) return rb_readlink(path, rb_filesystem_encoding()); } -#ifndef _WIN32 struct readlink_arg { const char *path; char *buf; @@ -3210,7 +3174,6 @@ rb_readlink(VALUE path, rb_encoding *enc) return v; } -#endif #else #define rb_file_s_readlink rb_f_notimplement #endif @@ -4902,7 +4865,7 @@ rb_file_dirname_n(VALUE fname, int n) * dotfile top 0 * end with dot dot 1 * .ext dot len of .ext - * .ext:stream dot len of .ext without :stream (NT only) + * .ext:stream dot len of .ext without :stream (NTFS only) * */ const char * @@ -5379,8 +5342,7 @@ test_check(int n, int argc, VALUE *argv) * "d" | boolean | True if file1 exists and is a directory * "e" | boolean | True if file1 exists * "f" | boolean | True if file1 exists and is a regular file - * "g" | boolean | True if file1 has the \CF{setgid} bit - * | | set (false under NT) + * "g" | boolean | True if file1 has the setgid bit set * "G" | boolean | True if file1 exists and has a group * | | ownership equal to the caller's group * "k" | boolean | True if file1 exists and has the sticky bit set @@ -5603,6 +5565,7 @@ rb_stat_init(VALUE obj, VALUE fname) if (STAT(StringValueCStr(fname), &st) == -1) { rb_sys_fail_path(fname); } + if (DATA_PTR(obj)) { xfree(DATA_PTR(obj)); DATA_PTR(obj) = NULL; @@ -5811,7 +5774,7 @@ rb_stat_rowned(VALUE obj) * stat.grpowned? -> true or false * * Returns true if the effective group id of the process is the same as - * the group id of <i>stat</i>. On Windows NT, returns <code>false</code>. + * the group id of <i>stat</i>. On Windows, returns <code>false</code>. * * File.stat("testfile").grpowned? #=> true * File.stat("/etc/passwd").grpowned? #=> false @@ -7040,7 +7003,7 @@ const char ruby_null_device[] = * f.pos = 800 * f.read # => "" * - * ==== Data Mode + * ==== \Data Mode * * To specify whether data is to be treated as text or as binary data, * either of the following may be suffixed to any of the string read/write modes @@ -7109,9 +7072,9 @@ const char ruby_null_device[] = * - +File::CREAT+: Create file if it does not exist. * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists. * - * === Data Mode Specified as an \Integer + * === \Data Mode Specified as an \Integer * - * Data mode cannot be specified as an integer. + * \Data mode cannot be specified as an integer. * When the stream access mode is given as an integer, * the data mode is always text, never binary. * @@ -7150,7 +7113,7 @@ const char ruby_null_device[] = * strings read are converted from external to internal encoding, * and strings written are converted from internal to external encoding. * For further details about transcoding input and output, - * see {Encodings}[rdoc-ref:io_streams.rdoc@Encodings]. + * see {Encodings}[rdoc-ref:encodings.rdoc@Encodings]. * * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt> * or <tt>'BOM|UTF16-BE'</tt>, @@ -7214,7 +7177,7 @@ const char ruby_null_device[] = * f.chmod(0644) * f.chmod(0444) * - * == \File Constants + * == \File \Constants * * Various constants for use in \File and \IO methods * may be found in module File::Constants; @@ -7286,7 +7249,7 @@ const char ruby_null_device[] = * * - ::blockdev?: Returns whether the file at the given path is a block device. * - ::chardev?: Returns whether the file at the given path is a character device. - * - ::directory?: Returns whether the file at the given path is a diretory. + * - ::directory?: Returns whether the file at the given path is a directory. * - ::executable?: Returns whether the file at the given path is executable * by the effective user and group of the current process. * - ::executable_real?: Returns whether the file at the given path is executable @@ -7557,8 +7520,6 @@ Init_File(void) /* Name of the null device */ rb_define_const(rb_mFConst, "NULL", rb_fstring_cstr(ruby_null_device)); - rb_define_method(rb_cFile, "path", rb_file_path, 0); - rb_define_method(rb_cFile, "to_path", rb_file_path, 0); rb_define_global_function("test", rb_f_test, -1); rb_cStat = rb_define_class_under(rb_cFile, "Stat", rb_cObject); @@ -138,6 +138,7 @@ #include "ractor_core.h" #include "builtin.h" +#include "shape.h" #define rb_setjmp(env) RUBY_SETJMP(env) #define rb_jmp_buf rb_jmpbuf_t @@ -573,6 +574,7 @@ struct RMoved { VALUE flags; VALUE dummy; VALUE destination; + shape_id_t original_shape_id; }; #define RMOVED(obj) ((struct RMoved *)(obj)) @@ -620,17 +622,29 @@ typedef struct RVALUE { VALUE v3; } values; } as; + + /* Start of RVALUE_OVERHEAD. + * Do not directly read these members from the RVALUE as they're located + * at the end of the slot (which may differ in size depending on the size + * pool). */ +#if RACTOR_CHECK_MODE + uint32_t _ractor_belonging_id; +#endif #if GC_DEBUG const char *file; int line; #endif } RVALUE; -#if GC_DEBUG -STATIC_ASSERT(sizeof_rvalue, offsetof(RVALUE, file) == SIZEOF_VALUE * 5); +#if RACTOR_CHECK_MODE +# define RVALUE_OVERHEAD (sizeof(RVALUE) - offsetof(RVALUE, _ractor_belonging_id)) +#elif GC_DEBUG +# define RVALUE_OVERHEAD (sizeof(RVALUE) - offsetof(RVALUE, file)) #else -STATIC_ASSERT(sizeof_rvalue, sizeof(RVALUE) == SIZEOF_VALUE * 5); +# define RVALUE_OVERHEAD 0 #endif + +STATIC_ASSERT(sizeof_rvalue, sizeof(RVALUE) == (SIZEOF_VALUE * 5) + RVALUE_OVERHEAD); STATIC_ASSERT(alignof_rvalue, RUBY_ALIGNOF(RVALUE) == SIZEOF_VALUE); typedef uintptr_t bits_t; @@ -870,7 +884,7 @@ typedef struct rb_objspace { #define BASE_SLOT_SIZE sizeof(RVALUE) -#define CEILDIV(i, mod) (((i) + (mod) - 1)/(mod)) +#define CEILDIV(i, mod) roomof(i, mod) enum { HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG), HEAP_PAGE_ALIGN_MASK = (~(~0UL << HEAP_PAGE_ALIGN_LOG)), @@ -932,7 +946,6 @@ struct heap_page { short slot_size; short total_slots; short free_slots; - short pinned_slots; short final_slots; struct { unsigned int before_sweep : 1; @@ -1359,6 +1372,27 @@ tick(void) return val; } +/* Implementation for macOS PPC by @nobu + * See: https://github.com/ruby/ruby/pull/5975#discussion_r890045558 + */ +#elif defined(__POWERPC__) && defined(__APPLE__) +typedef unsigned long long tick_t; +#define PRItick "llu" + +static __inline__ tick_t +tick(void) +{ + unsigned long int upper, lower, tmp; + # define mftbu(r) __asm__ volatile("mftbu %0" : "=r"(r)) + # define mftb(r) __asm__ volatile("mftb %0" : "=r"(r)) + do { + mftbu(upper); + mftb(lower); + mftbu(tmp); + } while (tmp != upper); + return ((tick_t)upper << 32) | lower; +} + #elif defined(__aarch64__) && defined(__GNUC__) typedef unsigned long tick_t; #define PRItick "lu" @@ -2440,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 */ @@ -2554,7 +2589,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, size_t rb_gc_obj_slot_size(VALUE obj) { - return GET_HEAP_PAGE(obj)->slot_size; + return GET_HEAP_PAGE(obj)->slot_size - RVALUE_OVERHEAD; } static inline size_t @@ -2569,9 +2604,17 @@ size_pool_slot_size(unsigned char pool_id) GC_ASSERT(size_pools[pool_id].slot_size == (short)slot_size); #endif + slot_size -= RVALUE_OVERHEAD; + return slot_size; } +size_t +rb_size_pool_slot_size(unsigned char pool_id) +{ + return size_pool_slot_size(pool_id); +} + bool rb_gc_size_allocatable_p(size_t size) { @@ -2677,6 +2720,8 @@ static inline size_t size_pool_idx_for_size(size_t size) { #if USE_RVARGC + size += RVALUE_OVERHEAD; + size_t slot_count = CEILDIV(size, BASE_SLOT_SIZE); /* size_pool_idx is ceil(log2(slot_count)) */ @@ -2751,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 @@ -2776,9 +2827,12 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t * } obj = newobj_alloc(objspace, cr, size_pool_idx, true); +#if SHAPE_IN_BASIC_FLAGS + flags |= (VALUE)(size_pool_idx) << SHAPE_FLAG_SHIFT; +#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); @@ -2827,6 +2881,9 @@ newobj_of0(VALUE klass, VALUE flags, int wb_protected, rb_ractor_t *cr, size_t a gc_event_hook_available_p(objspace)) && wb_protected) { obj = newobj_alloc(objspace, cr, size_pool_idx, false); +#if SHAPE_IN_BASIC_FLAGS + flags |= (VALUE)size_pool_idx << SHAPE_FLAG_SHIFT; +#endif newobj_init(klass, flags, wb_protected, objspace, obj); } else { @@ -2880,7 +2937,7 @@ rb_ec_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flag VALUE rb_newobj(void) { - return newobj_of(0, T_NONE, 0, 0, 0, FALSE, sizeof(RVALUE)); + return newobj_of(0, T_NONE, 0, 0, 0, FALSE, RVALUE_SIZE); } static size_t @@ -2895,44 +2952,33 @@ rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected) GC_ASSERT((flags & RUBY_T_MASK) == T_OBJECT); GC_ASSERT(flags & ROBJECT_EMBED); - st_table *index_tbl = RCLASS_IV_INDEX_TBL(klass); - uint32_t index_tbl_num_entries = index_tbl == NULL ? 0 : (uint32_t)index_tbl->num_entries; - size_t size; - bool embed = true; #if USE_RVARGC + uint32_t index_tbl_num_entries = RCLASS_EXT(klass)->max_iv_count; + size = rb_obj_embedded_size(index_tbl_num_entries); if (!rb_gc_size_allocatable_p(size)) { size = sizeof(struct RObject); - embed = false; } #else size = sizeof(struct RObject); - if (index_tbl_num_entries > ROBJECT_EMBED_LEN_MAX) { - embed = false; - } #endif -#if USE_RVARGC VALUE obj = newobj_of(klass, flags, 0, 0, 0, wb_protected, size); -#else - VALUE obj = newobj_of(klass, flags, Qundef, Qundef, Qundef, wb_protected, size); -#endif + RUBY_ASSERT(rb_shape_get_shape(obj)->type == SHAPE_ROOT || + rb_shape_get_shape(obj)->type == SHAPE_INITIAL_CAPACITY); - if (embed) { -#if USE_RVARGC - uint32_t capa = (uint32_t)((rb_gc_obj_slot_size(obj) - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); - GC_ASSERT(capa >= index_tbl_num_entries); + // Set the shape to the specific T_OBJECT shape which is always + // SIZE_POOL_COUNT away from the root shape. + ROBJECT_SET_SHAPE_ID(obj, ROBJECT_SHAPE_ID(obj) + SIZE_POOL_COUNT); - ROBJECT(obj)->numiv = capa; - for (size_t i = 0; i < capa; i++) { - ROBJECT(obj)->as.ary[i] = Qundef; - } -#endif - } - else { - rb_init_iv_list(obj); +#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; } +#endif return obj; } @@ -2944,7 +2990,7 @@ rb_newobj_of(VALUE klass, VALUE flags) return rb_class_instance_allocate_internal(klass, (flags | ROBJECT_EMBED) & ~FL_WB_PROTECTED, flags & FL_WB_PROTECTED); } else { - return newobj_of(klass, flags & ~FL_WB_PROTECTED, 0, 0, 0, flags & FL_WB_PROTECTED, sizeof(RVALUE)); + return newobj_of(klass, flags & ~FL_WB_PROTECTED, 0, 0, 0, flags & FL_WB_PROTECTED, RVALUE_SIZE); } } @@ -2982,7 +3028,7 @@ rb_imemo_name(enum imemo_type type) VALUE rb_imemo_new(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0) { - size_t size = sizeof(RVALUE); + size_t size = RVALUE_SIZE; VALUE flags = T_IMEMO | (type << FL_USHIFT); return newobj_of(v0, flags, v1, v2, v3, TRUE, size); } @@ -2990,7 +3036,7 @@ rb_imemo_new(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0) static VALUE rb_imemo_tmpbuf_new(VALUE v1, VALUE v2, VALUE v3, VALUE v0) { - size_t size = sizeof(RVALUE); + size_t size = sizeof(struct rb_imemo_tmpbuf_struct); VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); return newobj_of(v0, flags, v1, v2, v3, FALSE, size); } @@ -3051,7 +3097,7 @@ rb_imemo_new_debug(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0, } #endif -VALUE +MJIT_FUNC_EXPORTED VALUE rb_class_allocate_instance(VALUE klass) { return rb_class_instance_allocate_internal(klass, T_OBJECT | ROBJECT_EMBED, RGENGC_WB_PROTECTED_OBJECT); @@ -3071,7 +3117,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU { RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); if (klass) rb_data_object_check(klass); - return newobj_of(klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, FALSE, sizeof(RVALUE)); + return newobj_of(klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, FALSE, sizeof(struct RTypedData)); } VALUE @@ -3087,7 +3133,7 @@ rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) { RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); - return newobj_of(klass, T_DATA, (VALUE)type, (VALUE)1, (VALUE)datap, type->flags & RUBY_FL_WB_PROTECTED, sizeof(RVALUE)); + return newobj_of(klass, T_DATA, (VALUE)type, (VALUE)1, (VALUE)datap, type->flags & RUBY_FL_WB_PROTECTED, sizeof(struct RTypedData)); } VALUE @@ -3206,20 +3252,6 @@ rb_free_const_table(struct rb_id_table *tbl) rb_id_table_free(tbl); } -static int -free_iv_index_tbl_free_i(st_data_t key, st_data_t value, st_data_t data) -{ - xfree((void *)value); - return ST_CONTINUE; -} - -static void -iv_index_tbl_free(struct st_table *tbl) -{ - st_foreach(tbl, free_iv_index_tbl_free_i, 0); - st_free_table(tbl); -} - // alive: if false, target pointers can be freed already. // To check it, we need objspace parameter. static void @@ -3428,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)) { @@ -3443,15 +3479,12 @@ obj_free(rb_objspace_t *objspace, VALUE obj) case T_CLASS: rb_id_table_free(RCLASS_M_TBL(obj)); cc_table_free(objspace, obj, FALSE); - if (RCLASS_IV_TBL(obj)) { - st_free_table(RCLASS_IV_TBL(obj)); + if (RCLASS_IVPTR(obj)) { + xfree(RCLASS_IVPTR(obj)); } if (RCLASS_CONST_TBL(obj)) { rb_free_const_table(RCLASS_CONST_TBL(obj)); } - if (RCLASS_IV_INDEX_TBL(obj)) { - iv_index_tbl_free(RCLASS_IV_INDEX_TBL(obj)); - } if (RCLASS_CVC_TBL(obj)) { rb_id_table_foreach_values(RCLASS_CVC_TBL(obj), cvar_table_free_i, NULL); rb_id_table_free(RCLASS_CVC_TBL(obj)); @@ -3462,11 +3495,8 @@ obj_free(rb_objspace_t *objspace, VALUE obj) if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) { xfree(RCLASS_SUPERCLASSES(obj)); } -#if SIZEOF_SERIAL_T != SIZEOF_VALUE && USE_RVARGC - xfree(RCLASS(obj)->class_serial_ptr); -#endif -#if !USE_RVARGC +#if SIZE_POOL_COUNT == 1 if (RCLASS_EXT(obj)) xfree(RCLASS_EXT(obj)); #endif @@ -3632,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 @@ -4311,7 +4341,7 @@ run_single_final(VALUE cmd, VALUE objid) static void warn_exception_in_finalizer(rb_execution_context_t *ec, VALUE final) { - if (final != Qundef && !NIL_P(ruby_verbose)) { + if (!UNDEF_P(final) && !NIL_P(ruby_verbose)) { VALUE errinfo = ec->errinfo; rb_warn("Exception in finalizer %+"PRIsVALUE, final); rb_ec_error_print(ec, errinfo); @@ -4328,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; @@ -4677,7 +4711,7 @@ id2ref(VALUE objid) } } - if ((orig = id2ref_obj_tbl(objspace, objid)) != Qundef && + if (!UNDEF_P(orig = id2ref_obj_tbl(objspace, objid)) && is_live_object(objspace, orig)) { if (!rb_multi_ractor_p() || rb_ractor_shareable_p(orig)) { @@ -4696,6 +4730,7 @@ id2ref(VALUE objid) } } +/* :nodoc: */ static VALUE os_id2ref(VALUE os, VALUE objid) { @@ -4857,8 +4892,11 @@ obj_memsize_of(VALUE obj, int use_all_types) switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { - size += ROBJECT_NUMIV(obj) * sizeof(VALUE); + 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; case T_MODULE: @@ -4867,19 +4905,11 @@ obj_memsize_of(VALUE obj, int use_all_types) if (RCLASS_M_TBL(obj)) { size += rb_id_table_memsize(RCLASS_M_TBL(obj)); } - if (RCLASS_IV_TBL(obj)) { - size += st_memsize(RCLASS_IV_TBL(obj)); - } + // class IV sizes are allocated as powers of two + size += SIZEOF_VALUE << bit_length(RCLASS_IV_COUNT(obj)); if (RCLASS_CVC_TBL(obj)) { size += rb_id_table_memsize(RCLASS_CVC_TBL(obj)); } - if (RCLASS_IV_INDEX_TBL(obj)) { - // TODO: more correct value - size += st_memsize(RCLASS_IV_INDEX_TBL(obj)); - } - if (RCLASS_EXT(obj)->iv_tbl) { - size += st_memsize(RCLASS_EXT(obj)->iv_tbl); - } if (RCLASS_EXT(obj)->const_tbl) { size += rb_id_table_memsize(RCLASS_EXT(obj)->const_tbl); } @@ -4889,7 +4919,7 @@ obj_memsize_of(VALUE obj, int use_all_types) if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) { size += (RCLASS_SUPERCLASS_DEPTH(obj) + 1) * sizeof(VALUE); } -#if !USE_RVARGC +#if SIZE_POOL_COUNT == 1 size += sizeof(rb_classext_t); #endif } @@ -4980,7 +5010,7 @@ obj_memsize_of(VALUE obj, int use_all_types) BUILTIN_TYPE(obj), (void*)obj); } - return size + GET_HEAP_PAGE(obj)->slot_size; + return size + rb_gc_obj_slot_size(obj); } size_t @@ -5212,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; @@ -5220,33 +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)); - - VALUE dest = (VALUE)free_page->freelist; - 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(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(RB_BUILTIN_TYPE(dest) == T_NONE); - 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--; + 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; } @@ -5823,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; @@ -5842,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); @@ -6069,9 +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); @@ -6774,8 +6822,10 @@ mark_current_machine_context(rb_objspace_t *objspace, rb_execution_context_t *ec static void mark_current_machine_context(rb_objspace_t *objspace, rb_execution_context_t *ec) { - rb_wasm_scan_stack(rb_mark_locations); - each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], gc_mark_maybe); + VALUE *stack_start, *stack_end; + SET_STACK_END; + GET_STACK_BOUNDS(stack_start, stack_end, 1); + each_stack_location(objspace, ec, stack_start, stack_end, gc_mark_maybe); rb_wasm_scan_locals(rb_mark_locations); each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], gc_mark_maybe); @@ -7169,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) { @@ -7215,8 +7267,11 @@ 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); - mark_tbl_no_pin(objspace, RCLASS_IV_TBL(obj)); + for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { + gc_mark(objspace, RCLASS_IVPTR(obj)[i]); + } mark_const_tbl(objspace, RCLASS_CONST_TBL(obj)); break; @@ -7280,16 +7335,31 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) case T_OBJECT: { - const VALUE * const ptr = ROBJECT_IVPTR(obj); + 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); - uint32_t i, len = ROBJECT_NUMIV(obj); - for (i = 0; i < len; i++) { - gc_mark(objspace, ptr[i]); + 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); - if (LIKELY(during_gc) && - ROBJ_TRANSIENT_P(obj)) { - rb_transient_heap_mark(obj, ptr); + // Increment max_iv_count if applicable, used to determine size pool allocation + uint32_t num_of_ivs = shape->next_iv_index; + if (RCLASS_EXT(klass)->max_iv_count < num_of_ivs) { + RCLASS_EXT(klass)->max_iv_count = num_of_ivs; + } } } break; @@ -7303,6 +7373,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) gc_mark(objspace, any->as.file.fptr->writeconv_pre_ecopts); gc_mark(objspace, any->as.file.fptr->encs.ecopts); gc_mark(objspace, any->as.file.fptr->write_lock); + gc_mark(objspace, any->as.file.fptr->timeout); } break; @@ -7372,7 +7443,7 @@ gc_mark_stacked_objects(rb_objspace_t *objspace, int incremental, size_t count) #endif while (pop_mark_stack(mstack, &obj)) { - if (obj == Qundef) continue; /* skip */ + if (UNDEF_P(obj)) continue; /* skip */ if (RGENGC_CHECK_MODE && !RVALUE_MARKED(obj)) { rb_bug("gc_mark_stacked_objects: %s is not marked.", obj_info(obj)); @@ -8389,42 +8460,64 @@ static rb_size_pool_t * gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, VALUE src) { size_t obj_size; + 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: - obj_size = rb_obj_embedded_size(ROBJECT_NUMIV(src)); - break; + 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; - 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)){ - return &size_pools[size_pool_idx_for_size(obj_size)]; - } - else { - return &size_pools[0]; + idx = size_pool_idx_for_size(obj_size); } + return &size_pools[idx]; } 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); - rb_heap_t *dheap = SIZE_POOL_EDEN_HEAP(gc_compact_destination_pool(objspace, size_pool, src)); + GC_ASSERT(gc_is_moveable_obj(objspace, 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, @@ -8446,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; } @@ -8466,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; @@ -8922,8 +9026,10 @@ rb_gc_writebarrier(VALUE a, VALUE b) { rb_objspace_t *objspace = &rb_objspace; - if (RGENGC_CHECK_MODE && SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const"); - if (RGENGC_CHECK_MODE && SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const"); + if (RGENGC_CHECK_MODE) { + if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a); + if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b); + } retry: if (!is_incremental_marking(objspace)) { @@ -9175,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 @@ -9816,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) { @@ -9844,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; @@ -9857,17 +9988,25 @@ 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 */ memcpy(dest, src, MIN(src_slot_size, slot_size)); + + if (RVALUE_OVERHEAD > 0) { + void *dest_overhead = (void *)(((uintptr_t)dest) + slot_size - RVALUE_OVERHEAD); + void *src_overhead = (void *)(((uintptr_t)src) + src_slot_size - RVALUE_OVERHEAD); + + memcpy(dest_overhead, src_overhead, RVALUE_OVERHEAD); + } + memset(src, 0, src_slot_size); /* Set bits for object in new location */ @@ -9990,7 +10129,7 @@ gc_ref_update_array(rb_objspace_t * objspace, VALUE v) } #if USE_RVARGC - if ((size_t)GET_HEAP_PAGE(v)->slot_size >= rb_ary_size_as_embedded(v)) { + if (rb_gc_obj_slot_size(v) >= rb_ary_size_as_embedded(v)) { if (rb_ary_embeddable_p(v)) { rb_ary_make_embedded(v); } @@ -10003,14 +10142,18 @@ static void gc_ref_update_object(rb_objspace_t *objspace, VALUE v) { VALUE *ptr = ROBJECT_IVPTR(v); - uint32_t numiv = ROBJECT_NUMIV(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); @@ -10019,18 +10162,10 @@ gc_ref_update_object(rb_objspace_t *objspace, VALUE v) xfree(ptr); } ptr = ROBJECT(v)->as.ary; - - uint32_t capa = (uint32_t)((slot_size - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); - ROBJECT(v)->numiv = capa; - - // Fill end with Qundef - for (uint32_t i = numiv; i < capa; i++) { - ptr[i] = Qundef; - } } #endif - for (uint32_t i = 0; i < numiv; i++) { + for (uint32_t i = 0; i < ROBJECT_IV_COUNT(v); i++) { UPDATE_IF_MOVED(objspace, ptr[i]); } } @@ -10357,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; @@ -10375,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; @@ -10407,15 +10569,6 @@ update_subclass_entries(rb_objspace_t *objspace, rb_subclass_entry_t *entry) } } -static int -update_iv_index_tbl_i(st_data_t key, st_data_t value, st_data_t arg) -{ - rb_objspace_t *objspace = (rb_objspace_t *)arg; - struct rb_iv_index_tbl_entry *ent = (struct rb_iv_index_tbl_entry *)value; - UPDATE_IF_MOVED(objspace, ent->class_value); - return ST_CONTINUE; -} - static void update_class_ext(rb_objspace_t *objspace, rb_classext_t *ext) { @@ -10423,11 +10576,6 @@ update_class_ext(rb_objspace_t *objspace, rb_classext_t *ext) UPDATE_IF_MOVED(objspace, ext->includer); UPDATE_IF_MOVED(objspace, ext->refined_class); update_subclass_entries(objspace, ext->subclasses); - - // ext->iv_index_tbl - if (ext->iv_index_tbl) { - st_foreach(ext->iv_index_tbl, update_iv_index_tbl_i, (st_data_t)objspace); - } } static void @@ -10459,7 +10607,9 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) update_cvc_tbl(objspace, obj); update_superclasses(objspace, obj); - gc_update_tbl_refs(objspace, RCLASS_IV_TBL(obj)); + for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { + UPDATE_IF_MOVED(objspace, RCLASS_IVPTR(obj)[i]); + } update_class_ext(objspace, RCLASS_EXT(obj)); update_const_tbl(objspace, RCLASS_CONST_TBL(obj)); @@ -10474,9 +10624,6 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) UPDATE_IF_MOVED(objspace, RCLASS(obj)->super); } if (!RCLASS_EXT(obj)) break; - if (RCLASS_IV_TBL(obj)) { - gc_update_tbl_refs(objspace, RCLASS_IV_TBL(obj)); - } update_class_ext(objspace, RCLASS_EXT(obj)); update_m_tbl(objspace, RCLASS_CALLABLE_M_TBL(obj)); update_cc_tbl(objspace, obj); @@ -10516,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 ((size_t)GET_HEAP_PAGE(obj)->slot_size >= 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; } @@ -10701,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 @@ -10807,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) */ @@ -10934,7 +11083,7 @@ gc_count(rb_execution_context_t *ec, VALUE self) static VALUE gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned int orig_flags) { - static VALUE sym_major_by = Qnil, sym_gc_by, sym_immediate_sweep, sym_have_finalizer, sym_state; + static VALUE sym_major_by = Qnil, sym_gc_by, sym_immediate_sweep, sym_have_finalizer, sym_state, sym_need_major_by; static VALUE sym_nofree, sym_oldgen, sym_shady, sym_force, sym_stress; #if RGENGC_ESTIMATE_OLDMALLOC static VALUE sym_oldmalloc; @@ -10942,7 +11091,7 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned static VALUE sym_newobj, sym_malloc, sym_method, sym_capi; static VALUE sym_none, sym_marking, sym_sweeping; VALUE hash = Qnil, key = Qnil; - VALUE major_by; + VALUE major_by, need_major_by; unsigned int flags = orig_flags ? orig_flags : objspace->profile.latest_gc_info; if (SYMBOL_P(hash_or_key)) { @@ -10962,6 +11111,7 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned S(immediate_sweep); S(have_finalizer); S(state); + S(need_major_by); S(stress); S(nofree); @@ -10999,6 +11149,20 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned Qnil; SET(major_by, major_by); + if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */ + unsigned int need_major_flags = objspace->rgengc.need_major_gc; + need_major_by = + (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree : + (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen : + (need_major_flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady : + (need_major_flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force : +#if RGENGC_ESTIMATE_OLDMALLOC + (need_major_flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc : +#endif + Qnil; + SET(need_major_by, need_major_by); + } + SET(gc_by, (flags & GPR_FLAG_NEWOBJ) ? sym_newobj : (flags & GPR_FLAG_MALLOC) ? sym_malloc : @@ -11609,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); } /* @@ -11682,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); @@ -12098,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) { @@ -12156,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); @@ -12179,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); @@ -12236,7 +12425,7 @@ objspace_xrealloc(rb_objspace_t *objspace, void *ptr, size_t new_size, size_t ol #endif old_size = objspace_malloc_size(objspace, ptr, old_size); - TRY_WITH_GC(new_size, mem = realloc(ptr, new_size)); + TRY_WITH_GC(new_size, mem = RB_GNUC_EXTENSION_BLOCK(realloc(ptr, new_size))); new_size = objspace_malloc_size(objspace, mem, new_size); #if CALC_EXACT_MALLOC_SIZE @@ -12416,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); @@ -12470,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); + } } } @@ -12665,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); } @@ -12699,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 @@ -12767,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; } @@ -12799,7 +13054,7 @@ wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); /* Get reference from object id. */ - if ((obj = id2ref_obj_tbl(&rb_objspace, objid)) == Qundef) { + if (UNDEF_P(obj = id2ref_obj_tbl(&rb_objspace, objid))) { rb_bug("wmap_finalize: objid is not found."); } @@ -12818,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; } @@ -13034,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); } @@ -13049,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) @@ -13063,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); } @@ -13090,14 +13387,14 @@ static VALUE wmap_aref(VALUE self, VALUE key) { VALUE obj = wmap_lookup(self, key); - return obj != Qundef ? obj : Qnil; + return !UNDEF_P(obj) ? obj : Qnil; } /* Returns +true+ if +key+ is registered */ static VALUE wmap_has_key(VALUE self, VALUE key) { - return RBOOL(wmap_lookup(self, key) != Qundef); + return RBOOL(!UNDEF_P(wmap_lookup(self, key))); } /* Returns the number of referenced objects */ @@ -13364,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. * */ @@ -13678,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 @@ -13692,7 +13989,7 @@ gc_profile_enable_get(VALUE self) * call-seq: * GC::Profiler.enable -> nil * - * Starts the GC profiler. + * Starts the \GC profiler. * */ @@ -13709,7 +14006,7 @@ gc_profile_enable(VALUE _) * call-seq: * GC::Profiler.disable -> nil * - * Stops the GC profiler. + * Stops the \GC profiler. * */ @@ -13972,7 +14269,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU } case T_OBJECT: { - uint32_t len = ROBJECT_NUMIV(obj); + uint32_t len = ROBJECT_IV_CAPACITY(obj); if (RANY(obj)->as.basic.flags & ROBJECT_EMBED) { APPEND_F("(embed) len:%d", len); @@ -14014,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", @@ -14022,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))); @@ -14313,6 +14610,22 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self) */ #include "gc.rbinc" +/* + * call-seq: + * GC.using_rvargc? -> true or false + * + * Returns true if using experimental feature Variable Width Allocation, false + * otherwise. + */ +static VALUE +gc_using_rvargc_p(VALUE mod) +{ +#if USE_RVARGC + return Qtrue; +#else + return Qfalse; +#endif +} void Init_GC(void) @@ -14326,7 +14639,8 @@ Init_GC(void) gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), RBOOL(GC_DEBUG)); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE - RVALUE_OVERHEAD)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(RVALUE))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_OBJ_LIMIT")), SIZET2NUM(HEAP_PAGE_OBJ_LIMIT)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE)); @@ -14391,6 +14705,8 @@ Init_GC(void) rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); #endif + rb_define_singleton_method(rb_mGC, "using_rvargc?", gc_using_rvargc_p, 0); + if (GC_COMPACTION_SUPPORTED) { rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0); rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0); @@ -14413,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); @@ -6,10 +6,12 @@ #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("movq\t%%rsp, %0" : "=r" (*(p))) #elif defined(__i386) && defined(__GNUC__) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("movl\t%%esp, %0" : "=r" (*(p))) -#elif (defined(__powerpc__) || defined(__powerpc64__)) && defined(__GNUC__) && !defined(_AIX) +#elif (defined(__powerpc__) || defined(__powerpc64__)) && defined(__GNUC__) && !defined(_AIX) && !defined(__APPLE__) // Not Apple is NEEDED to unbreak ppc64 build on Darwin. Don't ask. #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("mr\t%0, %%r1" : "=r" (*(p))) #elif (defined(__powerpc__) || defined(__powerpc64__)) && defined(__GNUC__) && defined(_AIX) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("mr %0,1" : "=r" (*(p))) +#elif defined(__POWERPC__) && defined(__APPLE__) // Darwin ppc and ppc64 +#define SET_MACHINE_STACK_END(p) __asm__ volatile("mr %0, r1" : "=r" (*(p))) #elif defined(__aarch64__) && defined(__GNUC__) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("mov\t%0, sp" : "=r" (*(p))) #else @@ -114,10 +116,10 @@ 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); + RUBY_SYMBOL_EXPORT_BEGIN /* exports for objspace module */ @@ -138,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) @@ -253,21 +253,11 @@ module GC end # call-seq: - # GC.using_rvargc? -> true or false - # - # Returns true if using experimental feature Variable Width Allocation, false - # otherwise. - def self.using_rvargc? # :nodoc: - GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] > 1 - end - - - # 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; @@ -289,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 f9cf631765..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.1 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.3 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.1.3 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.2 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.6.0 https://github.com/ruby/rbs 5ec9d53efe4bf0a97f33c3016aed430be135583a +rbs 2.8.2 https://github.com/ruby/rbs typeprof 0.21.3 https://github.com/ruby/typeprof -debug 1.6.2 https://github.com/ruby/debug 19b4dde3308f532943e4234d1588d4fa26c52345 +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) { @@ -111,7 +114,7 @@ rb_any_cmp(VALUE a, VALUE b) RB_TYPE_P(b, T_STRING) && RBASIC(b)->klass == rb_cString) { return rb_str_hash_cmp(a, b); } - if (a == Qundef || b == Qundef) return -1; + if (UNDEF_P(a) || UNDEF_P(b)) return -1; if (SYMBOL_P(a) && SYMBOL_P(b)) { return a != b; } @@ -195,7 +198,7 @@ obj_any_hash(VALUE obj) { VALUE hval = rb_check_funcall_basic_kw(obj, id_hash, rb_mKernel, 0, 0, 0); - if (hval == Qundef) { + if (UNDEF_P(hval)) { hval = rb_exec_recursive_outer_mid(hash_recursive, obj, 0, id_hash); } @@ -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; /* @@ -437,7 +443,7 @@ ar_cleared_entry(VALUE hash, unsigned int index) * so you need to check key == Qundef */ ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, index); - return pair->key == Qundef; + return UNDEF_P(pair->key); } else { return FALSE; @@ -517,8 +523,8 @@ hash_verify_(VALUE hash, const char *file, int line) ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i); k = pair->key; v = pair->val; - HASH_ASSERT(k != Qundef); - HASH_ASSERT(v != Qundef); + HASH_ASSERT(!UNDEF_P(k)); + HASH_ASSERT(!UNDEF_P(v)); n++; } } @@ -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 { @@ -963,7 +987,7 @@ ar_foreach(VALUE hash, st_foreach_callback_func *func, st_data_t arg) static int ar_foreach_check(VALUE hash, st_foreach_check_callback_func *func, st_data_t arg, - st_data_t never) + st_data_t never) { if (RHASH_AR_TABLE_SIZE(hash) > 0) { unsigned i, ret = 0, bound = RHASH_AR_TABLE_BOUND(hash); @@ -984,13 +1008,13 @@ ar_foreach_check(VALUE hash, st_foreach_check_callback_func *func, st_data_t arg switch (retval) { case ST_CHECK: { - pair = RHASH_AR_TABLE_REF(hash, i); - if (pair->key == never) break; - ret = ar_find_entry_hint(hash, hint, key); - if (ret == RHASH_AR_TABLE_MAX_BOUND) { - retval = (*func)(0, 0, arg, 1); - return 2; - } + pair = RHASH_AR_TABLE_REF(hash, i); + if (pair->key == never) break; + ret = ar_find_entry_hint(hash, hint, key); + if (ret == RHASH_AR_TABLE_MAX_BOUND) { + retval = (*func)(0, 0, arg, 1); + return 2; + } } case ST_CONTINUE: break; @@ -998,11 +1022,11 @@ ar_foreach_check(VALUE hash, st_foreach_check_callback_func *func, st_data_t arg case ST_REPLACE: return 0; case ST_DELETE: { - if (!ar_cleared_entry(hash, i)) { - ar_clear_entry(hash, i); - RHASH_AR_TABLE_SIZE_DEC(hash); - } - break; + if (!ar_cleared_entry(hash, i)) { + ar_clear_entry(hash, i); + RHASH_AR_TABLE_SIZE_DEC(hash); + } + break; } } } @@ -1346,15 +1370,8 @@ struct hash_foreach_arg { }; static int -hash_ar_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error) +hash_iter_status_check(int status) { - struct hash_foreach_arg *arg = (struct hash_foreach_arg *)argp; - int status; - - if (error) return ST_STOP; - status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); - /* TODO: rehash check? rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); */ - switch (status) { case ST_DELETE: return ST_DELETE; @@ -1363,31 +1380,38 @@ hash_ar_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error) case ST_STOP: return ST_STOP; } + return ST_CHECK; } static int +hash_ar_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error) +{ + struct hash_foreach_arg *arg = (struct hash_foreach_arg *)argp; + + if (error) return ST_STOP; + + int status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); + /* TODO: rehash check? rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); */ + + return hash_iter_status_check(status); +} + +static int hash_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error) { struct hash_foreach_arg *arg = (struct hash_foreach_arg *)argp; - int status; - st_table *tbl; if (error) return ST_STOP; - tbl = RHASH_ST_TABLE(arg->hash); - status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); + + st_table *tbl = RHASH_ST_TABLE(arg->hash); + int status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); + if (RHASH_ST_TABLE(arg->hash) != tbl) { - rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); + rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); } - switch (status) { - case ST_DELETE: - return ST_DELETE; - case ST_CONTINUE: - break; - case ST_STOP: - return ST_STOP; - } - return ST_CHECK; + + return hash_iter_status_check(status); } static int @@ -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,13 +2110,27 @@ 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 (key == Qundef) return Qnil; + if (LIKELY(!FL_TEST_RAW(hash, RHASH_PROC_DEFAULT))) return ifnone; + if (UNDEF_P(key)) return Qnil; return call_default_proc(ifnone, hash, key); } else { @@ -2402,7 +2456,7 @@ rb_hash_delete(VALUE hash, VALUE key) { VALUE deleted_value = rb_hash_delete_entry(hash, key); - if (deleted_value != Qundef) { /* likely pass */ + if (!UNDEF_P(deleted_value)) { /* likely pass */ return deleted_value; } else { @@ -2445,7 +2499,8 @@ rb_hash_delete_m(VALUE hash, VALUE key) rb_hash_modify_check(hash); val = rb_hash_delete_entry(hash, key); - if (val != Qundef) { + if (!UNDEF_P(val)) { + compact_after_delete(hash); return val; } else { @@ -2502,7 +2557,7 @@ rb_hash_shift(VALUE hash) } else { rb_hash_foreach(hash, shift_i_safe, (VALUE)&var); - if (var.key != Qundef) { + if (!UNDEF_P(var.key)) { rb_hash_delete_entry(hash, var.key); return rb_assoc_new(var.key, var.val); } @@ -2517,7 +2572,7 @@ rb_hash_shift(VALUE hash) } else { rb_hash_foreach(hash, shift_i_safe, (VALUE)&var); - if (var.key != Qundef) { + if (!UNDEF_P(var.key)) { rb_hash_delete_entry(hash, var.key); return rb_assoc_new(var.key, var.val); } @@ -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; } @@ -2658,7 +2715,7 @@ rb_hash_slice(int argc, VALUE *argv, VALUE hash) for (i = 0; i < argc; i++) { key = argv[i]; value = rb_hash_lookup2(hash, key, Qundef); - if (value != Qundef) + if (!UNDEF_P(value)) rb_hash_aset(result, key, value); } @@ -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 { @@ -3181,7 +3241,7 @@ transform_keys_hash_i(VALUE key, VALUE value, VALUE transarg) struct transform_keys_args *p = (void *)transarg; VALUE trans = p->trans, result = p->result; VALUE new_key = rb_hash_lookup2(trans, key, Qundef); - if (new_key == Qundef) { + if (UNDEF_P(new_key)) { if (p->block_given) new_key = rb_yield(key); else @@ -3302,7 +3362,7 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash) if (!trans) { new_key = rb_yield(key); } - else if ((new_key = rb_hash_lookup2(trans, key, Qundef)) != Qundef) { + else if (!UNDEF_P(new_key = rb_hash_lookup2(trans, key, Qundef))) { /* use the transformed key */ } else if (block_given) { @@ -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; @@ -4195,7 +4251,7 @@ rb_hash_assoc(VALUE hash, VALUE key) ensure_arg.hash = hash; ensure_arg.orighash = orighash; value = rb_ensure(lookup2_call, (VALUE)&args, reset_hash_type, (VALUE)&ensure_arg); - if (value != Qundef) return rb_assoc_new(key, value); + if (!UNDEF_P(value)) return rb_assoc_new(key, value); } args[0] = key; @@ -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 @@ -4608,7 +4664,7 @@ hash_le_i(VALUE key, VALUE value, VALUE arg) { VALUE *args = (VALUE *)arg; VALUE v = rb_hash_lookup2(args[0], key, Qundef); - if (v != Qundef && rb_equal(value, v)) return ST_CONTINUE; + if (!UNDEF_P(v) && rb_equal(value, v)) return ST_CONTINUE; args[1] = Qfalse; return ST_STOP; } @@ -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(); @@ -206,6 +206,7 @@ double2hrtime(rb_hrtime_t *hrt, double d) const double TIMESPEC_SEC_MAX_PLUS_ONE = 2.0 * (TIMESPEC_SEC_MAX_as_double / 2.0 + 1.0); if (TIMESPEC_SEC_MAX_PLUS_ONE <= d) { + *hrt = RB_HRTIME_MAX; return NULL; } else if (d <= 0) { 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/debug.h b/include/ruby/debug.h index c88da9c43d..f95acdb17e 100644 --- a/include/ruby/debug.h +++ b/include/ruby/debug.h @@ -207,6 +207,17 @@ typedef VALUE (*rb_debug_inspector_func_t)(const rb_debug_inspector_t *dc, void VALUE rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data); /** + * Queries the backtrace object of the context. This is as if you call + * `caller_locations` at the point of debugger. + * + * @param[in] dc A debug context. + * @return An array of `Thread::Backtrace::Location` which represents the + * current point of execution at `dc`. + + */ +VALUE rb_debug_inspector_backtrace_locations(const rb_debug_inspector_t *dc); + +/** * Queries the current receiver of the passed context's upper frame. * * @param[in] dc A debug context. @@ -250,15 +261,27 @@ VALUE rb_debug_inspector_frame_binding_get(const rb_debug_inspector_t *dc, long VALUE rb_debug_inspector_frame_iseq_get(const rb_debug_inspector_t *dc, long index); /** - * Queries the backtrace object of the context. This is as if you call - * `caller_locations` at the point of debugger. + * Queries the depth of the passed context's upper frame. * - * @param[in] dc A debug context. - * @return An array of `Thread::Backtrace::Location` which represents the - * current point of execution at `dc`. + * Note that the depth is not same as the frame index because debug_inspector + * skips some special frames but the depth counts all frames. + * + * @param[in] dc A debug context. + * @param[in] index Index of the frame from top to bottom. + * @exception rb_eArgError `index` out of range. + * @retval The depth at `index`-th frame in Integer. + */ +VALUE rb_debug_inspector_frame_depth(const rb_debug_inspector_t *dc, long index); + +// A macro to recognize `rb_debug_inspector_frame_depth()` is available or not +#define RB_DEBUG_INSPECTOR_FRAME_DEPTH(dc, index) rb_debug_inspector_frame_depth(dc, index) +/** + * Return current frmae depth. + * + * @retval The depth of the current frame in Integer. */ -VALUE rb_debug_inspector_backtrace_locations(const rb_debug_inspector_t *dc); +VALUE rb_debug_inspector_current_depth(void); /** @} */ diff --git a/include/ruby/fiber/scheduler.h b/include/ruby/fiber/scheduler.h index d38651da5c..250b39b6df 100644 --- a/include/ruby/fiber/scheduler.h +++ b/include/ruby/fiber/scheduler.h @@ -23,6 +23,8 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() +#define RUBY_FIBER_SCHEDULER_VERSION 2 + struct timeval; /** @@ -144,7 +146,7 @@ VALUE rb_fiber_scheduler_make_timeout(struct timeval *timeout); VALUE rb_fiber_scheduler_close(VALUE scheduler); /** - * Nonblocking `sleep`. Depending on scheduler implementation, this for + * Non-blocking `sleep`. Depending on scheduler implementation, this for * instance switches to another fiber etc. * * @param[in] scheduler Target scheduler. @@ -172,7 +174,7 @@ int rb_fiber_scheduler_supports_process_wait(VALUE scheduler); #endif /** - * Nonblocking `waitpid`. Depending on scheduler implementation, this for + * Non-blocking `waitpid`. Depending on scheduler implementation, this for * instance switches to another fiber etc. * * @param[in] scheduler Target scheduler. @@ -183,7 +185,7 @@ int rb_fiber_scheduler_supports_process_wait(VALUE scheduler); VALUE rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags); /** - * Nonblocking wait for the passed "blocker", which is for instance + * Non-blocking wait for the passed "blocker", which is for instance * `Thread.join` or `Mutex.lock`. Depending on scheduler implementation, this * for instance switches to another fiber etc. * @@ -205,8 +207,8 @@ VALUE rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout); VALUE rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber); /** - * Nonblocking version of rb_io_wait(). Depending on scheduler implementation, - * this for instance switches to another fiber etc. + * Non-blocking version of rb_io_wait(). Depending on scheduler + * implementation, this for instance switches to another fiber etc. * * The "events" here is a Ruby level integer, which is an OR-ed value of * `IO::READABLE`, `IO::WRITABLE`, and `IO::PRIORITY`. @@ -220,7 +222,7 @@ VALUE rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber); VALUE rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout); /** - * Nonblocking wait until the passed IO is ready for reading. This is a + * Non-blocking wait until the passed IO is ready for reading. This is a * special case of rb_fiber_scheduler_io_wait(), where the interest is * `IO::READABLE` and timeout is never. * @@ -231,7 +233,7 @@ VALUE rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE VALUE rb_fiber_scheduler_io_wait_readable(VALUE scheduler, VALUE io); /** - * Nonblocking wait until the passed IO is ready for writing. This is a + * Non-blocking wait until the passed IO is ready for writing. This is a * special case of rb_fiber_scheduler_io_wait(), where the interest is * `IO::WRITABLE` and timeout is never. * @@ -242,57 +244,81 @@ VALUE rb_fiber_scheduler_io_wait_readable(VALUE scheduler, VALUE io); VALUE rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io); /** - * Nonblocking read from the passed IO. + * Non-blocking version of `IO.select`. + * + * It's possible that this will be emulated using a thread, so you should not + * rely on it for high performance. + * + * @param[in] scheduler Target scheduler. + * @param[in] readables An array of readable objects. + * @param[in] writables An array of writable objects. + * @param[in] exceptables An array of objects that might encounter exceptional conditions. + * @param[in] timeout Numeric timeout or nil. + * @return What `scheduler.io_select` returns, normally a 3-tuple of arrays of ready objects. + */ +VALUE rb_fiber_scheduler_io_select(VALUE scheduler, VALUE readables, VALUE writables, VALUE exceptables, VALUE timeout); + +/** + * Non-blocking version of `IO.select`, `argv` variant. + */ +VALUE rb_fiber_scheduler_io_selectv(VALUE scheduler, int argc, VALUE *argv); + +/** + * Non-blocking read from the passed IO. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to read from. * @param[out] buffer Return buffer. * @param[in] length Requested number of bytes to read. + * @param[in] offset The offset in the buffer to read to. * @retval RUBY_Qundef `scheduler` doesn't have `#io_read`. * @return otherwise What `scheduler.io_read` returns `[-errno, size]`. */ -VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length); +VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset); /** - * Nonblocking write to the passed IO. + * Non-blocking write to the passed IO. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to write to. * @param[in] buffer What to write. * @param[in] length Number of bytes to write. + * @param[in] offset The offset in the buffer to write from. * @retval RUBY_Qundef `scheduler` doesn't have `#io_write`. * @return otherwise What `scheduler.io_write` returns `[-errno, size]`. */ -VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length); +VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset); /** - * Nonblocking read from the passed IO at the specified offset. + * Non-blocking read from the passed IO at the specified offset. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to read from. - * @param[out] buffer Return buffer. + * @param[in] from The offset in the given IO to read the data from. + * @param[out] buffer The buffer to read the data to. * @param[in] length Requested number of bytes to read. - * @param[in] offset The offset in the given IO to read the data from. + * @param[in] offset The offset in the buffer to read to. * @retval RUBY_Qundef `scheduler` doesn't have `#io_read`. * @return otherwise What `scheduler.io_read` returns. */ -VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, VALUE buffer, size_t length, rb_off_t offset); +VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset); /** - * Nonblocking write to the passed IO at the specified offset. + * Non-blocking write to the passed IO at the specified offset. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to write to. - * @param[in] buffer What to write. + * @param[in] from The offset in the given IO to write the data to. + * @param[in] buffer The buffer to write the data from. * @param[in] length Number of bytes to write. - * @param[in] offset The offset in the given IO to write the data to. + * @param[in] offset The offset in the buffer to write from. * @retval RUBY_Qundef `scheduler` doesn't have `#io_write`. * @return otherwise What `scheduler.io_write` returns. */ -VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, VALUE buffer, size_t length, rb_off_t offset); +VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset); /** - * Nonblocking read from the passed IO using a native buffer. + * Non-blocking read from the passed IO using a native buffer. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to read from. @@ -305,7 +331,7 @@ VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, VALUE buffer, size VALUE rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *buffer, size_t size, size_t length); /** - * Nonblocking write to the passed IO using a native buffer. + * Non-blocking write to the passed IO using a native buffer. * * @param[in] scheduler Target scheduler. * @param[out] io An io object to write to. @@ -318,7 +344,7 @@ VALUE rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *buffer, VALUE rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *buffer, size_t size, size_t length); /** - * Nonblocking close the given IO. + * Non-blocking close the given IO. * * @param[in] scheduler Target scheduler. * @param[in] io An io object to close. @@ -328,7 +354,7 @@ VALUE rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void * VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io); /** - * Nonblocking DNS lookup. + * Non-blocking DNS lookup. * * @param[in] scheduler Target scheduler. * @param[in] hostname A host name to query. @@ -337,6 +363,12 @@ VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io); */ VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname); +/** + * Create and schedule a non-blocking fiber. + * + */ +VALUE rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat); + RBIMPL_SYMBOL_EXPORT_END() #endif /* RUBY_FIBER_SCHEDULER_H */ diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index fe1977a9a1..44111a0055 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,13 +24,14 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 2 +#define RUBY_ABI_VERSION 3 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ #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/config.h b/include/ruby/internal/config.h index 0c434e5b05..aa63376d7c 100644 --- a/include/ruby/internal/config.h +++ b/include/ruby/internal/config.h @@ -113,6 +113,8 @@ # define UNALIGNED_WORD_ACCESS 1 #elif defined(__powerpc64__) # define UNALIGNED_WORD_ACCESS 1 +#elif defined(__POWERPC__) // __POWERPC__ is defined for ppc and ppc64 on Darwin +# define UNALIGNED_WORD_ACCESS 1 #elif defined(__aarch64__) # define UNALIGNED_WORD_ACCESS 1 #elif defined(__mc68020__) diff --git a/include/ruby/internal/core/robject.h b/include/ruby/internal/core/robject.h index 7823061d8f..b1c2e1b0a9 100644 --- a/include/ruby/internal/core/robject.h +++ b/include/ruby/internal/core/robject.h @@ -37,16 +37,15 @@ /** * 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 */ #define ROBJECT_EMBED_LEN_MAX ROBJECT_EMBED_LEN_MAX #define ROBJECT_EMBED ROBJECT_EMBED -#define ROBJECT_NUMIV ROBJECT_NUMIV +#define ROBJECT_IV_CAPACITY ROBJECT_IV_CAPACITY #define ROBJECT_IVPTR ROBJECT_IVPTR -#define ROBJECT_IV_INDEX_TBL ROBJECT_IV_INDEX_TBL /** @endcond */ /** @@ -97,14 +96,6 @@ struct RObject { /** Basic part, including flags and class. */ struct RBasic basic; -#if USE_RVARGC - /** - * Number of instance variables. This is per object; objects might - * differ in this field even if they have the identical classes. - */ - uint32_t numiv; -#endif - /** Object's specific fields. */ union { @@ -113,14 +104,6 @@ struct RObject { * this pattern. */ struct { -#if !USE_RVARGC - /** - * Number of instance variables. This is per object; objects might - * differ in this field even if they have the identical classes. - */ - uint32_t numiv; -#endif - /** Pointer to a C array that holds instance variables. */ VALUE *ivptr; @@ -132,7 +115,7 @@ struct RObject { * * This is a shortcut for `RCLASS_IV_INDEX_TBL(rb_obj_class(obj))`. */ - struct st_table *iv_index_tbl; + struct rb_id_table *iv_index_tbl; } heap; #if USE_RVARGC @@ -157,11 +140,6 @@ struct RObject { /* Offsets for YJIT */ #ifndef __cplusplus -# if USE_RVARGC -static const int32_t ROBJECT_OFFSET_NUMIV = offsetof(struct RObject, numiv); -# else -static const int32_t ROBJECT_OFFSET_NUMIV = offsetof(struct RObject, as.heap.numiv); -# endif static const int32_t ROBJECT_OFFSET_AS_HEAP_IVPTR = offsetof(struct RObject, as.heap.ivptr); static const int32_t ROBJECT_OFFSET_AS_HEAP_IV_INDEX_TBL = offsetof(struct RObject, as.heap.iv_index_tbl); static const int32_t ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary); @@ -170,32 +148,6 @@ static const int32_t ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary); RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** - * Queries the number of instance variables. - * - * @param[in] obj Object in question. - * @return Its number of instance variables. - * @pre `obj` must be an instance of ::RObject. - */ -static inline uint32_t -ROBJECT_NUMIV(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - -#if USE_RVARGC - return ROBJECT(obj)->numiv; -#else - if (RB_FL_ANY_RAW(obj, ROBJECT_EMBED)) { - return ROBJECT_EMBED_LEN_MAX; - } - else { - return ROBJECT(obj)->as.heap.numiv; - } -#endif -} - -RBIMPL_ATTR_PURE_UNLESS_DEBUG() -RBIMPL_ATTR_ARTIFICIAL() -/** * Queries the instance variables. * * @param[in] obj Object in question. diff --git a/include/ruby/internal/eval.h b/include/ruby/internal/eval.h index 34a53849da..5bcbb97746 100644 --- a/include/ruby/internal/eval.h +++ b/include/ruby/internal/eval.h @@ -28,10 +28,12 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NONNULL(()) /** - * Evaluates the given string in an isolated binding. + * Evaluates the given string. * - * Here "isolated" means that the binding does not inherit any other - * bindings. This behaves same as the binding for required libraries. + * In case it is called from within a C-backended method, the evaluation is + * done under the current binding. However there can be no method. On such + * situation this function evaluates in an isolated binding, like `require` + * runs in a separate one. * * `__FILE__` will be `"(eval)"`, and `__LINE__` starts from 1 in the * evaluation. @@ -39,6 +41,31 @@ RBIMPL_ATTR_NONNULL(()) * @param[in] str Ruby code to evaluate. * @exception rb_eException Raises an exception on error. * @return The evaluated result. + * + * @internal + * + * @shyouhei's old tale about the birth and growth of this function: + * + * At the beginning, there was no rb_eval_string(). @shyouhei heard that + * @shugo, author of Apache httpd's mod_ruby module, requested @matz for this + * API. He wanted a way so that mod_ruby can evaluate ruby scripts one by one, + * separately, in each different contexts. So this function was made. It was + * designed to be a global interpreter entry point like ruby_run_node(). + * + * The way it is implemented however allows extension libraries (not just + * programs like Apache httpd) to call this function. Because its name says + * nothing about the initial design, people started to think of it as an + * orthodox way to call ruby level `eval` method from their extension + * libraries. Even our `extension.rdoc` has had a description of this function + * basically according to this understanding. + * + * The old (mod_ruby like) usage still works. But over time, usages of this + * function from extension libraries got popular, while mod_ruby faded out; is + * no longer maintained now. Devs decided to actively support both. This + * function now auto-detects how it is called, and switches how it works + * depending on it. + * + * @see https://bugs.ruby-lang.org/issues/18780 */ VALUE rb_eval_string(const char *str); diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index c51bd2e9d9..7383426b23 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -941,21 +941,8 @@ RB_OBJ_FREEZE_RAW(VALUE obj) RB_FL_SET_RAW(obj, RUBY_FL_FREEZE); } -/** - * Prevents further modifications to the given object. ::rb_eFrozenError shall - * be raised if modification is attempted. - * - * @param[out] x Object in question. - */ -static inline void -rb_obj_freeze_inline(VALUE x) -{ - if (RB_FL_ABLE(x)) { - RB_OBJ_FREEZE_RAW(x); - if (RBASIC_CLASS(x) && !(RBASIC(x)->flags & RUBY_FL_SINGLETON)) { - rb_freeze_singleton_class(x); - } - } -} +RUBY_SYMBOL_EXPORT_BEGIN +void rb_obj_freeze_inline(VALUE obj); +RUBY_SYMBOL_EXPORT_END #endif /* RBIMPL_FL_TYPE_H */ 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/class.h b/include/ruby/internal/intern/class.h index 2181ab93c7..0fb2d001bc 100644 --- a/include/ruby/internal/intern/class.h +++ b/include/ruby/internal/intern/class.h @@ -200,6 +200,18 @@ VALUE rb_class_descendants(VALUE klass); */ VALUE rb_class_subclasses(VALUE klass); + +/** + * Returns the attached object for a singleton class. + * If the given class is not a singleton class, raises a TypeError. + * + * @param[in] klass A class. + * @return The object which has the singleton class `klass`. + * + * @internal + */ +VALUE rb_class_attached_object(VALUE klass); + /** * Generates an array of symbols, which are the list of method names defined in * the passed class. diff --git a/include/ruby/internal/intern/cont.h b/include/ruby/internal/intern/cont.h index 37493009f5..32647f48aa 100644 --- a/include/ruby/internal/intern/cont.h +++ b/include/ruby/internal/intern/cont.h @@ -39,6 +39,28 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() VALUE rb_fiber_new(rb_block_call_func_t func, VALUE callback_obj); /** + * Creates a Fiber instance from a C-backended block with the specified + * storage. + * + * If the given storage is Qundef or Qtrue, this function is equivalent to + * rb_fiber_new() which inherits storage from the current fiber. + * + * 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). + * + * Otherwise, the given storage is used as the internal storage. + * + * @param[in] func A function, to become the fiber's body. + * @param[in] callback_obj Passed as-is to `func`. + * @param[in] storage The way to set up the storage for the fiber. + * @return An allocated new instance of rb_cFiber, which is ready to be + * "resume"d. + */ +VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE callback_obj, VALUE storage); + +/** * Queries the fiber which is calling this function. Any ruby execution * context has its fiber, either explicitly or implicitly. * diff --git a/include/ruby/internal/intern/gc.h b/include/ruby/internal/intern/gc.h index e7b8008729..2ee1d257db 100644 --- a/include/ruby/internal/intern/gc.h +++ b/include/ruby/internal/intern/gc.h @@ -26,7 +26,7 @@ # include <stddef.h> /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include <sys/types.h> /* ssize_t */ #endif @@ -71,7 +71,7 @@ RBIMPL_ATTR_NONNULL((1)) * addressable. * @param[out] start Pointer to an array of objects. * @param[out] end Pointer that terminates the array of objects. - * @post Objects from `start` to `end`, both inclusive, are marked. + * @post Objects from `start` (included) to `end` (excluded) are marked. * * @internal * diff --git a/include/ruby/internal/intern/object.h b/include/ruby/internal/intern/object.h index 19af49b140..b9ffa57c06 100644 --- a/include/ruby/internal/intern/object.h +++ b/include/ruby/internal/intern/object.h @@ -92,8 +92,8 @@ VALUE rb_class_new_instance_kw(int argc, const VALUE *argv, VALUE klass, int kw_ * * @param[in] lhs Comparison left hand side. * @param[in] rhs Comparison right hand side. - * @retval RUBY_Qtrue They are equal. - * @retval RUBY_Qfalse Otherwise. + * @retval non-zero They are equal. + * @retval 0 Otherwise. * @note This function actually calls `lhs.eql?(rhs)` so you cannot * implement your class' `#eql?` method using it. */ diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index 0f59262a91..6884db195d 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -287,12 +287,12 @@ typedef uint128_t DSIZE_T; RBIMPL_CAST((type *)alloca(rbimpl_size_mul_or_raise(sizeof(type), (n)))) /** - * Identical to #RB_ALLOCV_N(), except it implicitly assumes the type of array - * is ::VALUE. + * Identical to #RB_ALLOCV_N(), except that it allocates a number of bytes and + * returns a void* . * * @param v A variable to hold the just-in-case opaque Ruby object. * @param n Size of allocation, in bytes. - * @return An array of `n` bytes of ::VALUE. + * @return A void pointer to `n` bytes storage. * @note `n` may be evaluated twice. */ #define RB_ALLOCV(v, n) \ diff --git a/include/ruby/internal/special_consts.h b/include/ruby/internal/special_consts.h index 38934e4da3..dc0a6b41d6 100644 --- a/include/ruby/internal/special_consts.h +++ b/include/ruby/internal/special_consts.h @@ -76,6 +76,8 @@ #define RB_SPECIAL_CONST_P RB_SPECIAL_CONST_P #define RB_STATIC_SYM_P RB_STATIC_SYM_P #define RB_TEST RB_TEST +#define RB_UNDEF_P RB_UNDEF_P +#define RB_NIL_OR_UNDEF_P RB_NIL_OR_UNDEF_P /** @endcond */ /** special constants - i.e. non-zero and non-fixnum constants */ @@ -94,9 +96,9 @@ ruby_special_consts { RUBY_SYMBOL_FLAG, /**< Flag to denote a static symbol. */ #elif USE_FLONUM RUBY_Qfalse = 0x00, /* ...0000 0000 */ + RUBY_Qnil = 0x04, /* ...0000 0100 */ RUBY_Qtrue = 0x14, /* ...0001 0100 */ - RUBY_Qnil = 0x08, /* ...0000 1000 */ - RUBY_Qundef = 0x34, /* ...0011 0100 */ + RUBY_Qundef = 0x24, /* ...0010 0100 */ RUBY_IMMEDIATE_MASK = 0x07, /* ...0000 0111 */ RUBY_FIXNUM_FLAG = 0x01, /* ...xxxx xxx1 */ RUBY_FLONUM_MASK = 0x03, /* ...0000 0011 */ @@ -104,14 +106,14 @@ ruby_special_consts { RUBY_SYMBOL_FLAG = 0x0c, /* ...xxxx 1100 */ #else RUBY_Qfalse = 0x00, /* ...0000 0000 */ - RUBY_Qtrue = 0x02, /* ...0000 0010 */ - RUBY_Qnil = 0x04, /* ...0000 0100 */ - RUBY_Qundef = 0x06, /* ...0000 0110 */ + RUBY_Qnil = 0x02, /* ...0000 0010 */ + RUBY_Qtrue = 0x06, /* ...0000 0110 */ + RUBY_Qundef = 0x0a, /* ...0000 1010 */ RUBY_IMMEDIATE_MASK = 0x03, /* ...0000 0011 */ RUBY_FIXNUM_FLAG = 0x01, /* ...xxxx xxx1 */ RUBY_FLONUM_MASK = 0x00, /* any values ANDed with FLONUM_MASK cannot be FLONUM_FLAG */ RUBY_FLONUM_FLAG = 0x02, /* ...0000 0010 */ - RUBY_SYMBOL_FLAG = 0x0e, /* ...0000 1110 */ + RUBY_SYMBOL_FLAG = 0x0e, /* ...xxxx 1110 */ #endif RUBY_SPECIAL_SHIFT = 8 /**< Least significant 8 bits are reserved. */ @@ -136,12 +138,21 @@ static inline bool RB_TEST(VALUE obj) { /* + * if USE_FLONUM * Qfalse: ....0000 0000 - * Qnil: ....0000 1000 - * ~Qnil: ....1111 0111 + * Qnil: ....0000 0100 + * ~Qnil: ....1111 1011 * v ....xxxx xxxx * ---------------------------- - * RTEST(v) ....xxxx 0xxx + * RTEST(v) ....xxxx x0xx + * + * if ! USE_FLONUM + * Qfalse: ....0000 0000 + * Qnil: ....0000 0010 + * ~Qnil: ....1111 1101 + * v ....xxxx xxxx + * ---------------------------- + * RTEST(v) ....xxxx xx0x * * RTEST(v) can be 0 if and only if (v == Qfalse || v == Qnil). */ @@ -168,6 +179,62 @@ RBIMPL_ATTR_CONST() RBIMPL_ATTR_CONSTEXPR(CXX11) RBIMPL_ATTR_ARTIFICIAL() /** + * Checks if the given object is undef. + * + * @param[in] obj An arbitrary ruby object. + * @retval true `obj` is ::RUBY_Qundef. + * @retval false Anything else. + */ +static inline bool +RB_UNDEF_P(VALUE obj) +{ + return obj == RUBY_Qundef; +} + +RBIMPL_ATTR_CONST() +RBIMPL_ATTR_CONSTEXPR(CXX14) +RBIMPL_ATTR_ARTIFICIAL() +/** + * Checks if the given object is nil or undef. Can be used to see if + * a keyword argument is not given or given `nil`. + * + * @param[in] obj An arbitrary ruby object. + * @retval true `obj` is ::RUBY_Qnil or ::RUBY_Qundef. + * @retval false Anything else. + */ +static inline bool +RB_NIL_OR_UNDEF_P(VALUE obj) +{ + /* + * if USE_FLONUM + * Qundef: ....0010 0100 + * Qnil: ....0000 0100 + * mask: ....1101 1111 + * common_bits: ....0000 0100 + * --------------------------------- + * Qnil & mask ....0000 0100 + * Qundef & mask ....0000 0100 + * + * if ! USE_FLONUM + * Qundef: ....0000 1010 + * Qnil: ....0000 0010 + * mask: ....1111 0111 + * common_bits: ....0000 0010 + * ---------------------------- + * Qnil & mask ....0000 0010 + * Qundef & mask ....0000 0010 + * + * NIL_OR_UNDEF_P(v) can be true only when v is Qundef or Qnil. + */ + const VALUE mask = ~(RUBY_Qundef ^ RUBY_Qnil); + const VALUE common_bits = RUBY_Qundef & RUBY_Qnil; + return (obj & mask) == common_bits; +} + +RBIMPL_ATTR_CONST() +RBIMPL_ATTR_CONSTEXPR(CXX11) +RBIMPL_ATTR_ARTIFICIAL() +/** * Checks if the given object is a so-called Fixnum. * * @param[in] obj An arbitrary ruby object. @@ -259,7 +326,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool RB_SPECIAL_CONST_P(VALUE obj) { - return RB_IMMEDIATE_P(obj) || ! RB_TEST(obj); + return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse; } RBIMPL_ATTR_CONST() diff --git a/include/ruby/io.h b/include/ruby/io.h index dc4c8becf6..88029b1bb9 100644 --- a/include/ruby/io.h +++ b/include/ruby/io.h @@ -58,12 +58,20 @@ // IO#wait, IO#wait_readable, IO#wait_writable, IO#wait_priority are defined by this implementation. #define RUBY_IO_WAIT_METHODS +// Used as the default timeout argument to `rb_io_wait` to use the `IO#timeout` value. +#define RUBY_IO_TIMEOUT_DEFAULT Qnil + RBIMPL_SYMBOL_EXPORT_BEGIN() struct stat; struct timeval; /** + * Indicates that a timeout has occurred while performing an IO operation. + */ +RUBY_EXTERN VALUE rb_eIOTimeoutError; + +/** * Type of events that an IO can wait. * * @internal @@ -214,6 +222,11 @@ typedef struct rb_io_t { * This of course doesn't help inter-process IO interleaves, though. */ VALUE write_lock; + + /** + * The timeout associated with this IO when performing blocking operations. + */ + VALUE timeout; } rb_io_t; /** @alias{rb_io_enc_t} */ @@ -845,13 +858,37 @@ int rb_io_wait_writable(int fd); int rb_wait_for_single_fd(int fd, int events, struct timeval *tv); /** + * Get the timeout associated with the specified io object. + * + * @param[in] io An IO object. + * @retval RUBY_Qnil There is no associated timeout. + * @retval Otherwise The timeout value. + */ +VALUE rb_io_timeout(VALUE io); + +/** + * Set the timeout associated with the specified io object. This timeout is + * used as a best effort timeout to prevent operations from blocking forever. + * + * @param[in] io An IO object. + * @param[in] timeout A timeout value. Must respond to #to_f. + * @ + */ +VALUE rb_io_set_timeout(VALUE io, VALUE timeout); + +/** * Blocks until the passed IO is ready for the passed events. The "events" * here is a Ruby level integer, which is an OR-ed value of `IO::READABLE`, * `IO::WRITable`, and `IO::PRIORITY`. * + * If timeout is `Qnil`, it will use the default timeout as given by + * `rb_io_timeout(io)`. + * * @param[in] io An IO object to wait. * @param[in] events See above. * @param[in] timeout Time, or numeric seconds since UNIX epoch. + * If Qnil, use the default timeout. If Qfalse + * or Qundef, wait forever. * @exception rb_eIOError `io` is not open. * @exception rb_eRangeError `timeout` is out of range. * @exception rb_eSystemCallError `select(2)` failed for some reason. @@ -903,13 +940,8 @@ VALUE rb_io_maybe_wait(int error, VALUE io, VALUE events, VALUE timeout); * @exception rb_eIOError `io` is not open. * @exception rb_eRangeError `timeout` is out of range. * @exception rb_eSystemCallError `select(2)` failed for some reason. - * @exception rb_eTypeError Operation timed out. - * @return Always returns ::RUBY_IO_READABLE. - * - * @internal - * - * Because rb_io_maybe_wait() returns ::RUBY_Qfalse on timeout, this function - * fails to convert that value to `int`, and raises ::rb_eTypeError. + * @retval 0 Operation timed out. + * @retval Otherwise Always returns ::RUBY_IO_READABLE. */ int rb_io_maybe_wait_readable(int error, VALUE io, VALUE timeout); @@ -924,13 +956,8 @@ int rb_io_maybe_wait_readable(int error, VALUE io, VALUE timeout); * @exception rb_eIOError `io` is not open. * @exception rb_eRangeError `timeout` is out of range. * @exception rb_eSystemCallError `select(2)` failed for some reason. - * @exception rb_eTypeError Operation timed out. - * @return Always returns ::RUBY_IO_WRITABLE. - * - * @internal - * - * Because rb_io_maybe_wait() returns ::RUBY_Qfalse on timeout, this function - * fails to convert that value to `int`, and raises ::rb_eTypeError. + * @retval 0 Operation timed out. + * @retval Otherwise Always returns ::RUBY_IO_WRITABLE. */ int rb_io_maybe_wait_writable(int error, VALUE io, VALUE timeout); diff --git a/include/ruby/io/buffer.h b/include/ruby/io/buffer.h index 16b23ec629..e4b855d8e7 100644 --- a/include/ruby/io/buffer.h +++ b/include/ruby/io/buffer.h @@ -21,6 +21,8 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() // WARNING: This entire interface is experimental and may change in the future! #define RB_IO_BUFFER_EXPERIMENTAL 1 +#define RUBY_IO_BUFFER_VERSION 2 + RUBY_EXTERN VALUE rb_cIOBuffer; RUBY_EXTERN size_t RUBY_IO_BUFFER_PAGE_SIZE; RUBY_EXTERN size_t RUBY_IO_BUFFER_DEFAULT_SIZE; @@ -35,6 +37,9 @@ enum rb_io_buffer_flags { // A non-private mapping is marked as external. RB_IO_BUFFER_MAPPED = 4, + // A mapped buffer that is also shared. + RB_IO_BUFFER_SHARED = 8, + // The buffer is locked and cannot be resized. // More specifically, it means we can't change the base address or size. // A buffer is typically locked before a system call that uses the data. @@ -51,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 @@ -81,10 +82,10 @@ void rb_io_buffer_resize(VALUE self, size_t size); void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length); // The length is the minimum required length. -VALUE rb_io_buffer_read(VALUE self, VALUE io, size_t length); -VALUE rb_io_buffer_pread(VALUE self, VALUE io, size_t length, rb_off_t offset); -VALUE rb_io_buffer_write(VALUE self, VALUE io, size_t length); -VALUE rb_io_buffer_pwrite(VALUE self, VALUE io, size_t length, rb_off_t offset); +VALUE rb_io_buffer_read(VALUE self, VALUE io, size_t length, size_t offset); +VALUE rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset); +VALUE rb_io_buffer_write(VALUE self, VALUE io, size_t length, size_t offset); +VALUE rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset); RBIMPL_SYMBOL_EXPORT_END() diff --git a/include/ruby/memory_view.h b/include/ruby/memory_view.h index 83931038a0..1ddca2d46f 100644 --- a/include/ruby/memory_view.h +++ b/include/ruby/memory_view.h @@ -16,7 +16,7 @@ # include <stddef.h> /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include <sys/types.h> /* ssize_t */ #endif diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index a7ef59c7c8..8d7c601703 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -356,9 +356,9 @@ int onigenc_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, c #define ONIGENC_PRECISE_MBC_ENC_LEN(enc,p,e) (enc)->precise_mbc_enc_len(p,e,enc) ONIG_EXTERN -int onigenc_mbclen_approximate(const OnigUChar* p,const OnigUChar* e, const struct OnigEncodingTypeST* enc); +int onigenc_mbclen(const OnigUChar* p,const OnigUChar* e, const struct OnigEncodingTypeST* enc); -#define ONIGENC_MBC_ENC_LEN(enc,p,e) onigenc_mbclen_approximate(p,e,enc) +#define ONIGENC_MBC_ENC_LEN(enc,p,e) onigenc_mbclen(p,e,enc) #define ONIGENC_MBC_MAXLEN(enc) ((enc)->max_enc_len) #define ONIGENC_MBC_MAXLEN_DIST(enc) ONIGENC_MBC_MAXLEN(enc) #define ONIGENC_MBC_MINLEN(enc) ((enc)->min_enc_len) @@ -744,6 +744,8 @@ typedef struct { typedef struct { int lower; int upper; + long base_num; + long inner_num; } OnigRepeatRange; typedef void (*OnigWarnFunc)(const char* s); @@ -852,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/random.h b/include/ruby/random.h index 657b37f034..39bdb6f3e3 100644 --- a/include/ruby/random.h +++ b/include/ruby/random.h @@ -16,6 +16,26 @@ #include "ruby/ruby.h" +/* + * version + * 0: before versioning; deprecated + * 1: added version, flags and init_32bit function + */ +#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR 1 +#define RUBY_RANDOM_INTERFACE_VERSION_MINOR 0 + +#define RUBY_RANDOM_PASTE_VERSION_SUFFIX(x, y, z) x##_##y##_##z +#define RUBY_RANDOM_WITH_VERSION_SUFFIX(name, major, minor) \ + RUBY_RANDOM_PASTE_VERSION_SUFFIX(name, major, minor) +#define rb_random_data_type \ + RUBY_RANDOM_WITH_VERSION_SUFFIX(rb_random_data_type, \ + RUBY_RANDOM_INTERFACE_VERSION_MAJOR, \ + RUBY_RANDOM_INTERFACE_VERSION_MINOR) +#define RUBY_RANDOM_INTERFACE_VERSION_INITIALIZER \ + {RUBY_RANDOM_INTERFACE_VERSION_MAJOR, RUBY_RANDOM_INTERFACE_VERSION_MINOR} +#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX 0xff +#define RUBY_RANDOM_INTERFACE_VERSION_MINOR_MAX 0xff + RBIMPL_SYMBOL_EXPORT_BEGIN() /** @@ -48,6 +68,17 @@ typedef void rb_random_init_func(rb_random_t *rng, const uint32_t *buf, size_t l RBIMPL_ATTR_NONNULL(()) /** + * This is the type of functions called when your random object is initialised. + * Passed data is the seed integer. + * + * @param[out] rng Your random struct to fill in. + * @param[in] data Seed, single word. + * @post `rng` is initialised using the passed seeds. + */ +typedef void rb_random_init_int32_func(rb_random_t *rng, uint32_t data); + +RBIMPL_ATTR_NONNULL(()) +/** * This is the type of functions called from your object's `#rand` method. * * @param[out] rng Your random struct to extract an integer from. @@ -84,9 +115,24 @@ typedef struct { /** Number of bits of seed numbers. */ size_t default_seed_bits; - /** Initialiser function. */ + /** + * Major/minor versions of this interface + */ + struct { + uint8_t major, minor; + } version; + + /** + * Reserved flags + */ + uint16_t flags; + + /** Function to initialize from uint32_t array. */ rb_random_init_func *init; + /** Function to initialize from single uint32_t. */ + rb_random_init_int32_func *init_int32; + /** Function to obtain a random integer. */ rb_random_get_int32_func *get_int32; @@ -130,11 +176,12 @@ typedef struct { } rb_random_interface_t; /** - * This utility macro defines 3 functions named prefix_init, prefix_get_int32, - * prefix_get_bytes. + * This utility macro defines 4 functions named prefix_init, prefix_init_int32, + * prefix_get_int32, prefix_get_bytes. */ #define RB_RANDOM_INTERFACE_DECLARE(prefix) \ static void prefix##_init(rb_random_t *, const uint32_t *, size_t); \ + static void prefix##_init_int32(rb_random_t *, uint32_t); \ static unsigned int prefix##_get_int32(rb_random_t *); \ static void prefix##_get_bytes(rb_random_t *, void *, size_t) @@ -161,7 +208,9 @@ typedef struct { * ``` */ #define RB_RANDOM_INTERFACE_DEFINE(prefix) \ + RUBY_RANDOM_INTERFACE_VERSION_INITIALIZER, 0, \ prefix##_init, \ + prefix##_init_int32, \ prefix##_get_int32, \ prefix##_get_bytes @@ -173,6 +222,12 @@ typedef struct { RB_RANDOM_INTERFACE_DEFINE(prefix), \ prefix##_get_real +#define RB_RANDOM_DEFINE_INIT_INT32_FUNC(prefix) \ + static void prefix##_init_int32(rb_random_t *rnd, uint32_t data) \ + { \ + prefix##_init(rnd, &data, 1); \ + } + #if defined _WIN32 && !defined __CYGWIN__ typedef rb_data_type_t rb_random_data_type_t; # define RB_RANDOM_PARENT 0 @@ -189,7 +244,7 @@ typedef const rb_data_type_t rb_random_data_type_t; * 0, RB_RANDOM_INTERFACE_DEFINE(your), * }; * - * static inline constexpr your_prng = { + * static inline constexpr rb_random_data_type_t your_prng_type = { * "your PRNG", * { rb_random_mark, }, * RB_RANDOM_PARENT, // <<-- HERE 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/util.h b/include/ruby/util.h index f0ea874322..e8727a3200 100644 --- a/include/ruby/util.h +++ b/include/ruby/util.h @@ -19,7 +19,7 @@ # include <stddef.h> /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include <sys/types.h> /* ssize_t */ #endif diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 93e6183ed9..18de3a17d8 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -19,11 +19,6 @@ RUBY_SYMBOL_EXPORT_BEGIN */ /* - * Definitions for NT port of Perl - */ - - -/* * Ok now we can include the normal include files. */ @@ -130,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 @@ -302,7 +304,6 @@ extern DWORD rb_w32_osver(void); extern int rb_w32_uchown(const char *, int, int); extern int rb_w32_ulink(const char *, const char *); extern ssize_t rb_w32_ureadlink(const char *, char *, size_t); -extern ssize_t rb_w32_wreadlink(const WCHAR *, WCHAR *, size_t); extern int rb_w32_usymlink(const char *src, const char *link); extern int gettimeofday(struct timeval *, struct timezone *); extern int clock_gettime(clockid_t, struct timespec *); @@ -393,6 +394,7 @@ scalb(double a, long b) #endif #define S_IFLNK 0xa000 +#define S_IFSOCK 0xc000 /* * define this so we can do inplace editing @@ -20,6 +20,7 @@ static void Init_builtin_prelude(void); void rb_call_inits(void) { + CALL(default_shapes); CALL(Thread_Mutex); #if USE_TRANSIENT_HEAP CALL(TransientHeap); @@ -77,6 +78,7 @@ rb_call_inits(void) CALL(vm_stack_canary); CALL(ast); CALL(gc_stress); + CALL(shape); // enable builtin loading CALL(builtin); @@ -97,6 +99,7 @@ rb_call_builtin_inits(void) BUILTIN(warning); BUILTIN(array); BUILTIN(kernel); + BUILTIN(symbol); BUILTIN(timev); BUILTIN(thread_sync); BUILTIN(yjit); @@ -105,7 +108,6 @@ rb_call_builtin_inits(void) #if USE_MJIT BUILTIN(mjit); BUILTIN(mjit_c); - BUILTIN(mjit_compiler); #endif Init_builtin_prelude(); } @@ -697,7 +697,7 @@ defined { val = Qnil; if (vm_defined(ec, GET_CFP(), op_type, obj, v)) { - val = pushval; + val = pushval; } } diff --git a/internal.h b/internal.h index 0740ae99e5..b63af50616 100644 --- a/internal.h +++ b/internal.h @@ -25,6 +25,9 @@ /* Prevent compiler from reordering access */ #define ACCESS_ONCE(type,x) (*((volatile type *)&(x))) +#define UNDEF_P RB_UNDEF_P +#define NIL_OR_UNDEF_P RB_NIL_OR_UNDEF_P + #include "ruby/ruby.h" /* Following macros were formerly defined in this header but moved to somewhere @@ -48,9 +51,6 @@ #undef RHASH_TBL #undef RHASH_EMPTY_P -/* internal/object.h */ -#undef ROBJECT_IV_INDEX_TBL - /* internal/struct.h */ #undef RSTRUCT_LEN #undef RSTRUCT_PTR diff --git a/internal/basic_operators.h b/internal/basic_operators.h new file mode 100644 index 0000000000..2cd9f50073 --- /dev/null +++ b/internal/basic_operators.h @@ -0,0 +1,64 @@ +#ifndef INTERNAL_BOP_H /*-*-C-*-vi:se ft=c:*/ +#define INTERNAL_BOP_H + +#include "internal.h" +#include "ruby/internal/dllexport.h" + +enum ruby_basic_operators { + BOP_PLUS, + BOP_MINUS, + BOP_MULT, + BOP_DIV, + BOP_MOD, + BOP_EQ, + BOP_EQQ, + BOP_LT, + BOP_LE, + BOP_LTLT, + BOP_AREF, + BOP_ASET, + BOP_LENGTH, + BOP_SIZE, + BOP_EMPTY_P, + BOP_NIL_P, + BOP_SUCC, + BOP_GT, + BOP_GE, + BOP_NOT, + BOP_NEQ, + BOP_MATCH, + BOP_FREEZE, + BOP_UMINUS, + BOP_MAX, + BOP_MIN, + BOP_CALL, + BOP_AND, + BOP_OR, + BOP_CMP, + BOP_DEFAULT, + + BOP_LAST_ +}; + +MJIT_SYMBOL_EXPORT_BEGIN +RUBY_EXTERN short ruby_vm_redefined_flag[BOP_LAST_]; +MJIT_SYMBOL_EXPORT_END + +/* optimize insn */ +#define INTEGER_REDEFINED_OP_FLAG (1 << 0) +#define FLOAT_REDEFINED_OP_FLAG (1 << 1) +#define STRING_REDEFINED_OP_FLAG (1 << 2) +#define ARRAY_REDEFINED_OP_FLAG (1 << 3) +#define HASH_REDEFINED_OP_FLAG (1 << 4) +/* #define BIGNUM_REDEFINED_OP_FLAG (1 << 5) */ +#define SYMBOL_REDEFINED_OP_FLAG (1 << 6) +#define TIME_REDEFINED_OP_FLAG (1 << 7) +#define REGEXP_REDEFINED_OP_FLAG (1 << 8) +#define NIL_REDEFINED_OP_FLAG (1 << 9) +#define TRUE_REDEFINED_OP_FLAG (1 << 10) +#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)) + +#endif diff --git a/internal/class.h b/internal/class.h index ae680564a6..63917e867f 100644 --- a/internal/class.h +++ b/internal/class.h @@ -14,6 +14,10 @@ #include "ruby/internal/stdbool.h" /* for bool */ #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 @@ -25,24 +29,15 @@ struct rb_subclass_entry { struct rb_subclass_entry *prev; }; -struct rb_iv_index_tbl_entry { - uint32_t index; - rb_serial_t class_serial; - VALUE class_value; -}; - struct rb_cvar_class_tbl_entry { uint32_t index; rb_serial_t global_cvar_state; + const rb_cref_t * cref; VALUE class_value; }; struct rb_classext_struct { - struct st_table *iv_index_tbl; // ID -> struct rb_iv_index_tbl_entry - struct st_table *iv_tbl; -#if SIZEOF_SERIAL_T == SIZEOF_VALUE /* otherwise m_tbl is in struct RClass */ - struct rb_id_table *m_tbl; -#endif + VALUE *iv_ptr; struct rb_id_table *const_tbl; struct rb_id_table *callable_m_tbl; struct rb_id_table *cc_tbl; /* ID -> [[ci, cc1], cc2, ...] */ @@ -57,63 +52,42 @@ struct rb_classext_struct { * included. Hopefully that makes sense. */ struct rb_subclass_entry *module_subclass_entry; -#if SIZEOF_SERIAL_T != SIZEOF_VALUE && !USE_RVARGC /* otherwise class_serial is in struct RClass */ - rb_serial_t class_serial; -#endif const VALUE origin_; const VALUE refined_class; 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 }; struct RClass { struct RBasic basic; VALUE super; -#if !USE_RVARGC - struct rb_classext_struct *ptr; -#endif -#if SIZEOF_SERIAL_T == SIZEOF_VALUE - /* Class serial is as wide as VALUE. Place it here. */ - rb_serial_t class_serial; -#else - /* Class serial does not fit into struct RClass. Place m_tbl instead. */ struct rb_id_table *m_tbl; -# if USE_RVARGC - rb_serial_t *class_serial_ptr; -# endif +#if SIZE_POOL_COUNT == 1 + struct rb_classext_struct *ptr; #endif }; typedef struct rb_subclass_entry rb_subclass_entry_t; typedef struct rb_classext_struct rb_classext_t; -#if USE_RVARGC +#if RCLASS_EXT_EMBEDDED # define RCLASS_EXT(c) ((rb_classext_t *)((char *)(c) + sizeof(struct RClass))) #else # define RCLASS_EXT(c) (RCLASS(c)->ptr) #endif -#define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl) #define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl) -#if SIZEOF_SERIAL_T == SIZEOF_VALUE -# define RCLASS_M_TBL(c) (RCLASS_EXT(c)->m_tbl) -#else -# define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) -#endif +#define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) +#define RCLASS_IVPTR(c) (RCLASS_EXT(c)->iv_ptr) #define RCLASS_CALLABLE_M_TBL(c) (RCLASS_EXT(c)->callable_m_tbl) #define RCLASS_CC_TBL(c) (RCLASS_EXT(c)->cc_tbl) #define RCLASS_CVC_TBL(c) (RCLASS_EXT(c)->cvc_tbl) -#define RCLASS_IV_INDEX_TBL(c) (RCLASS_EXT(c)->iv_index_tbl) #define RCLASS_ORIGIN(c) (RCLASS_EXT(c)->origin_) #define RCLASS_REFINED_CLASS(c) (RCLASS_EXT(c)->refined_class) -#if SIZEOF_SERIAL_T == SIZEOF_VALUE -# define RCLASS_SERIAL(c) (RCLASS(c)->class_serial) -#else -# if USE_RVARGC -# define RCLASS_SERIAL(c) (*RCLASS(c)->class_serial_ptr) -# else -# define RCLASS_SERIAL(c) (RCLASS_EXT(c)->class_serial) -# endif -#endif #define RCLASS_INCLUDER(c) (RCLASS_EXT(c)->includer) #define RCLASS_SUBCLASS_ENTRY(c) (RCLASS_EXT(c)->subclass_entry) #define RCLASS_MODULE_SUBCLASS_ENTRY(c) (RCLASS_EXT(c)->module_subclass_entry) @@ -145,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/cmdlineopt.h b/internal/cmdlineopt.h index 71568a8745..bf52f1214b 100644 --- a/internal/cmdlineopt.h +++ b/internal/cmdlineopt.h @@ -36,6 +36,9 @@ typedef struct ruby_cmdline_options { unsigned int do_split: 1; unsigned int do_search: 1; unsigned int setids: 2; +#if USE_YJIT + unsigned int yjit: 1; +#endif } ruby_cmdline_options_t; struct ruby_opt_message { diff --git a/internal/compar.h b/internal/compar.h index 5e336adafa..9115e4bd63 100644 --- a/internal/compar.h +++ b/internal/compar.h @@ -8,38 +8,18 @@ * file COPYING are met. Consult the file for details. * @brief Internal header for Comparable. */ -#include "internal/vm.h" /* for rb_method_basic_definition_p */ +#include "internal/basic_operators.h" #define STRING_P(s) (RB_TYPE_P((s), T_STRING) && CLASS_OF(s) == rb_cString) -enum { - cmp_opt_Integer, - cmp_opt_String, - cmp_opt_Float, - cmp_optimizable_count -}; +#define CMP_OPTIMIZABLE(type) BASIC_OP_UNREDEFINED_P(BOP_CMP, type##_REDEFINED_OP_FLAG) -struct cmp_opt_data { - unsigned int opt_methods; - unsigned int opt_inited; -}; - -#define NEW_CMP_OPT_MEMO(type, value) \ - NEW_PARTIAL_MEMO_FOR(type, value, cmp_opt) -#define CMP_OPTIMIZABLE_BIT(type) (1U << TOKEN_PASTE(cmp_opt_,type)) -#define CMP_OPTIMIZABLE(data, type) \ - (((data).opt_inited & CMP_OPTIMIZABLE_BIT(type)) ? \ - ((data).opt_methods & CMP_OPTIMIZABLE_BIT(type)) : \ - (((data).opt_inited |= CMP_OPTIMIZABLE_BIT(type)), \ - rb_method_basic_definition_p(TOKEN_PASTE(rb_c,type), id_cmp) && \ - ((data).opt_methods |= CMP_OPTIMIZABLE_BIT(type)))) - -#define OPTIMIZED_CMP(a, b, data) \ - ((FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(data, Integer)) ? \ +#define OPTIMIZED_CMP(a, b) \ + ((FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(INTEGER)) ? \ (((long)a > (long)b) ? 1 : ((long)a < (long)b) ? -1 : 0) : \ - (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(data, String)) ? \ + (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(STRING)) ? \ rb_str_cmp(a, b) : \ - (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b) && CMP_OPTIMIZABLE(data, Float)) ? \ + (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b) && CMP_OPTIMIZABLE(FLOAT)) ? \ rb_float_cmp(a, b) : \ rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b)) diff --git a/internal/cont.h b/internal/cont.h index abffc97104..c3b091668a 100644 --- a/internal/cont.h +++ b/internal/cont.h @@ -9,6 +9,7 @@ * @brief Internal header for Fiber. */ #include "ruby/ruby.h" /* for VALUE */ +#include "iseq.h" struct rb_thread_struct; /* in vm_core.h */ struct rb_fiber_struct; /* in cont.c */ @@ -17,7 +18,12 @@ struct rb_execution_context_struct; /* in vm_core.c */ /* cont.c */ void rb_fiber_reset_root_local_storage(struct rb_thread_struct *); void ruby_register_rollback_func_for_ensure(VALUE (*ensure_func)(VALUE), VALUE (*rollback_func)(VALUE)); -void rb_fiber_init_mjit_cont(struct rb_fiber_struct *fiber); +void rb_jit_cont_init(void); +void rb_jit_cont_each_iseq(rb_iseq_callback callback, void *data); +void rb_jit_cont_finish(void); + +// Copy locals from the current execution to the specified fiber. +VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb_fiber_struct *fiber); VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber); unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber); diff --git a/internal/encoding.h b/internal/encoding.h index 853426a58d..a3b81bd388 100644 --- a/internal/encoding.h +++ b/internal/encoding.h @@ -12,6 +12,9 @@ #include "ruby/encoding.h" /* for rb_encoding */ #define rb_enc_autoload_p(enc) (!rb_enc_mbmaxlen(enc)) +#define rb_is_usascii_enc(enc) ((enc) == rb_usascii_encoding()) +#define rb_is_ascii8bit_enc(enc) ((enc) == rb_ascii8bit_encoding()) +#define rb_is_locale_enc(enc) ((enc) == rb_locale_encoding()) /* encoding.c */ ID rb_id_encoding(void); 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 84b7f9fa3e..e54a5dce9d 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -67,12 +67,16 @@ struct rb_objspace; /* in vm_core.h */ rb_obj_write((VALUE)(a), UNALIGNED_MEMBER_ACCESS((VALUE *)(slot)), \ (VALUE)(b), __FILE__, __LINE__) -#if USE_RVARGC +// 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 #endif +#define RCLASS_EXT_EMBEDDED (SIZE_POOL_COUNT > 1) + typedef struct ractor_newobj_size_pool_cache { struct RVALUE *freelist; struct heap_page *using_page; 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/numeric.h b/internal/numeric.h index 19069cb3bc..89bc54b307 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -35,12 +35,16 @@ enum ruby_num_rounding_mode { RUBY_NUM_ROUND_DEFAULT = ROUND_DEFAULT, }; +/* same as internal.h */ +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + #if SIZEOF_DOUBLE <= SIZEOF_VALUE typedef double rb_float_value_type; #else typedef struct { - VALUE values[(SIZEOF_DOUBLE + SIZEOF_VALUE - 1) / SIZEOF_VALUE]; - /* roomof() needs internal.h, and the order of some macros may matter */ + VALUE values[roomof(SIZEOF_DOUBLE, SIZEOF_VALUE)]; } rb_float_value_type; #endif diff --git a/internal/object.h b/internal/object.h index 88f3a44bc6..7b54e13dd2 100644 --- a/internal/object.h +++ b/internal/object.h @@ -9,11 +9,6 @@ * @brief Internal header for Object. */ #include "ruby/ruby.h" /* for VALUE */ -#include "internal/class.h" /* for RCLASS_IV_INDEX_TBL */ - -#ifdef ROBJECT_IV_INDEX_TBL -# undef ROBJECT_IV_INDEX_TBL -#endif /* object.c */ VALUE rb_class_search_ancestor(VALUE klass, VALUE super); @@ -26,7 +21,6 @@ int rb_bool_expected(VALUE, const char *, int raise); static inline void RBASIC_CLEAR_CLASS(VALUE obj); static inline void RBASIC_SET_CLASS_RAW(VALUE obj, VALUE klass); static inline void RBASIC_SET_CLASS(VALUE obj, VALUE klass); -static inline struct st_table *ROBJECT_IV_INDEX_TBL_inline(VALUE obj); RUBY_SYMBOL_EXPORT_BEGIN /* object.c (export) */ @@ -64,20 +58,4 @@ RBASIC_SET_CLASS(VALUE obj, VALUE klass) RBASIC_SET_CLASS_RAW(obj, klass); RB_OBJ_WRITTEN(obj, oldv, klass); } - -RBIMPL_ATTR_PURE() -static inline struct st_table * -ROBJECT_IV_INDEX_TBL_inline(VALUE obj) -{ - if (RB_FL_ANY_RAW(obj, ROBJECT_EMBED)) { - VALUE klass = rb_obj_class(obj); - return RCLASS_IV_INDEX_TBL(klass); - } - else { - const struct RObject *const ptr = ROBJECT(obj); - return ptr->as.heap.iv_index_tbl; - } -} -#define ROBJECT_IV_INDEX_TBL ROBJECT_IV_INDEX_TBL_inline - #endif /* INTERNAL_OBJECT_H */ diff --git a/internal/parse.h b/internal/parse.h index d9f5b56bc5..f242c384ad 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -15,6 +15,8 @@ struct rb_iseq_struct; /* in vm_core.h */ VALUE rb_parser_set_yydebug(VALUE, VALUE); void *rb_parser_load_file(VALUE parser, VALUE name); void rb_parser_keep_script_lines(VALUE vparser); +void rb_parser_error_tolerant(VALUE vparser); +void rb_parser_keep_tokens(VALUE vparser); RUBY_SYMBOL_EXPORT_BEGIN VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); diff --git a/internal/string.h b/internal/string.h index 43b716f9b2..12edbff2b1 100644 --- a/internal/string.h +++ b/internal/string.h @@ -106,7 +106,7 @@ STR_EMBED_P(VALUE str) static inline bool STR_SHARED_P(VALUE str) { - return FL_ALL_RAW(str, STR_NOEMBED | ELTS_SHARED); + return FL_ALL_RAW(str, STR_NOEMBED | STR_SHARED); } static inline bool 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 1a19e8964b..6dec6a6759 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -13,6 +13,7 @@ #include "constant.h" /* for rb_const_entry_t */ #include "ruby/internal/stdbool.h" /* for bool */ #include "ruby/ruby.h" /* for VALUE */ +#include "shape.h" /* for rb_shape_t */ /* global variable */ @@ -24,7 +25,6 @@ void rb_gc_update_global_tbl(void); size_t rb_generic_ivar_memsize(VALUE); VALUE rb_search_class_path(VALUE); VALUE rb_attr_delete(VALUE, ID); -VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef); void rb_autoload_str(VALUE mod, ID id, VALUE file); VALUE rb_autoload_at_p(VALUE, ID, int); NORETURN(VALUE rb_mod_const_missing(VALUE,VALUE)); @@ -35,7 +35,10 @@ void rb_gvar_ractor_local(const char *name); static inline bool ROBJ_TRANSIENT_P(VALUE obj); static inline void ROBJ_TRANSIENT_SET(VALUE obj); static inline void ROBJ_TRANSIENT_UNSET(VALUE obj); -uint32_t rb_obj_ensure_iv_index_mapping(VALUE obj, ID id); + +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) */ @@ -47,11 +50,15 @@ void rb_iv_tbl_copy(VALUE dst, VALUE src); RUBY_SYMBOL_EXPORT_END MJIT_SYMBOL_EXPORT_BEGIN +VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef); VALUE rb_gvar_get(ID); VALUE rb_gvar_set(ID, VALUE); VALUE rb_gvar_defined(ID); void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID); -void rb_init_iv_list(VALUE obj); +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, rb_shape_t *shape, uint32_t newsize); +attr_index_t rb_obj_ivar_set(VALUE obj, ID id, VALUE val); MJIT_SYMBOL_EXPORT_END static inline bool diff --git a/internal/vm.h b/internal/vm.h index c6c6b2ccc2..cf245c6579 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -40,7 +40,7 @@ enum method_missing_reason { }; /* vm_insnhelper.h */ -rb_serial_t rb_next_class_serial(void); +VALUE rb_vm_push_frame_fname(struct rb_execution_context_struct *ec, VALUE fname); /* vm.c */ VALUE rb_obj_is_thread(VALUE obj); @@ -167,6 +167,8 @@ off_t __syscall(quad_t number, ...); #define IO_RBUF_CAPA_FOR(fptr) (NEED_READCONV(fptr) ? IO_CBUF_CAPA_MIN : IO_RBUF_CAPA_MIN) #define IO_WBUF_CAPA_MIN 8192 +#define IO_MAX_BUFFER_GROWTH 8 * 1024 * 1024 // 8MB + /* define system APIs */ #ifdef _WIN32 #undef open @@ -178,6 +180,7 @@ off_t __syscall(quad_t number, ...); VALUE rb_cIO; VALUE rb_eEOFError; VALUE rb_eIOError; +VALUE rb_eIOTimeoutError; VALUE rb_mWaitReadable; VALUE rb_mWaitWritable; @@ -493,7 +496,7 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd) #if defined(_WIN32) #define WAIT_FD_IN_WIN32(fptr) \ - (rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil)) + (rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT)) #else #define WAIT_FD_IN_WIN32(fptr) #endif @@ -831,6 +834,54 @@ rb_io_set_write_io(VALUE io, VALUE w) /* * call-seq: + * timeout -> duration or nil + * + * Get the internal timeout duration or nil if it was not set. + * + */ +VALUE +rb_io_timeout(VALUE self) +{ + rb_io_t *fptr = rb_io_get_fptr(self); + + return fptr->timeout; +} + +/* + * call-seq: + * timeout = duration -> duration + * timeout = nil -> nil + * + * Set the internal timeout to the specified duration or nil. The timeout + * applies to all blocking operations where possible. + * + * This affects the following methods (but is not limited to): #gets, #puts, + * #read, #write, #wait_readable and #wait_writable. This also affects + * blocking socket operations like Socket#accept and Socket#connect. + * + * Some operations like File#open and IO#close are not affected by the + * timeout. A timeout during a write operation may leave the IO in an + * inconsistent state, e.g. data was partially written. Generally speaking, a + * timeout is a last ditch effort to prevent an application from hanging on + * slow I/O operations, such as those that occur during a slowloris attack. + */ +VALUE +rb_io_set_timeout(VALUE self, VALUE timeout) +{ + // Validate it: + if (RTEST(timeout)) { + rb_time_interval(timeout); + } + + rb_io_t *fptr = rb_io_get_fptr(self); + + fptr->timeout = timeout; + + return self; +} + +/* + * call-seq: * IO.try_convert(object) -> new_io or nil * * Attempts to convert +object+ into an \IO object via method +to_io+; @@ -1000,7 +1051,7 @@ void rb_io_read_check(rb_io_t *fptr) { if (!READ_DATA_PENDING(fptr)) { - rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil); + rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT); } return; } @@ -1052,56 +1103,124 @@ struct io_internal_read_struct { VALUE th; rb_io_t *fptr; int nonblock; + int fd; + void *buf; size_t capa; + struct timeval *timeout; }; struct io_internal_write_struct { + VALUE th; + rb_io_t *fptr; + int nonblock; int fd; + const void *buf; size_t capa; + struct timeval *timeout; }; #ifdef HAVE_WRITEV struct io_internal_writev_struct { + VALUE th; + rb_io_t *fptr; + int nonblock; int fd; + int iovcnt; const struct iovec *iov; + struct timeval *timeout; }; #endif -static int nogvl_wait_for(VALUE th, rb_io_t *fptr, short events); +static int nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout); + +/** + * Wait for the given events on the given file descriptor. + * Returns -1 if an error or timeout occurred. +errno+ will be set. + * Returns the event mask if an event occurred. + */ +static inline int +io_internal_wait(VALUE thread, rb_io_t *fptr, int error, int events, struct timeval *timeout) +{ + int ready = nogvl_wait_for(thread, fptr, events, timeout); + + if (ready > 0) { + return ready; + } + else if (ready == 0) { + errno = ETIMEDOUT; + return -1; + } + + errno = error; + return -1; +} + static VALUE internal_read_func(void *ptr) { struct io_internal_read_struct *iis = ptr; - ssize_t r; -retry: - r = read(iis->fptr->fd, iis->buf, iis->capa); - if (r < 0 && !iis->nonblock) { - int e = errno; - if (io_again_p(e)) { - if (nogvl_wait_for(iis->th, iis->fptr, RB_WAITFD_IN) != -1) { + ssize_t result; + + if (iis->timeout && !iis->nonblock) { + if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_IN, iis->timeout) == -1) { + return -1; + } + } + + retry: + result = read(iis->fd, iis->buf, iis->capa); + + if (result < 0 && !iis->nonblock) { + if (io_again_p(errno)) { + if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_IN, iis->timeout) == -1) { + return -1; + } + else { goto retry; } - errno = e; } } - return r; + + return result; } #if defined __APPLE__ -# define do_write_retry(code) do {ret = code;} while (ret == -1 && errno == EPROTOTYPE) +# define do_write_retry(code) do {result = code;} while (result == -1 && errno == EPROTOTYPE) #else -# define do_write_retry(code) ret = code +# define do_write_retry(code) result = code #endif + static VALUE internal_write_func(void *ptr) { struct io_internal_write_struct *iis = ptr; - ssize_t ret; + ssize_t result; + + if (iis->timeout && !iis->nonblock) { + if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_OUT, iis->timeout) == -1) { + return -1; + } + } + + retry: do_write_retry(write(iis->fd, iis->buf, iis->capa)); - return (VALUE)ret; + + if (result < 0 && !iis->nonblock) { + int e = errno; + if (io_again_p(e)) { + if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) { + return -1; + } + else { + goto retry; + } + } + } + + return result; } #ifdef HAVE_WRITEV @@ -1109,20 +1228,40 @@ static VALUE internal_writev_func(void *ptr) { struct io_internal_writev_struct *iis = ptr; - ssize_t ret; + ssize_t result; + + if (iis->timeout && !iis->nonblock) { + if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_OUT, iis->timeout) == -1) { + return -1; + } + } + + retry: do_write_retry(writev(iis->fd, iis->iov, iis->iovcnt)); - return (VALUE)ret; + + if (result < 0 && !iis->nonblock) { + if (io_again_p(errno)) { + if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) { + return -1; + } + else { + goto retry; + } + } + } + + return result; } #endif static ssize_t -rb_read_internal(rb_io_t *fptr, void *buf, size_t count) +rb_io_read_memory(rb_io_t *fptr, void *buf, size_t count) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, fptr->self, buf, count, 0); - if (result != Qundef) { + if (!UNDEF_P(result)) { return rb_fiber_scheduler_io_result_apply(result); } } @@ -1131,31 +1270,53 @@ rb_read_internal(rb_io_t *fptr, void *buf, size_t count) .th = rb_thread_current(), .fptr = fptr, .nonblock = 0, + .fd = fptr->fd, + .buf = buf, - .capa = count + .capa = count, + .timeout = NULL, }; + struct timeval timeout_storage; + + if (fptr->timeout != Qnil) { + timeout_storage = rb_time_interval(fptr->timeout); + iis.timeout = &timeout_storage; + } + return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fptr->fd); } static ssize_t -rb_write_internal(rb_io_t *fptr, const void *buf, size_t count) +rb_io_write_memory(rb_io_t *fptr, const void *buf, size_t count) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, buf, count, 0); - if (result != Qundef) { + if (!UNDEF_P(result)) { return rb_fiber_scheduler_io_result_apply(result); } } struct io_internal_write_struct iis = { + .th = rb_thread_current(), + .fptr = fptr, + .nonblock = 0, .fd = fptr->fd, + .buf = buf, - .capa = count + .capa = count, + .timeout = NULL }; + struct timeval timeout_storage; + + if (fptr->timeout != Qnil) { + timeout_storage = rb_time_interval(fptr->timeout); + iis.timeout = &timeout_storage; + } + return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fptr->fd); } @@ -1163,23 +1324,36 @@ rb_write_internal(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 (result != Qundef) { - return rb_fiber_scheduler_io_result_apply(result); - } + if (!UNDEF_P(result)) { + return rb_fiber_scheduler_io_result_apply(result); } } struct io_internal_writev_struct iis = { + .th = rb_thread_current(), + .fptr = fptr, + .nonblock = 0, .fd = fptr->fd, + .iov = iov, .iovcnt = iovcnt, + .timeout = NULL }; + struct timeval timeout_storage; + + if (fptr->timeout != Qnil) { + timeout_storage = rb_time_interval(fptr->timeout); + iis.timeout = &timeout_storage; + } + return (ssize_t)rb_thread_io_blocking_region(internal_writev_func, &iis, fptr->fd); } #endif @@ -1196,11 +1370,13 @@ io_flush_buffer_sync(void *arg) fptr->wbuf.len = 0; return 0; } + if (0 <= r) { fptr->wbuf.off += (int)r; fptr->wbuf.len -= (int)r; errno = EAGAIN; } + return (VALUE)-1; } @@ -1231,7 +1407,7 @@ io_fflush(rb_io_t *fptr) return 0; while (fptr->wbuf.len > 0 && io_flush_buffer(fptr) != 0) { - if (!rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) + if (!rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) return -1; rb_io_check_closed(fptr); @@ -1255,6 +1431,10 @@ rb_io_wait(VALUE io, VALUE events, VALUE timeout) struct timeval tv_storage; struct timeval *tv = NULL; + if (NIL_OR_UNDEF_P(timeout)) { + timeout = fptr->timeout; + } + if (timeout != Qnil) { tv_storage = rb_time_interval(timeout); tv = &tv_storage; @@ -1473,7 +1653,7 @@ make_writeconv(rb_io_t *fptr) ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_READ_MASK; ecopts = fptr->encs.ecopts; - if (!fptr->encs.enc || (fptr->encs.enc == rb_ascii8bit_encoding() && !fptr->encs.enc2)) { + if (!fptr->encs.enc || (rb_is_ascii8bit_enc(fptr->encs.enc) && !fptr->encs.enc2)) { /* no encoding conversion */ fptr->writeconv_pre_ecflags = 0; fptr->writeconv_pre_ecopts = Qnil; @@ -1562,7 +1742,7 @@ io_binwrite_string_internal(rb_io_t *fptr, const char *ptr, long length) return result; } else { - return rb_write_internal(fptr, ptr, length); + return rb_io_write_memory(fptr, ptr, length); } } #else @@ -1597,7 +1777,7 @@ io_binwrite_string_internal(rb_io_t *fptr, const char *ptr, long length) } // Otherwise, we should write the data directly: - return rb_write_internal(fptr, ptr, length); + return rb_io_write_memory(fptr, ptr, length); } #endif @@ -1613,19 +1793,17 @@ 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; } // Wait for it to become writable: - else if (rb_io_maybe_wait_writable(errno, p->fptr->self, Qnil)) { + else if (rb_io_maybe_wait_writable(errno, p->fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { rb_io_check_closed(p->fptr); } else { @@ -1859,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) { @@ -1892,7 +2070,7 @@ io_binwritev_internal(VALUE arg) iov->iov_base = (char *)iov->iov_base + result; iov->iov_len -= result; } - else if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) { + else if (rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { rb_io_check_closed(fptr); } else { @@ -2070,6 +2248,7 @@ io_writev(int argc, const VALUE *argv, VALUE io) * Hello, World! * foobar2 * + * Related: IO#read. */ static VALUE @@ -2199,7 +2378,7 @@ rb_io_flush(VALUE io) * tell -> integer * * Returns the current position (in bytes) in +self+ - * (see {Position}[rdoc-ref:io_streams.rdoc@Position]): + * (see {Position}[rdoc-ref:IO@Position]): * * f = File.open('t.txt') * f.tell # => 0 @@ -2265,7 +2444,7 @@ interpret_seek_whence(VALUE vwhence) * seek(offset, whence = IO::SEEK_SET) -> 0 * * Seeks to the position given by integer +offset+ - * (see {Position}[rdoc-ref:io_streams.rdoc@Position]) + * (see {Position}[rdoc-ref:IO@Position]) * and constant +whence+, which is one of: * * - +:CUR+ or <tt>IO::SEEK_CUR</tt>: @@ -2325,7 +2504,7 @@ rb_io_seek_m(int argc, VALUE *argv, VALUE io) * pos = new_position -> new_position * * Seeks to the given +new_position+ (in bytes); - * see {Position}[rdoc-ref:io_streams.rdoc@Position]: + * see {Position}[rdoc-ref:IO@Position]: * * f = File.open('t.txt') * f.tell # => 0 @@ -2359,8 +2538,8 @@ static void clear_readconv(rb_io_t *fptr); * * Repositions the stream to its beginning, * setting both the position and the line number to zero; - * see {Position}[rdoc-ref:io_streams.rdoc@Position] - * and {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number]: + * see {Position}[rdoc-ref:IO@Position] + * and {Line Number}[rdoc-ref:IO@Line+Number]: * * f = File.open('t.txt') * f.tell # => 0 @@ -2398,12 +2577,12 @@ rb_io_rewind(VALUE io) static int fptr_wait_readable(rb_io_t *fptr) { - int ret = rb_io_maybe_wait_readable(errno, fptr->self, Qnil); + int result = rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT); - if (ret) + if (result) rb_io_check_closed(fptr); - return ret; + return result; } static int @@ -2422,7 +2601,7 @@ io_fillbuf(rb_io_t *fptr) } if (fptr->rbuf.len == 0) { retry: - r = rb_read_internal(fptr, fptr->rbuf.ptr, fptr->rbuf.capa); + r = rb_io_read_memory(fptr, fptr->rbuf.ptr, fptr->rbuf.capa); if (r < 0) { if (fptr_wait_readable(fptr)) @@ -2450,7 +2629,7 @@ io_fillbuf(rb_io_t *fptr) * eof -> true or false * * Returns +true+ if the stream is positioned at its end, +false+ otherwise; - * see {Position}[rdoc-ref:io_streams.rdoc@Position]: + * see {Position}[rdoc-ref:IO@Position]: * * f = File.open('t.txt') * f.eof # => false @@ -2732,6 +2911,29 @@ rb_io_pid(VALUE io) return PIDT2NUM(fptr->pid); } +/* + * call-seq: + * path -> string or nil + * + * Returns the path associated with the IO, or +nil+ if there is no path + * associated with the IO. It is not guaranteed that the path exists on + * the filesystem. + * + * $stdin.path # => "<STDIN>" + * + * File.open("testfile") {|f| f.path} # => "testfile" + */ + +static VALUE +rb_io_path(VALUE io) +{ + rb_io_t *fptr = RFILE(io)->fptr; + + if (!fptr) + return Qnil; + + return rb_obj_dup(fptr->pathv); +} /* * call-seq: @@ -2814,7 +3016,7 @@ io_bufread(char *ptr, long len, rb_io_t *fptr) while (n > 0) { again: rb_io_check_closed(fptr); - c = rb_read_internal(fptr, ptr+offset, n); + c = rb_io_read_memory(fptr, ptr+offset, n); if (c == 0) break; if (c < 0) { if (fptr_wait_readable(fptr)) @@ -3070,7 +3272,9 @@ io_setstrbuf(VALUE *str, long len) } len -= clen; } - rb_str_modify_expand(*str, len); + if ((rb_str_capacity(*str) - (size_t)RSTRING_LEN(*str)) < (size_t)len) { + rb_str_modify_expand(*str, len); + } return FALSE; } @@ -3153,7 +3357,17 @@ read_all(rb_io_t *fptr, long siz, VALUE str) pos += rb_str_coderange_scan_restartable(RSTRING_PTR(str) + pos, RSTRING_PTR(str) + bytes, enc, &cr); if (bytes < siz) break; siz += BUFSIZ; - rb_str_modify_expand(str, BUFSIZ); + + size_t capa = rb_str_capacity(str); + if (capa < (size_t)RSTRING_LEN(str) + BUFSIZ) { + if (capa < BUFSIZ) { + capa = BUFSIZ; + } + else if (capa > IO_MAX_BUFFER_GROWTH) { + capa = IO_MAX_BUFFER_GROWTH; + } + rb_str_modify_expand(str, capa); + } } if (shrinkable) io_shrink_read_string(str, RSTRING_LEN(str)); str = io_enc_str(str, fptr); @@ -3170,7 +3384,7 @@ rb_io_set_nonblock(rb_io_t *fptr) } static VALUE -read_internal_call(VALUE arg) +io_read_memory_call(VALUE arg) { struct io_internal_read_struct *iis = (struct io_internal_read_struct *)arg; @@ -3178,7 +3392,7 @@ read_internal_call(VALUE arg) if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, iis->fptr->self, iis->buf, iis->capa, 0); - if (result != Qundef) { + if (!UNDEF_P(result)) { // This is actually returned as a pseudo-VALUE and later cast to a long: return (VALUE)rb_fiber_scheduler_io_result_apply(result); } @@ -3188,9 +3402,9 @@ read_internal_call(VALUE arg) } static long -read_internal_locktmp(VALUE str, struct io_internal_read_struct *iis) +io_read_memory_locktmp(VALUE str, struct io_internal_read_struct *iis) { - return (long)rb_str_locktmp_ensure(str, read_internal_call, (VALUE)iis); + return (long)rb_str_locktmp_ensure(str, io_read_memory_call, (VALUE)iis); } #define no_exception_p(opts) !rb_opts_exception_p((opts), TRUE) @@ -3232,9 +3446,11 @@ io_getpartial(int argc, VALUE *argv, VALUE io, int no_exception, int nonblock) iis.th = rb_thread_current(); iis.fptr = fptr; iis.nonblock = nonblock; + iis.fd = fptr->fd; iis.buf = RSTRING_PTR(str); iis.capa = len; - n = read_internal_locktmp(str, &iis); + iis.timeout = NULL; + n = io_read_memory_locktmp(str, &iis); if (n < 0) { int e = errno; if (!nonblock && fptr_wait_readable(fptr)) @@ -3395,13 +3611,15 @@ io_read_nonblock(rb_execution_context_t *ec, VALUE io, VALUE length, VALUE str, n = read_buffered_data(RSTRING_PTR(str), len, fptr); if (n <= 0) { - rb_io_set_nonblock(fptr); + rb_fd_set_nonblock(fptr->fd); shrinkable |= io_setstrbuf(&str, len); iis.fptr = fptr; iis.nonblock = 1; + iis.fd = fptr->fd; iis.buf = RSTRING_PTR(str); iis.capa = len; - n = read_internal_locktmp(str, &iis); + iis.timeout = NULL; + n = io_read_memory_locktmp(str, &iis); if (n < 0) { int e = errno; if (io_again_p(e)) { @@ -3440,7 +3658,7 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex) if (io_fflush(fptr) < 0) rb_sys_fail_on_write(fptr); - rb_io_set_nonblock(fptr); + rb_fd_set_nonblock(fptr->fd); n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); RB_GC_GUARD(str); @@ -3462,14 +3680,13 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex) /* * call-seq: - * read(maxlen = nil) -> string or nil - * read(maxlen = nil, out_string) -> out_string or nil + * read(maxlen = nil, out_string = nil) -> new_string, out_string, or nil * - * Reads bytes from the stream (in binary mode): + * 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. @@ -3529,6 +3746,7 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex) * If you need the behavior like a single read(2) system call, * consider #readpartial, #read_nonblock, and #sysread. * + * Related: IO#write. */ static VALUE @@ -3587,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; @@ -3603,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 @@ -3645,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); @@ -3792,7 +4035,7 @@ extract_getline_opts(VALUE opts, struct getline_arg *args) kwds[0] = rb_intern_const("chomp"); } rb_get_kwargs(opts, kwds, 0, -2, &vchomp); - chomp = (vchomp != Qundef) && RTEST(vchomp); + chomp = (!UNDEF_P(vchomp)) && RTEST(vchomp); } args->chomp = chomp; } @@ -3906,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) { @@ -3937,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 */ @@ -4013,13 +4266,13 @@ rb_io_gets_internal(VALUE io) /* * call-seq: - * gets(sep = $/, **line_opts) -> string or nil - * gets(limit, **line_opts) -> string or nil - * gets(sep, limit, **line_opts) -> string or nil + * gets(sep = $/, chomp: false) -> string or nil + * gets(limit, chomp: false) -> string or nil + * gets(sep, limit, chomp: false) -> string or nil * - * Reads and returns a line from the stream - * (see {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO]); + * Reads and returns a line from the stream; * assigns the return value to <tt>$_</tt>. + * See {Line IO}[rdoc-ref:IO@Line+IO]. * * With no arguments given, returns the next line * as determined by line separator <tt>$/</tt>, or +nil+ if none: @@ -4036,7 +4289,7 @@ rb_io_gets_internal(VALUE io) * With only string argument +sep+ given, * returns the next line as determined by line separator +sep+, * or +nil+ if none; - * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]: + * see {Line Separator}[rdoc-ref:IO@Line+Separator]: * * f = File.new('t.txt') * f.gets('l') # => "First l" @@ -4057,7 +4310,7 @@ rb_io_gets_internal(VALUE io) * * With only integer argument +limit+ given, * limits the number of bytes in the line; - * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]: + * see {Line Limit}[rdoc-ref:IO@Line+Limit]: * * # No more than one line. * File.open('t.txt') {|f| f.gets(10) } # => "First line" @@ -4071,8 +4324,8 @@ rb_io_gets_internal(VALUE io) * or +nil+ if none. * - But returns no more bytes than are allowed by the limit. * - * For all forms above, optional keyword arguments +line_opts+ specify - * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]: + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted: * * f = File.open('t.txt') * # Chomp the lines. @@ -4101,8 +4354,8 @@ rb_io_gets_m(int argc, VALUE *argv, VALUE io) * call-seq: * lineno -> integer * - * Returns the current line number for the stream. - * See {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number]. + * Returns the current line number for the stream; + * see {Line Number}[rdoc-ref:IO@Line+Number]. * */ @@ -4120,8 +4373,8 @@ rb_io_lineno(VALUE io) * call-seq: * lineno = integer -> integer * - * Sets and returns the line number for the stream. - * See {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number]. + * Sets and returns the line number for the stream; + * see {Line Number}[rdoc-ref:IO@Line+Number]. * */ @@ -4138,12 +4391,14 @@ rb_io_set_lineno(VALUE io, VALUE lineno) /* * call-seq: - * readline(sep = $/, **line_opts) -> string - * readline(limit, **line_opts) -> string - * readline(sep, limit, **line_opts) -> string + * readline(sep = $/, chomp: false) -> string + * readline(limit, chomp: false) -> string + * readline(sep, limit, chomp: false) -> string * - * Reads a line as with IO#gets, but raises EOFError if already at end-of-file. + * Reads a line as with IO#gets, but raises EOFError if already at end-of-stream. * + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted. */ static VALUE @@ -4161,13 +4416,13 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io); /* * call-seq: - * readlines(sep = $/, **line_opts) -> array - * readlines(limit, **line_opts) -> array - * readlines(sep, limit, **line_opts) -> array + * readlines(sep = $/, chomp: false) -> array + * readlines(limit, chomp: false) -> array + * readlines(sep, limit, chomp: false) -> array * - * Reads and returns all remaining line from the stream - * (see {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO]); + * Reads and returns all remaining line from the stream; * does not modify <tt>$_</tt>. + * See {Line IO}[rdoc-ref:IO@Line+IO]. * * With no arguments given, returns lines * as determined by line separator <tt>$/</tt>, or +nil+ if none: @@ -4181,7 +4436,7 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io); * With only string argument +sep+ given, * returns lines as determined by line separator +sep+, * or +nil+ if none; - * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]: + * see {Line Separator}[rdoc-ref:IO@Line+Separator]: * * f = File.new('t.txt') * f.readlines('li') @@ -4202,7 +4457,7 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io); * * With only integer argument +limit+ given, * limits the number of bytes in each line; - * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]: + * see {Line Limit}[rdoc-ref:IO@Line+Limit]: * * f = File.new('t.txt') * f.readlines(8) @@ -4215,8 +4470,8 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io); * - Returns lines as determined by line separator +sep+. * - But returns no more bytes in a line than are allowed by the limit. * - * For all forms above, optional keyword arguments +line_opts+ specify - * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]: + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted: * * f = File.new('t.txt') * f.readlines(chomp: true) @@ -4250,15 +4505,15 @@ io_readlines(const struct getline_arg *arg, VALUE io) /* * call-seq: - * each_line(sep = $/, **line_opts) {|line| ... } -> self - * each_line(limit, **line_opts) {|line| ... } -> self - * each_line(sep, limit, **line_opts) {|line| ... } -> self + * each_line(sep = $/, chomp: false) {|line| ... } -> self + * each_line(limit, chomp: false) {|line| ... } -> self + * each_line(sep, limit, chomp: false) {|line| ... } -> self * each_line -> enumerator * - * Calls the block with each remaining line read from the stream - * (see {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO]); - * does nothing if already at end-of-file; + * Calls the block with each remaining line read from the stream; * returns +self+. + * Does nothing if already at end-of-stream; + * See {Line IO}[rdoc-ref:IO@Line+IO]. * * With no arguments given, reads lines * as determined by line separator <tt>$/</tt>: @@ -4278,7 +4533,7 @@ io_readlines(const struct getline_arg *arg, VALUE io) * * With only string argument +sep+ given, * reads lines as determined by line separator +sep+; - * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]: + * see {Line Separator}[rdoc-ref:IO@Line+Separator]: * * f = File.new('t.txt') * f.each_line('li') {|line| p line } @@ -4314,7 +4569,7 @@ io_readlines(const struct getline_arg *arg, VALUE io) * * With only integer argument +limit+ given, * limits the number of bytes in each line; - * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]: + * see {Line Limit}[rdoc-ref:IO@Line+Limit]: * * f = File.new('t.txt') * f.each_line(8) {|line| p line } @@ -4338,8 +4593,8 @@ io_readlines(const struct getline_arg *arg, VALUE io) * - Calls with the next line as determined by line separator +sep+. * - But returns no more bytes than are allowed by the limit. * - * For all forms above, optional keyword arguments +line_opts+ specify - * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]: + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted: * * f = File.new('t.txt') * f.each_line(chomp: true) {|line| p line } @@ -4380,7 +4635,8 @@ rb_io_each_line(int argc, VALUE *argv, VALUE io) * each_byte {|byte| ... } -> self * each_byte -> enumerator * - * Calls the given block with each byte (0..255) in the stream; returns +self+: + * Calls the given block with each byte (0..255) in the stream; returns +self+. + * See {Byte IO}[rdoc-ref:IO@Byte+IO]. * * f = File.new('t.rus') * a = [] @@ -4527,7 +4783,8 @@ io_getc(rb_io_t *fptr, rb_encoding *enc) * each_char {|c| ... } -> self * each_char -> enumerator * - * Calls the given block with each character in the stream; returns +self+: + * Calls the given block with each character in the stream; returns +self+. + * See {Character IO}[rdoc-ref:IO@Character+IO]. * * f = File.new('t.rus') * a = [] @@ -4688,7 +4945,8 @@ rb_io_each_codepoint(VALUE io) * getc -> character or nil * * Reads and returns the next 1-character string from the stream; - * returns +nil+ if already at end-of-file: + * returns +nil+ if already at end-of-stream. + * See {Character IO}[rdoc-ref:IO@Character+IO]. * * f = File.open('t.txt') * f.getc # => "F" @@ -4720,7 +4978,8 @@ rb_io_getc(VALUE io) * readchar -> string * * Reads and returns the next 1-character string from the stream; - * raises EOFError if already at end-of-file: + * raises EOFError if already at end-of-stream. + * See {Character IO}[rdoc-ref:IO@Character+IO]. * * f = File.open('t.txt') * f.readchar # => "F" @@ -4749,7 +5008,8 @@ rb_io_readchar(VALUE io) * getbyte -> integer or nil * * Reads and returns the next byte (in range 0..255) from the stream; - * returns +nil+ if already at end-of-file: + * returns +nil+ if already at end-of-stream. + * See {Byte IO}[rdoc-ref:IO@Byte+IO]. * * f = File.open('t.txt') * f.getbyte # => 70 @@ -4759,7 +5019,6 @@ rb_io_readchar(VALUE io) * f.close * * Related: IO#readbyte (may raise EOFError). - * */ VALUE @@ -4793,7 +5052,8 @@ rb_io_getbyte(VALUE io) * readbyte -> integer * * Reads and returns the next byte (in range 0..255) from the stream; - * raises EOFError if already at end-of-file: + * raises EOFError if already at end-of-stream. + * See {Byte IO}[rdoc-ref:IO@Byte+IO]. * * f = File.open('t.txt') * f.readbyte # => 70 @@ -4824,10 +5084,11 @@ rb_io_readbyte(VALUE io) * * Pushes back ("unshifts") the given data onto the stream's buffer, * placing the data so that it is next to be read; returns +nil+. + * See {Byte IO}[rdoc-ref:IO@Byte+IO]. * * Note that: * - * - Calling the method hs no effect with unbuffered reads (such as IO#sysread). + * - Calling the method has no effect with unbuffered reads (such as IO#sysread). * - Calling #rewind on the stream discards the pushed-back data. * * When argument +integer+ is given, uses only its low-order byte: @@ -4884,10 +5145,11 @@ rb_io_ungetbyte(VALUE io, VALUE b) * * Pushes back ("unshifts") the given data onto the stream's buffer, * placing the data so that it is next to be read; returns +nil+. + * See {Character IO}[rdoc-ref:IO@Character+IO]. * * Note that: * - * - Calling the method hs no effect with unbuffered reads (such as IO#sysread). + * - Calling the method has no effect with unbuffered reads (such as IO#sysread). * - Calling #rewind on the stream discards the pushed-back data. * * When argument +integer+ is given, interprets the integer as a character: @@ -4968,8 +5230,10 @@ rb_io_ungetc(VALUE io, VALUE c) * Returns +true+ if the stream is associated with a terminal device (tty), * +false+ otherwise: * - * File.new('t.txt').isatty #=> false - * File.new('/dev/tty').isatty #=> true + * f = File.new('t.txt').isatty #=> false + * f.close + * f = File.new('/dev/tty').isatty #=> true + * f.close * * IO#tty? is an alias for IO#isatty. * @@ -5103,13 +5367,13 @@ finish_writeconv(rb_io_t *fptr, int noalloc) res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0); while (dp-ds) { size_t remaining = dp-ds; - long result = rb_write_internal(fptr, ds, remaining); + long result = rb_io_write_memory(fptr, ds, remaining); if (result > 0) { ds += result; if ((size_t)result == remaining) break; } - else if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) { + else if (rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { if (fptr->fd < 0) return noalloc ? Qtrue : rb_exc_new3(rb_eIOError, rb_str_new_cstr(closed_stream)); } @@ -5254,7 +5518,7 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl, // VALUE scheduler = rb_fiber_scheduler_current(); // if (scheduler != Qnil) { // VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self); - // if (result != Qundef) done = 1; + // if (!UNDEF_P(result)) done = 1; // } // } @@ -5450,12 +5714,32 @@ rb_io_close(VALUE io) * call-seq: * close -> nil * - * Closes the stream, if it is open, after flushing any buffered writes - * to the operating system; does nothing if the stream is already closed. - * A stream is automatically closed when claimed by the garbage collector. + * Closes the stream for both reading and writing + * if open for either or both; returns +nil+. + * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams]. + * + * If the stream is open for writing, flushes any buffered writes + * to the operating system before closing. + * + * If the stream was opened by IO.popen, sets global variable <tt>$?</tt> + * (child exit status). + * + * Example: + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close + * puts $? + * puts pipe.closed? + * end + * + * Output: * - * If the stream was opened by IO.popen, #close sets global variable <tt>$?</tt>. + * false + * pid 13760 exit 0 + * true * + * Related: IO#close_read, IO#close_write, IO#closed?. */ static VALUE @@ -5493,7 +5777,7 @@ static VALUE io_close(VALUE io) { VALUE closed = rb_check_funcall(io, rb_intern("closed?"), 0, 0); - if (closed != Qundef && RTEST(closed)) return io; + if (!UNDEF_P(closed) && RTEST(closed)) return io; rb_rescue2(io_call_close, io, ignore_closed_stream, io, rb_eIOError, (VALUE)0); return io; @@ -5504,17 +5788,24 @@ io_close(VALUE io) * closed? -> true or false * * Returns +true+ if the stream is closed for both reading and writing, - * +false+ otherwise: + * +false+ otherwise. + * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams]. * - * f = File.new('t.txt') - * f.close # => nil - * f.closed? # => true - * f = IO.popen('/bin/sh','r+') - * f.close_write # => nil - * f.closed? # => false - * f.close_read # => nil - * f.closed? # => true + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_read + * puts pipe.closed? + * pipe.close_write + * puts pipe.closed? + * end + * + * Output: + * + * false + * false + * true * + * Related: IO#close_read, IO#close_write, IO#close. */ @@ -5541,15 +5832,32 @@ rb_io_closed(VALUE io) * call-seq: * close_read -> nil * - * Closes the read end of a duplexed stream (i.e., one that is both readable - * and writable, such as a pipe); does nothing if already closed: + * Closes the stream for reading if open for reading; + * returns +nil+. + * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams]. + * + * If the stream was opened by IO.popen and is also closed for writing, + * sets global variable <tt>$?</tt> (child exit status). + * + * Example: + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_write + * puts pipe.closed? + * pipe.close_read + * puts $? + * puts pipe.closed? + * end * - * f = IO.popen('/bin/sh','r+') - * f.close_read - * f.readlines # Raises IOError + * Output: * - * Raises an exception if the stream is not duplexed. + * false + * false + * pid 14748 exit 0 + * true * + * Related: IO#close, IO#close_write, IO#closed?. */ static VALUE @@ -5597,13 +5905,32 @@ rb_io_close_read(VALUE io) * call-seq: * close_write -> nil * - * Closes the write end of a duplexed stream (i.e., one that is both readable - * and writable, such as a pipe); does nothing if already closed: + * Closes the stream for writing if open for writing; + * returns +nil+. + * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams]. + * + * Flushes any buffered writes to the operating system before closing. + * + * If the stream was opened by IO.popen and is also closed for reading, + * sets global variable <tt>$?</tt> (child exit status). + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_read + * puts pipe.closed? + * pipe.close_write + * puts $? + * puts pipe.closed? + * end + * + * Output: * - * f = IO.popen('/bin/sh', 'r+') - * f.close_write - * f.print 'nowhere' # Raises IOError. + * false + * false + * pid 15044 exit 0 + * true * + * Related: IO#close, IO#close_read, IO#closed?. */ static VALUE @@ -5716,7 +6043,7 @@ rb_io_syswrite(VALUE io, VALUE str) tmp = rb_str_tmp_frozen_acquire(str); RSTRING_GETMEM(tmp, ptr, len); - n = rb_write_internal(fptr, ptr, len); + n = rb_io_write_memory(fptr, ptr, len); if (n < 0) rb_sys_fail_path(fptr->pathv); rb_str_tmp_frozen_release(str, tmp); @@ -5762,9 +6089,11 @@ rb_io_sysread(int argc, VALUE *argv, VALUE io) iis.th = rb_thread_current(); iis.fptr = fptr; iis.nonblock = 0; + iis.fd = fptr->fd; iis.buf = RSTRING_PTR(str); iis.capa = ilen; - n = read_internal_locktmp(str, &iis); + iis.timeout = NULL; + n = io_read_memory_locktmp(str, &iis); if (n < 0) { rb_sys_fail_path(fptr->pathv); @@ -5812,7 +6141,7 @@ pread_internal_call(VALUE arg) * * - Reads at the given +offset+ (in bytes). * - Disregards, and does not modify, the stream's position - * (see {Position}[rdoc-ref:io_streams.rdoc@Position]). + * (see {Position}[rdoc-ref:IO@Position]). * - Bypasses any user space buffering in the stream. * * Because this method does not disturb the stream's state @@ -5888,7 +6217,7 @@ internal_pwrite_func(void *ptr) * * - Writes at the given +offset+ (in bytes). * - Disregards, and does not modify, the stream's position - * (see {Position}[rdoc-ref:io_streams.rdoc@Position]). + * (see {Position}[rdoc-ref:IO@Position]). * - Bypasses any user space buffering in the stream. * * Because this method does not disturb the stream's state @@ -6258,7 +6587,7 @@ rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc, ext = rb_default_external_encoding(); default_ext = 1; } - if (ext == rb_ascii8bit_encoding()) { + if (rb_is_ascii8bit_enc(ext)) { /* If external is ASCII-8BIT, no transcoding */ intern = NULL; } @@ -6366,21 +6695,21 @@ rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2 v = rb_hash_lookup2(opt, sym_extenc, Qundef); if (v != Qnil) extenc = v; v = rb_hash_lookup2(opt, sym_intenc, Qundef); - if (v != Qundef) intenc = v; + if (!UNDEF_P(v)) intenc = v; } - if ((extenc != Qundef || intenc != Qundef) && !NIL_P(encoding)) { + if ((!UNDEF_P(extenc) || !UNDEF_P(intenc)) && !NIL_P(encoding)) { if (!NIL_P(ruby_verbose)) { int idx = rb_to_encoding_index(encoding); if (idx >= 0) encoding = rb_enc_from_encoding(rb_enc_from_index(idx)); rb_warn("Ignoring encoding parameter '%"PRIsVALUE"': %s_encoding is used", - encoding, extenc == Qundef ? "internal" : "external"); + encoding, UNDEF_P(extenc) ? "internal" : "external"); } encoding = Qnil; } - if (extenc != Qundef && !NIL_P(extenc)) { + if (!UNDEF_P(extenc) && !NIL_P(extenc)) { extencoding = rb_to_encoding(extenc); } - if (intenc != Qundef) { + if (!UNDEF_P(intenc)) { if (NIL_P(intenc)) { /* internal_encoding: nil => no transcoding */ intencoding = (rb_encoding *)Qnil; @@ -6413,7 +6742,7 @@ rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2 rb_io_ext_int_to_encs(rb_to_encoding(encoding), NULL, enc_p, enc2_p, 0); } } - else if (extenc != Qundef || intenc != Qundef) { + else if (!UNDEF_P(extenc) || !UNDEF_P(intenc)) { extracted = 1; rb_io_ext_int_to_encs(extencoding, intencoding, enc_p, enc2_p, 0); } @@ -7656,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); @@ -8212,6 +8541,7 @@ rb_io_init_copy(VALUE dest, VALUE io) fptr->encs = orig->encs; fptr->pid = orig->pid; fptr->lineno = orig->lineno; + fptr->timeout = orig->timeout; if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv; fptr_copy_finalizer(fptr, orig); @@ -8322,6 +8652,7 @@ deprecated_str_setter(VALUE val, ID id, VALUE *var) * Writes the given objects to the stream; returns +nil+. * Appends the output record separator <tt>$OUTPUT_RECORD_SEPARATOR</tt> * (<tt>$\\</tt>), if it is not +nil+. + * See {Line IO}[rdoc-ref:IO@Line+IO]. * * With argument +objects+ given, for each object: * @@ -8459,6 +8790,7 @@ rb_f_print(int argc, const VALUE *argv, VALUE _) * putc(object) -> object * * Writes a character to the stream. + * See {Character IO}[rdoc-ref:IO@Character+IO]. * * If +object+ is numeric, converts to integer if necessary, * then writes the character whose code is the @@ -8562,6 +8894,7 @@ io_puts_ary(VALUE ary, VALUE out, int recur) * returns +nil+.\ * Writes a newline after each that does not already end with a newline sequence. * If called without arguments, writes a newline. + * See {Line IO}[rdoc-ref:IO@Line+IO]. * * Note that each added newline is the character <tt>"\n"<//tt>, * not the output record separator (<tt>$\\</tt>). @@ -8603,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. */ @@ -8611,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); } @@ -8881,6 +9221,7 @@ prep_io(int fd, int fmode, VALUE klass, const char *path) fp->self = io; fp->fd = fd; fp->mode = fmode; + fp->timeout = Qnil; if (!io_check_tty(fp)) { #ifdef __CYGWIN__ fp->mode |= FMODE_BINMODE; @@ -8985,6 +9326,7 @@ rb_io_fptr_new(void) fp->encs.ecflags = 0; fp->encs.ecopts = Qnil; fp->write_lock = Qnil; + fp->timeout = Qnil; return fp; } @@ -9085,14 +9427,27 @@ rb_io_initialize(int argc, VALUE *argv, VALUE io) rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError)); } #endif - if (!NIL_P(opt) && rb_hash_aref(opt, sym_autoclose) == Qfalse) { - fmode |= FMODE_PREP; + VALUE path = Qnil; + + if (!NIL_P(opt)) { + if (rb_hash_aref(opt, sym_autoclose) == Qfalse) { + fmode |= FMODE_PREP; + } + + path = rb_hash_aref(opt, RB_ID2SYM(idPath)); + if (!NIL_P(path)) { + StringValue(path); + path = rb_str_new_frozen(path); + } } + MakeOpenFile(io, fp); fp->self = io; fp->fd = fd; fp->mode = fmode; fp->encs = convconfig; + fp->pathv = path; + fp->timeout = Qnil; clear_codeconv(fp); io_check_tty(fp); if (fileno(stdin) == fd) @@ -9163,31 +9518,32 @@ rb_io_set_encoding_by_bom(VALUE io) * * Argument +path+ must be a valid file path: * - * File.new('/etc/fstab') - * File.new('t.txt') + * f = File.new('/etc/fstab') + * f.close + * f = File.new('t.txt') + * f.close * * Optional argument +mode+ (defaults to 'r') must specify a valid mode; * see {Access Modes}[rdoc-ref:File@Access+Modes]: * - * File.new('t.tmp', 'w') - * File.new('t.tmp', File::RDONLY) + * f = File.new('t.tmp', 'w') + * f.close + * f = File.new('t.tmp', File::RDONLY) + * f.close * * Optional argument +perm+ (defaults to 0666) must specify valid permissions * see {File Permissions}[rdoc-ref:File@File+Permissions]: * - * File.new('t.tmp', File::CREAT, 0644) - * File.new('t.tmp', File::CREAT, 0444) + * f = File.new('t.tmp', File::CREAT, 0644) + * f.close + * f = File.new('t.tmp', File::CREAT, 0444) + * f.close * * Optional keyword arguments +opts+ specify: * * - {Open Options}[rdoc-ref:IO@Open+Options]. * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options]. * - * Examples: - * - * File.new('t.tmp', autoclose: true) - * File.new('t.tmp', internal_encoding: nil) - * */ static VALUE @@ -9460,7 +9816,7 @@ io_wait(int argc, VALUE *argv, VALUE io) if (RB_SYMBOL_P(argv[i])) { events |= wait_mode_sym(argv[i]); } - else if (timeout == Qundef) { + else if (UNDEF_P(timeout)) { rb_time_interval(timeout = argv[i]); } else { @@ -9468,7 +9824,7 @@ io_wait(int argc, VALUE *argv, VALUE io) } } - if (timeout == Qundef) timeout = Qnil; + if (UNDEF_P(timeout)) timeout = Qnil; if (events == 0) { events = RUBY_IO_READABLE; @@ -9970,9 +10326,9 @@ static VALUE argf_readline(int, VALUE *, VALUE); /* * call-seq: - * readline(sep = $/, **line_opts) -> string - * readline(limit, **line_opts) -> string - * readline(sep, limit, **line_opts) -> string + * readline(sep = $/, chomp: false) -> string + * readline(limit, chomp: false) -> string + * readline(sep, limit, chomp: false) -> string * * Equivalent to method Kernel#gets, except that it raises an exception * if called at end-of-stream: @@ -9981,6 +10337,8 @@ static VALUE argf_readline(int, VALUE *, VALUE); * ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] * in `readline': end of file reached (EOFError) * + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted. */ static VALUE @@ -10029,18 +10387,18 @@ static VALUE argf_readlines(int, VALUE *, VALUE); /* * call-seq: - * readlines(sep = $/, **line_opts) -> array - * readlines(limit, **line_opts) -> array - * readlines(sep, limit, **line_opts) -> array + * readlines(sep = $/, chomp: false, **enc_opts) -> array + * readlines(limit, chomp: false, **enc_opts) -> array + * readlines(sep, limit, chomp: false, **enc_opts) -> array * * Returns an array containing the lines returned by calling - * Kernel#gets until the end-of-file is reached; - * (see {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO]). + * Kernel#gets until the end-of-stream is reached; + * (see {Line IO}[rdoc-ref:IO@Line+IO]). * * With only string argument +sep+ given, * returns the remaining lines as determined by line separator +sep+, * or +nil+ if none; - * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]: + * see {Line Separator}[rdoc-ref:IO@Line+Separator]: * * # Default separator. * $ cat t.txt | ruby -e "p readlines" @@ -10060,7 +10418,7 @@ static VALUE argf_readlines(int, VALUE *, VALUE); * * With only integer argument +limit+ given, * limits the number of bytes in the line; - * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]: + * see {Line Limit}[rdoc-ref:IO@Line+Limit]: * * $cat t.txt | ruby -e "p readlines 10" * ["First line", "\n", "Second lin", "e\n", "\n", "Fourth lin", "e\n", "Fifth line", "\n"] @@ -10072,18 +10430,17 @@ static VALUE argf_readlines(int, VALUE *, VALUE); * ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] * * With arguments +sep+ and +limit+ given, combines the two behaviors; - * see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit]. - * - * For all forms above, optional keyword arguments specify: + * see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]. * - * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]. - * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options]. - * - * Examples: + * Optional keyword argument +chomp+ specifies whether line separators + * are to be omitted: * * $ cat t.txt | ruby -e "p readlines(chomp: true)" * ["First line", "Second line", "", "Fourth line", "Fifth line"] * + * Optional keyword arguments +enc_opts+ specify encoding options; + * see {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options]. + * */ static VALUE @@ -10649,6 +11006,13 @@ rb_io_advise(int argc, VALUE *argv, VALUE io) static VALUE rb_f_select(int argc, VALUE *argv, VALUE obj) { + VALUE scheduler = rb_fiber_scheduler_current(); + if (scheduler != Qnil) { + // It's optionally supported. + VALUE result = rb_fiber_scheduler_io_selectv(scheduler, argc, argv); + if (!UNDEF_P(result)) return result; + } + VALUE timeout; struct select_args args; struct timeval timerec; @@ -11560,7 +11924,7 @@ io_s_foreach(VALUE v) * For both forms, command and path, the remaining arguments are the same. * * With argument +sep+ given, parses lines as determined by that line separator - * (see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]): + * (see {Line Separator}[rdoc-ref:IO@Line+Separator]): * * File.foreach('t.txt', 'li') {|line| p line } * @@ -11583,7 +11947,7 @@ io_s_foreach(VALUE v) * * With argument +limit+ given, parses lines as determined by the default * line separator and the given line-length limit - * (see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]): + * (see {Line Limit}[rdoc-ref:IO@Line+Limit]): * * File.foreach('t.txt', 7) {|line| p line } * @@ -11602,13 +11966,13 @@ io_s_foreach(VALUE v) * With arguments +sep+ and +limit+ given, * parses lines as determined by the given * line separator and the given line-length limit - * (see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit]): + * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]): * * Optional keyword arguments +opts+ specify: * * - {Open Options}[rdoc-ref:IO@Open+Options]. * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options]. - * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]. + * - {Line Options}[rdoc-ref:IO@Line+Options]. * * Returns an Enumerator if no block is given. * @@ -11678,7 +12042,7 @@ io_s_readlines(VALUE v) * For both forms, command and path, the remaining arguments are the same. * * With argument +sep+ given, parses lines as determined by that line separator - * (see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]): + * (see {Line Separator}[rdoc-ref:IO@Line+Separator]): * * # Ordinary separator. * IO.readlines('t.txt', 'li') @@ -11692,7 +12056,7 @@ io_s_readlines(VALUE v) * * With argument +limit+ given, parses lines as determined by the default * line separator and the given line-length limit - * (see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]): + * (see {Line Limit}[rdoc-ref:IO@Line+Limit]): * * IO.readlines('t.txt', 7) * # => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"] @@ -11700,13 +12064,13 @@ io_s_readlines(VALUE v) * With arguments +sep+ and +limit+ given, * parses lines as determined by the given * line separator and the given line-length limit - * (see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit]): + * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]): * * Optional keyword arguments +opts+ specify: * * - {Open Options}[rdoc-ref:IO@Open+Options]. * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options]. - * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]. + * - {Line Options}[rdoc-ref:IO@Line+Options]. * */ @@ -12074,7 +12438,7 @@ maygvl_copy_stream_continue_p(int has_gvl, struct copy_stream_struct *stp) return FALSE; } -struct wait_for_single_fd { +struct fiber_scheduler_wait_for_arguments { VALUE scheduler; rb_io_t *fptr; @@ -12084,11 +12448,11 @@ struct wait_for_single_fd { }; static void * -rb_thread_fiber_scheduler_wait_for(void * _args) +fiber_scheduler_wait_for(void * _arguments) { - struct wait_for_single_fd *args = (struct wait_for_single_fd *)_args; + struct fiber_scheduler_wait_for_arguments *arguments = (struct fiber_scheduler_wait_for_arguments *)_arguments; - args->result = rb_fiber_scheduler_io_wait(args->scheduler, args->fptr->self, INT2NUM(args->events), Qnil); + arguments->result = rb_fiber_scheduler_io_wait(arguments->scheduler, arguments->fptr->self, INT2NUM(arguments->events), RUBY_IO_TIMEOUT_DEFAULT); return NULL; } @@ -12098,12 +12462,12 @@ rb_thread_fiber_scheduler_wait_for(void * _args) STATIC_ASSERT(pollin_expected, POLLIN == RB_WAITFD_IN); STATIC_ASSERT(pollout_expected, POLLOUT == RB_WAITFD_OUT); static int -nogvl_wait_for(VALUE th, rb_io_t *fptr, short events) +nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout) { VALUE scheduler = rb_fiber_scheduler_current_for_thread(th); if (scheduler != Qnil) { - struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events}; - rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args); + struct fiber_scheduler_wait_for_arguments args = {.scheduler = scheduler, .fptr = fptr, .events = events}; + rb_thread_call_with_gvl(fiber_scheduler_wait_for, &args); return RTEST(args.result); } @@ -12115,22 +12479,32 @@ nogvl_wait_for(VALUE th, rb_io_t *fptr, short events) fds.fd = fd; fds.events = events; - return poll(&fds, 1, -1); + int timeout_milliseconds = -1; + + if (timeout) { + timeout_milliseconds = (int)(timeout->tv_sec * 1000) + (int)(timeout->tv_usec / 1000); + } + + return poll(&fds, 1, timeout_milliseconds); } #else /* !USE_POLL */ # define IOWAIT_SYSCALL "select" static int -nogvl_wait_for(VALUE th, rb_io_t *fptr, short events) +nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout) { VALUE scheduler = rb_fiber_scheduler_current_for_thread(th); if (scheduler != Qnil) { - struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events}; - rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args); + struct fiber_scheduler_wait_for_arguments args = {.scheduler = scheduler, .fptr = fptr, .events = events}; + rb_thread_call_with_gvl(fiber_scheduler_wait_for, &args); return RTEST(args.result); } int fd = fptr->fd; - if (fd == -1) return 0; + + if (fd == -1) { + errno = EBADF; + return -1; + } rb_fdset_t fds; int ret; @@ -12140,16 +12514,18 @@ nogvl_wait_for(VALUE th, rb_io_t *fptr, short events) switch (events) { case RB_WAITFD_IN: - ret = rb_fd_select(fd + 1, &fds, 0, 0, 0); + ret = rb_fd_select(fd + 1, &fds, 0, 0, timeout); break; case RB_WAITFD_OUT: - ret = rb_fd_select(fd + 1, 0, &fds, 0, 0); + ret = rb_fd_select(fd + 1, 0, &fds, 0, timeout); break; default: VM_UNREACHABLE(nogvl_wait_for); } rb_fd_term(&fds); + + // On timeout, this returns 0. return ret; } #endif /* !USE_POLL */ @@ -12164,7 +12540,7 @@ maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp) ret = RB_NUM2INT(rb_io_wait(stp->src, RB_INT2NUM(RUBY_IO_READABLE), Qnil)); } else { - ret = nogvl_wait_for(stp->th, stp->src_fptr, RB_WAITFD_IN); + ret = nogvl_wait_for(stp->th, stp->src_fptr, RB_WAITFD_IN, NULL); } } while (ret < 0 && maygvl_copy_stream_continue_p(has_gvl, stp)); @@ -12182,7 +12558,7 @@ nogvl_copy_stream_wait_write(struct copy_stream_struct *stp) int ret; do { - ret = nogvl_wait_for(stp->th, stp->dst_fptr, RB_WAITFD_OUT); + ret = nogvl_wait_for(stp->th, stp->dst_fptr, RB_WAITFD_OUT, NULL); } while (ret < 0 && maygvl_copy_stream_continue_p(0, stp)); if (ret < 0) { @@ -12533,7 +12909,7 @@ static ssize_t maygvl_read(int has_gvl, rb_io_t *fptr, void *buf, size_t count) { if (has_gvl) - return rb_read_internal(fptr, buf, count); + return rb_io_read_memory(fptr, buf, count); else return read(fptr->fd, buf, count); } @@ -12719,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; } @@ -13122,6 +13499,17 @@ global_argf_p(VALUE arg) return arg == argf; } +typedef VALUE (*argf_encoding_func)(VALUE io); + +static VALUE +argf_encoding(VALUE argf, argf_encoding_func func) +{ + if (!RTEST(ARGF.current_file)) { + return rb_enc_default_external(); + } + return func(rb_io_check_io(ARGF.current_file)); +} + /* * call-seq: * ARGF.external_encoding -> encoding @@ -13141,10 +13529,7 @@ global_argf_p(VALUE arg) static VALUE argf_external_encoding(VALUE argf) { - if (!RTEST(ARGF.current_file)) { - return rb_enc_from_encoding(rb_default_external_encoding()); - } - return rb_io_external_encoding(rb_io_check_io(ARGF.current_file)); + return argf_encoding(argf, rb_io_external_encoding); } /* @@ -13163,10 +13548,7 @@ argf_external_encoding(VALUE argf) static VALUE argf_internal_encoding(VALUE argf) { - if (!RTEST(ARGF.current_file)) { - return rb_enc_from_encoding(rb_default_external_encoding()); - } - return rb_io_internal_encoding(rb_io_check_io(ARGF.current_file)); + return argf_encoding(argf, rb_io_internal_encoding); } /* @@ -13758,7 +14140,7 @@ static void argf_block_call(ID mid, int argc, VALUE *argv, VALUE argf) { VALUE ret = ARGF_block_call(mid, argc, argv, argf_block_call_i, argf); - if (ret != Qundef) ARGF.next_p = 1; + if (!UNDEF_P(ret)) ARGF.next_p = 1; } static VALUE @@ -13774,7 +14156,7 @@ static void argf_block_call_line(ID mid, int argc, VALUE *argv, VALUE argf) { VALUE ret = ARGF_block_call(mid, argc, argv, argf_block_call_line_i, argf); - if (ret != Qundef) ARGF.next_p = 1; + if (!UNDEF_P(ret)) ARGF.next_p = 1; } /* @@ -14407,10 +14789,10 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * * - A position, which determines where in the stream the next * read or write is to occur; - * see {Position}[rdoc-ref:io_streams.rdoc@Position]. + * see {Position}[rdoc-ref:IO@Position]. * - A line number, which is a special, line-oriented, "position" * (different from the position mentioned above); - * see {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number]. + * see {Line Number}[rdoc-ref:IO@Line+Number]. * * == Extension <tt>io/console</tt> * @@ -14441,10 +14823,332 @@ 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. * + * == Basic \IO + * + * You can perform basic stream \IO with these methods, + * which typically operate on multi-byte strings: + * + * - IO#read: Reads and returns some or all of the remaining bytes from the stream. + * - IO#write: Writes zero or more strings to the stream; + * each given object that is not already a string is converted via +to_s+. + * + * === Position + * + * An \IO stream has a nonnegative integer _position_, + * which is the byte offset at which the next read or write is to occur. + * A new stream has position zero (and line number zero); + * method +rewind+ resets the position (and line number) to zero. + * + * The relevant methods: + * + * - IO#tell (aliased as +#pos+): Returns the current position (in bytes) in the stream. + * - IO#pos=: Sets the position of the stream to a given integer +new_position+ (in bytes). + * - IO#seek: Sets the position of the stream to a given integer +offset+ (in bytes), + * relative to a given position +whence+ + * (indicating the beginning, end, or current position). + * - IO#rewind: Positions the stream at the beginning (also resetting the line number). + * + * === Open and Closed Streams + * + * A new \IO stream may be open for reading, open for writing, or both. + * + * A stream is automatically closed when claimed by the garbage collector. + * + * Attempted reading or writing on a closed stream raises an exception. + * + * The relevant methods: + * + * - IO#close: Closes the stream for both reading and writing. + * - IO#close_read: Closes the stream for reading. + * - IO#close_write: Closes the stream for writing. + * - IO#closed?: Returns whether the stream is closed. + * + * === End-of-Stream + * + * You can query whether a stream is positioned at its end: + * + * - IO#eof? (also aliased as +#eof+): Returns whether the stream is at end-of-stream. + * + * You can reposition to end-of-stream by using method IO#seek: + * + * f = File.new('t.txt') + * f.eof? # => false + * f.seek(0, :END) + * f.eof? # => true + * f.close + * + * Or by reading all stream content (which is slower than using IO#seek): + * + * f.rewind + * f.eof? # => false + * f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n" + * f.eof? # => true + * + * == Line \IO + * + * You can read an \IO stream line-by-line using these methods: + * + * - IO#each_line: Reads each remaining line, passing it to the given block. + * - IO#gets: Returns the next line. + * - IO#readline: Like #gets, but raises an exception at end-of-stream. + * - IO#readlines: Returns all remaining lines in an array. + * + * Each of these reader methods accepts: + * + * - An optional line separator, +sep+; + * see {Line Separator}[rdoc-ref:IO@Line+Separator]. + * - An optional line-size limit, +limit+; + * see {Line Limit}[rdoc-ref:IO@Line+Limit]. + * + * For each of these reader methods, reading may begin mid-line, + * depending on the stream's position; + * see {Position}[rdoc-ref:IO@Position]: + * + * f = File.new('t.txt') + * f.pos = 27 + * f.each_line {|line| p line } + * f.close + * + * Output: + * + * "rth line\n" + * "Fifth line\n" + * + * You can write to an \IO stream line-by-line using this method: + * + * - IO#puts: Writes objects to the stream. + * + * === Line Separator + * + * Each of these methods uses a <i>line separator</i>, + * which is the string that delimits lines: + * + * - IO.foreach. + * - IO.readlines. + * - IO#each_line. + * - IO#gets. + * - IO#readline. + * - IO#readlines. + * + * The default line separator is the given by the global variable <tt>$/</tt>, + * whose value is by default <tt>"\n"</tt>. + * The line to be read next is all data from the current position + * to the next line separator: + * + * f = File.new('t.txt') + * f.gets # => "First line\n" + * f.gets # => "Second line\n" + * f.gets # => "\n" + * f.gets # => "Fourth line\n" + * f.gets # => "Fifth line\n" + * f.close + * + * You can specify a different line separator: + * + * f = File.new('t.txt') + * f.gets('l') # => "First l" + * f.gets('li') # => "ine\nSecond li" + * f.gets('lin') # => "ne\n\nFourth lin" + * f.gets # => "e\n" + * f.close + * + * There are two special line separators: + * + * - +nil+: The entire stream is read into a single string: + * + * f = File.new('t.txt') + * f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" + * f.close + * + * - <tt>''</tt> (the empty string): The next "paragraph" is read + * (paragraphs being separated by two consecutive line separators): + * + * f = File.new('t.txt') + * f.gets('') # => "First line\nSecond line\n\n" + * f.gets('') # => "Fourth line\nFifth line\n" + * f.close + * + * === Line Limit + * + * Each of these methods uses a <i>line limit</i>, + * which specifies that the number of bytes returned may not be (much) longer + * than the given +limit+; + * + * - IO.foreach. + * - IO.readlines. + * - IO#each_line. + * - IO#gets. + * - IO#readline. + * - IO#readlines. + * + * A multi-byte character will not be split, and so a line may be slightly longer + * than the given limit. + * + * If +limit+ is not given, the line is determined only by +sep+. + * + * # Text with 1-byte characters. + * File.open('t.txt') {|f| f.gets(1) } # => "F" + * File.open('t.txt') {|f| f.gets(2) } # => "Fi" + * File.open('t.txt') {|f| f.gets(3) } # => "Fir" + * File.open('t.txt') {|f| f.gets(4) } # => "Firs" + * # No more than one line. + * File.open('t.txt') {|f| f.gets(10) } # => "First line" + * File.open('t.txt') {|f| f.gets(11) } # => "First line\n" + * File.open('t.txt') {|f| f.gets(12) } # => "First line\n" + * + * # Text with 2-byte characters, which will not be split. + * File.open('t.rus') {|f| f.gets(1).size } # => 1 + * File.open('t.rus') {|f| f.gets(2).size } # => 1 + * File.open('t.rus') {|f| f.gets(3).size } # => 2 + * File.open('t.rus') {|f| f.gets(4).size } # => 2 + * + * === Line Separator and Line Limit + * + * With arguments +sep+ and +limit+ given, + * combines the two behaviors: + * + * - Returns the next line as determined by line separator +sep+. + * - But returns no more bytes than are allowed by the limit. + * + * Example: + * + * File.open('t.txt') {|f| f.gets('li', 20) } # => "First li" + * File.open('t.txt') {|f| f.gets('li', 2) } # => "Fi" + * + * === Line Number + * + * A readable \IO stream has a non-negative integer <i>line number</i>. + * + * The relevant methods: + * + * - IO#lineno: Returns the line number. + * - IO#lineno=: Resets and returns the line number. + * + * Unless modified by a call to method IO#lineno=, + * the line number is the number of lines read + * by certain line-oriented methods, + * according to the given line separator +sep+: + * + * - IO.foreach: Increments the line number on each call to the block. + * - IO#each_line: Increments the line number on each call to the block. + * - IO#gets: Increments the line number. + * - IO#readline: Increments the line number. + * - IO#readlines: Increments the line number for each line read. + * + * A new stream is initially has line number zero (and position zero); + * method +rewind+ resets the line number (and position) to zero: + * + * f = File.new('t.txt') + * f.lineno # => 0 + * f.gets # => "First line\n" + * f.lineno # => 1 + * f.rewind + * f.lineno # => 0 + * f.close + * + * Reading lines from a stream usually changes its line number: + * + * f = File.new('t.txt', 'r') + * f.lineno # => 0 + * f.readline # => "This is line one.\n" + * f.lineno # => 1 + * f.readline # => "This is the second line.\n" + * f.lineno # => 2 + * f.readline # => "Here's the third line.\n" + * f.lineno # => 3 + * f.eof? # => true + * f.close + * + * Iterating over lines in a stream usually changes its line number: + * + * File.open('t.txt') do |f| + * f.each_line do |line| + * p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}" + * end + * end + * + * Output: + * + * "position=11 eof?=false lineno=1" + * "position=23 eof?=false lineno=2" + * "position=24 eof?=false lineno=3" + * "position=36 eof?=false lineno=4" + * "position=47 eof?=true lineno=5" + * + * Unlike the stream's {position}[rdoc-ref:IO@Position], + * the line number does not affect where the next read or write will occur: + * + * f = File.new('t.txt') + * f.lineno = 1000 + * f.lineno # => 1000 + * f.gets # => "First line\n" + * f.lineno # => 1001 + * f.close + * + * Associated with the line number is the global variable <tt>$.</tt>: + * + * - When a stream is opened, <tt>$.</tt> is not set; + * its value is left over from previous activity in the process: + * + * $. = 41 + * f = File.new('t.txt') + * $. = 41 + * # => 41 + * f.close + * + * - When a stream is read, <tt>#.</tt> is set to the line number for that stream: + * + * f0 = File.new('t.txt') + * f1 = File.new('t.dat') + * f0.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] + * $. # => 5 + * f1.readlines # => ["\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"] + * $. # => 1 + * f0.close + * f1.close + * + * - Methods IO#rewind and IO#seek do not affect <tt>$.</tt>: + * + * f = File.new('t.txt') + * f.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] + * $. # => 5 + * f.rewind + * f.seek(0, :SET) + * $. # => 5 + * f.close + * + * == Character \IO + * + * You can process an \IO stream character-by-character using these methods: + * + * - IO#getc: Reads and returns the next character from the stream. + * - IO#readchar: Like #getc, but raises an exception at end-of-stream. + * - IO#ungetc: Pushes back ("unshifts") a character or integer onto the stream. + * - IO#putc: Writes a character to the stream. + * - IO#each_char: Reads each remaining character in the stream, + * passing the character to the given block. + * == Byte \IO + * + * You can process an \IO stream byte-by-byte using these methods: + * + * - IO#getbyte: Returns the next 8-bit byte as an integer in range 0..255. + * - IO#readbyte: Like #getbyte, but raises an exception if at end-of-stream. + * - IO#ungetbyte: Pushes back ("unshifts") a byte back onto the stream. + * - IO#each_byte: Reads each remaining byte in the stream, + * passing the byte to the given block. + * + * == Codepoint \IO + * + * You can process an \IO stream codepoint-by-codepoint: + * + * - IO#each_codepoint: Reads each remaining codepoint, passing it to the given block. + * * == What's Here * * First, what's elsewhere. \Class \IO: @@ -14492,11 +15196,11 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - #read_nonblock: the next _n_ bytes read from +self+ for a given _n_, * in non-block mode. * - #readbyte: Returns the next byte read from +self+; - * same as #getbyte, but raises an exception on end-of-file. + * same as #getbyte, but raises an exception on end-of-stream. * - #readchar: Returns the next character read from +self+; - * same as #getc, but raises an exception on end-of-file. + * same as #getc, but raises an exception on end-of-stream. * - #readline: Returns the next line read from +self+; - * same as #getline, but raises an exception of end-of-file. + * same as #getline, but raises an exception of end-of-stream. * - #readlines: Returns an array of all lines read read from +self+. * - #readpartial: Returns up to the given number of bytes from +self+. * @@ -14556,7 +15260,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - #binmode?: Returns whether +self+ is in binary mode. * - #close_on_exec?: Returns the close-on-exec flag for +self+. * - #closed?: Returns whether +self+ is closed. - * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-file. + * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-stream. * - #external_encoding: Returns the external encoding object for +self+. * - #fileno (aliased as #to_i): Returns the integer file descriptor for +self+ * - #internal_encoding: Returns the internal encoding object for +self+. @@ -14647,6 +15351,8 @@ Init_IO(void) rb_cIO = rb_define_class("IO", rb_cObject); rb_include_module(rb_cIO, rb_mEnumerable); + rb_eIOTimeoutError = rb_define_class_under(rb_cIO, "TimeoutError", rb_eIOError); + rb_define_const(rb_cIO, "READABLE", INT2NUM(RUBY_IO_READABLE)); rb_define_const(rb_cIO, "WRITABLE", INT2NUM(RUBY_IO_WRITABLE)); rb_define_const(rb_cIO, "PRIORITY", INT2NUM(RUBY_IO_PRIORITY)); @@ -14745,23 +15451,26 @@ Init_IO(void) rb_define_alias(rb_cIO, "to_i", "fileno"); rb_define_method(rb_cIO, "to_io", rb_io_to_io, 0); - rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0); - rb_define_method(rb_cIO, "fdatasync", rb_io_fdatasync, 0); - rb_define_method(rb_cIO, "sync", rb_io_sync, 0); - rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1); + rb_define_method(rb_cIO, "timeout", rb_io_timeout, 0); + rb_define_method(rb_cIO, "timeout=", rb_io_set_timeout, 1); + + rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0); + rb_define_method(rb_cIO, "fdatasync", rb_io_fdatasync, 0); + rb_define_method(rb_cIO, "sync", rb_io_sync, 0); + rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1); - rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0); - rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1); + rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0); + rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1); - rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); + rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); - rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); - rb_define_method(rb_cIO, "read", io_read, -1); + rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); + rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, -1); - rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); - rb_define_method(rb_cIO, "readline", rb_io_readline, -1); - rb_define_method(rb_cIO, "getc", rb_io_getc, 0); - rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0); + rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); + rb_define_method(rb_cIO, "readline", rb_io_readline, -1); + rb_define_method(rb_cIO, "getc", rb_io_getc, 0); + rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0); rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0); rb_define_method(rb_cIO, "readbyte", rb_io_readbyte, 0); rb_define_method(rb_cIO, "ungetbyte",rb_io_ungetbyte, 1); @@ -14808,6 +15517,10 @@ Init_IO(void) rb_define_method(rb_cIO, "ioctl", rb_io_ioctl, -1); rb_define_method(rb_cIO, "fcntl", rb_io_fcntl, -1); rb_define_method(rb_cIO, "pid", rb_io_pid, 0); + + rb_define_method(rb_cIO, "path", rb_io_path, 0); + rb_define_method(rb_cIO, "to_path", rb_io_path, 0); + rb_define_method(rb_cIO, "inspect", rb_io_inspect, 0); rb_define_method(rb_cIO, "external_encoding", rb_io_external_encoding, 0); @@ -14834,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 4326d21def..87b51c0b8c 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -14,6 +14,7 @@ #include "internal/array.h" #include "internal/bits.h" #include "internal/error.h" +#include "internal/numeric.h" #include "internal/string.h" #include "internal/thread.h" @@ -46,7 +47,7 @@ struct rb_io_buffer { }; static inline void * -io_buffer_map_memory(size_t size) +io_buffer_map_memory(size_t size, int flags) { #if defined(_WIN32) void * base = VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); @@ -55,7 +56,15 @@ io_buffer_map_memory(size_t size) rb_sys_fail("io_buffer_map_memory:VirtualAlloc"); } #else - void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + int mmap_flags = MAP_ANONYMOUS; + if (flags & RB_IO_BUFFER_SHARED) { + mmap_flags |= MAP_SHARED; + } + else { + mmap_flags |= MAP_PRIVATE; + } + + void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); if (base == MAP_FAILED) { rb_sys_fail("io_buffer_map_memory:mmap"); @@ -66,7 +75,7 @@ io_buffer_map_memory(size_t size) } 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); @@ -75,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; @@ -87,11 +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; + // 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); @@ -101,23 +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; + // This buffer refers to external buffer. + buffer->flags |= RB_IO_BUFFER_EXTERNAL; + buffer->flags |= RB_IO_BUFFER_SHARED; access |= MAP_SHARED; } @@ -128,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 @@ -161,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. @@ -183,7 +194,7 @@ io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb base = calloc(size, 1); } else if (flags & RB_IO_BUFFER_MAPPED) { - base = io_buffer_map_memory(size); + base = io_buffer_map_memory(size, flags); } if (!base) { @@ -195,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; } @@ -238,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; @@ -278,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; } @@ -311,6 +429,7 @@ struct io_buffer_for_yield_instance_arguments { VALUE klass; VALUE string; VALUE instance; + enum rb_io_buffer_flags flags; }; static VALUE @@ -318,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); } @@ -347,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) @@ -386,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); @@ -393,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); } } @@ -402,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; } @@ -417,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; } @@ -439,27 +560,29 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags * mapping, you need to open a file in read-write mode, and explicitly pass * +flags+ argument without IO::Buffer::IMMUTABLE. * - * File.write('test.txt', 'test') + * Example: + * + * File.write('test.txt', 'test') * - * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) - * # => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY> + * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) + * # => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY> * - * buffer.readonly? # => true + * buffer.readonly? # => true * - * buffer.get_string - * # => "test" + * buffer.get_string + * # => "test" * - * buffer.set_string('b', 0) - * # `set_string': Buffer is not writable! (IO::Buffer::AccessError) + * buffer.set_string('b', 0) + * # `set_string': Buffer is not writable! (IO::Buffer::AccessError) * - * # create read/write mapping: length 4 bytes, offset 0, flags 0 - * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) - * buffer.set_string('b', 0) - * # => 1 + * # create read/write mapping: length 4 bytes, offset 0, flags 0 + * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) + * buffer.set_string('b', 0) + * # => 1 * - * # Check it - * File.read('test.txt') - * # => "best" + * # Check it + * File.read('test.txt') + * # => "best" * * Note that some operating systems may not have cache coherency between mapped * buffers and file reads. @@ -467,16 +590,14 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags static VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass) { - if (argc < 1 || argc > 4) { - rb_error_arity(argc, 2, 4); - } + rb_check_arity(argc, 1, 4); // We might like to handle a string path? VALUE io = argv[0]; 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); @@ -495,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]); @@ -525,7 +647,7 @@ io_flags_for_size(size_t size) * Create a new zero-filled IO::Buffer of +size+ bytes. * By default, the buffer will be _internal_: directly allocated chunk * of the memory. But if the requested +size+ is more than OS-specific - * IO::Bufer::PAGE_SIZE, the buffer would be allocated using the + * IO::Buffer::PAGE_SIZE, the buffer would be allocated using the * virtual memory mechanism (anonymous +mmap+ on Unix, +VirtualAlloc+ * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED * as a second parameter. @@ -534,14 +656,14 @@ io_flags_for_size(size_t size) * * buffer = IO::Buffer.new(4) * # => - * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> - * # 0x00000000 00 00 00 00 .... + * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> + * # 0x00000000 00 00 00 00 .... * * buffer.get_string(0, 1) # => "\x00" * * buffer.set_string("test") * buffer - * # => + * # => * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> * # 0x00000000 74 65 73 74 test */ @@ -550,17 +672,14 @@ rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) { io_buffer_experimental(); - if (argc < 0 || argc > 2) { - rb_error_arity(argc, 0, 2); - } + 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; @@ -574,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; } @@ -609,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; @@ -632,43 +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_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_SHARED) { + rb_str_cat2(result, " SHARED"); + } + + 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"); } @@ -718,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; @@ -735,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); } } @@ -759,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. @@ -776,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)); } /* @@ -791,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); } /* @@ -807,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); } /* @@ -827,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); } /* @@ -852,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); } /* @@ -874,10 +997,26 @@ 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(buffer->flags & RB_IO_BUFFER_MAPPED); +} + +/* + * call-seq: shared? -> true or false + * + * If the buffer is _shared_, meaning it references memory that can be shared + * with other processes (and thus might change without being modified + * locally). + */ +static VALUE +rb_io_buffer_shared_p(VALUE self) +{ + 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_SHARED); } /* @@ -890,6 +1029,8 @@ rb_io_buffer_mapped_p(VALUE self) * Locking is not thread safe, but is a semantic used to ensure buffers don't * move while being used by a system call. * + * Example: + * * buffer.locked do * buffer.write(io) # theoretical system call interface * end @@ -897,19 +1038,19 @@ rb_io_buffer_mapped_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; } /* @@ -926,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); + + return self; +} - if (!(data->flags & RB_IO_BUFFER_LOCKED)) { +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; } @@ -959,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; } @@ -978,6 +1131,14 @@ rb_io_buffer_try_unlock(VALUE self) * can enter the lock. Also, locked buffer can't be changed with #resize or * #free. * + * The following operations acquire a lock: #resize, #free. + * + * Locking is not thread safe. It is designed as a safety net around + * non-blocking system calls. You can only share a buffer between threads with + * appropriate synchronisation techniques. + * + * Example: + * * buffer = IO::Buffer.new(4) * buffer.locked? #=> false * @@ -993,28 +1154,22 @@ rb_io_buffer_try_unlock(VALUE self) * buffer.set_string("test", 0) * end * end - * - * The following operations acquire a lock: #resize, #free. - * - * Locking is not thread safe. It is designed as a safety net around - * non-blocking system calls. You can only share a buffer between threads with - * appropriate synchronisation techniques. */ 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; } @@ -1029,46 +1184,73 @@ rb_io_buffer_locked(VALUE self) * * After the buffer is freed, no further operations can't be performed on it. * - * buffer = IO::Buffer.for('test') - * buffer.free - * # => #<IO::Buffer 0x0000000000000000+0 NULL> + * You can resize a freed buffer to re-allocate it. + * + * Example: * - * buffer.get_value(:U8, 0) - * # in `get_value': The buffer is not allocated! (IO::Buffer::AllocationError) + * buffer = IO::Buffer.for('test') + * buffer.free + * # => #<IO::Buffer 0x0000000000000000+0 NULL> * - * buffer.get_string - * # in `get_string': The buffer is not allocated! (IO::Buffer::AllocationError) + * buffer.get_value(:U8, 0) + * # in `get_value': The buffer is not allocated! (IO::Buffer::AllocationError) * - * buffer.null? - * # => true + * buffer.get_string + * # in `get_string': The buffer is not allocated! (IO::Buffer::AllocationError) * - * You can resize a freed buffer to re-allocate it. + * buffer.null? + * # => true */ 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 *buffer, VALUE self, size_t offset, size_t 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->flags |= (buffer->flags & RB_IO_BUFFER_READONLY); + slice->base = (char*)buffer->base + offset; + slice->size = length; + + // The source should be the root buffer: + if (buffer->source != Qnil) + slice->source = buffer->source; + else + slice->source = self; + + return instance; +} + /* - * call-seq: slice(offset, 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. @@ -1076,75 +1258,76 @@ io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length * The slicing happens without copying of memory, and the slice keeps being * associated with the original buffer's source (string, or file), if any. * - * Raises RuntimeError if the <tt>offset+length<tt> is out of the current + * If the offset is not given, it will be zero. If the offset is negative, it + * will raise an ArgumentError. + * + * If the length is not given, the slice will be as long as the original + * buffer minus the specified offset. If the length is negative, it will raise + * an ArgumentError. + * + * Raises RuntimeError if the <tt>offset+length</tt> is out of the current * buffer's bounds. * - * string = 'test' - * buffer = IO::Buffer.for(string) + * Example: * - * slice = buffer.slice(1, 2) - * # => - * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> - * # 0x00000000 65 73 es + * string = 'test' + * buffer = IO::Buffer.for(string) * - * # Put "o" into 0s position of the slice - * slice.set_string('o', 0) - * slice - * # => - * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> - * # 0x00000000 6f 73 os + * slice = buffer.slice + * # => + * # #<IO::Buffer 0x0000000108338e68+4 SLICE> + * # 0x00000000 74 65 73 74 test + * + * buffer.slice(2) + * # => + * # #<IO::Buffer 0x0000000108338e6a+2 SLICE> + * # 0x00000000 73 74 st + * + * slice = buffer.slice(1, 2) + * # => + * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> + * # 0x00000000 65 73 es * + * # Put "o" into 0s position of the slice + * slice.set_string('o', 0) + * slice + * # => + * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> + * # 0x00000000 6f 73 os * - * # it is also visible at position 1 of the original buffer - * buffer - * # => - * # #<IO::Buffer 0x00007fc3d31e2d80+4 SLICE> - * # 0x00000000 74 6f 73 74 tost + * # it is also visible at position 1 of the original buffer + * buffer + * # => + * # #<IO::Buffer 0x00007fc3d31e2d80+4 SLICE> + * # 0x00000000 74 6f 73 74 tost * - * # ...and original string - * string - * # => tost + * # ...and original string + * string + * # => tost */ -VALUE -rb_io_buffer_slice(VALUE self, VALUE _offset, VALUE _length) +static VALUE +io_buffer_slice(int argc, VALUE *argv, VALUE self) { - // TODO fail on negative offets/lengths. - size_t offset = NUM2SIZET(_offset); - size_t length = NUM2SIZET(_length); + 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); - io_buffer_validate_range(data, 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->size = length; - - // The source should be the root buffer: - if (data->source != Qnil) - slice->source = data->source; - else - slice->source = self; - - return instance; + 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; } } @@ -1154,20 +1337,21 @@ rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) return 0; } -inline static void -io_buffer_get_bytes_for_writing(struct rb_io_buffer *data, void **base, size_t *size) +static inline void +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; } @@ -1178,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; } @@ -1204,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); } /* @@ -1215,25 +1399,27 @@ rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) * * Transfers ownership to a new buffer, deallocating the current one. * - * buffer = IO::Buffer.new('test') - * other = buffer.transfer - * other - * # => - * # #<IO::Buffer 0x00007f136a15f7b0+4 SLICE> - * # 0x00000000 74 65 73 74 test - * buffer - * # => - * # #<IO::Buffer 0x0000000000000000+0 NULL> - * buffer.null? - * # => true + * Example: + * + * buffer = IO::Buffer.new('test') + * other = buffer.transfer + * other + * # => + * # #<IO::Buffer 0x00007f136a15f7b0+4 SLICE> + * # 0x00000000 74 65 73 74 test + * buffer + * # => + * # #<IO::Buffer 0x0000000000000000+0 NULL> + * buffer.null? + * # => true */ 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!"); } @@ -1241,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); } /* @@ -1339,7 +1530,7 @@ rb_io_buffer_resize(VALUE self, size_t size) * buffer = IO::Buffer.new(4) * buffer.set_string("test", 0) * buffer.resize(8) # resize to 8 bytes - * # => + * # => * # #<IO::Buffer 0x0000555f5d1a1630+8 INTERNAL> * # 0x00000000 74 65 73 74 00 00 00 00 test.... * @@ -1349,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; } @@ -1384,7 +1575,7 @@ static void io_buffer_validate_type(size_t size, size_t offset) { if (offset > size) { - rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%ld > size=%ld)", offset, size); + rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size); } } @@ -1487,8 +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) { -#define IO_BUFFER_DATA_TYPE_SIZE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE; +io_buffer_buffer_type_size(ID buffer_type) +{ +#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) @@ -1514,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: * @@ -1525,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) @@ -1570,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 @@ -1594,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: @@ -1610,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); @@ -1618,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: @@ -1630,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); } @@ -1653,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. @@ -1681,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); } @@ -1712,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. * @@ -1731,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); } @@ -1789,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; @@ -1811,10 +2007,10 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) return self; } -inline static void -rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offset, VALUE value) +static inline void +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); @@ -1849,13 +2045,15 @@ rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offs * symbols described in #get_value. * * buffer = IO::Buffer.new(8) - * # => + * # => * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 + * * buffer.set_value(:U8, 1, 111) * # => 1 + * * buffer - * # => + * # => * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> * # 0x00000000 00 6f 00 00 00 00 00 00 .o...... * @@ -1863,18 +2061,19 @@ rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offs * * buffer = IO::Buffer.new(8) * buffer.set_value(:U32, 0, 2.5) + * * buffer - * # => - * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> - * # 0x00000000 00 00 00 02 00 00 00 00 - * # ^^ the same as if we'd pass just integer 2 + * # => + * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> + * # 0x00000000 00 00 00 02 00 00 00 00 + * # ^^ the same as if we'd pass just integer 2 */ static VALUE 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); @@ -1884,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. * @@ -1895,33 +2094,33 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) * buffer = IO::Buffer.new(8) * buffer.set_values([:U8, :U16], 0, [1, 2]) * buffer - * # => + * # => * # #<IO::Buffer 0x696f717561746978+8 INTERNAL> * # 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); } @@ -1930,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); @@ -1947,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!"); @@ -1975,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); } @@ -2007,49 +2203,49 @@ 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) - * # => + * # => * # #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * # 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: * @@ -2067,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) { - if (argc < 1 || argc > 4) rb_error_arity(argc, 1, 4); + 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; @@ -2088,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); } /* @@ -2097,46 +2293,35 @@ io_buffer_copy(int argc, VALUE *argv, VALUE self) * Read a chunk or all of the buffer into a string, in the specified * +encoding+. If no encoding is provided +Encoding::BINARY+ is used. * - * buffer = IO::Buffer.for('test') - * buffer.get_string - * # => "test" - * buffer.get_string(2) - * # => "st" - * buffer.get_string(2, 1) - * # => "s" + * buffer = IO::Buffer.for('test') + * buffer.get_string + * # => "test" + * buffer.get_string(2) + * # => "st" + * buffer.get_string(2, 1) + * # => "s" */ static VALUE io_buffer_get_string(int argc, VALUE *argv, VALUE self) { - if (argc > 3) rb_error_arity(argc, 0, 3); + 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); } @@ -2144,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) @@ -2152,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 @@ -2167,30 +2352,30 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) static VALUE io_buffer_set_string(int argc, VALUE *argv, VALUE self) { - if (argc < 1 || argc > 4) rb_error_arity(argc, 1, 4); + 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); } @@ -2229,28 +2414,15 @@ rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length) static VALUE io_buffer_clear(int argc, VALUE *argv, VALUE self) { - if (argc > 3) rb_error_arity(argc, 0, 3); - - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + rb_check_arity(argc, 0, 3); 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); @@ -2282,56 +2454,166 @@ 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 -rb_io_buffer_read(VALUE self, VALUE io, size_t length) +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, length); + VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, length, offset); - if (result != Qundef) { + 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); + + 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]]) -> read length or -errno + * + * 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 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>read</tt> operation will occur. + * + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. + * + * 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(VALUE self, VALUE io, VALUE length) +io_buffer_read(int argc, VALUE *argv, VALUE self) { - return rb_io_buffer_read(self, io, RB_NUM2SIZE(length)); + rb_check_arity(argc, 1, 3); + + VALUE io = argv[0]; + + 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); } struct io_buffer_pread_internal_argument { @@ -2367,94 +2649,190 @@ io_buffer_pread_internal(void *_argument) } VALUE -rb_io_buffer_pread(VALUE self, VALUE io, size_t length, rb_off_t offset) +rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, self, length, offset); + VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, length, offset); - if (result != Qundef) { + 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, - .offset = offset, + + // 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(VALUE self, VALUE io, VALUE length, VALUE offset) +io_buffer_pread(int argc, VALUE *argv, VALUE self) { - return rb_io_buffer_pread(self, io, RB_NUM2SIZE(length), NUM2OFFT(offset)); + rb_check_arity(argc, 2, 4); + + VALUE io = argv[0]; + rb_off_t from = NUM2OFFT(argv[1]); + + 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 -rb_io_buffer_write(VALUE self, VALUE io, size_t length) +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, length); + VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, length, offset); - if (result != Qundef) { + 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); + + 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(VALUE self, VALUE io, VALUE length) +io_buffer_write(int argc, VALUE *argv, VALUE self) { - return rb_io_buffer_write(self, io, RB_NUM2SIZE(length)); + rb_check_arity(argc, 1, 3); + + VALUE io = argv[0]; + + 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); } struct io_buffer_pwrite_internal_argument { @@ -2490,42 +2868,72 @@ io_buffer_pwrite_internal(void *_argument) } VALUE -rb_io_buffer_pwrite(VALUE self, VALUE io, size_t length, rb_off_t offset) +rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, self, length, OFFT2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, length, offset); - if (result != Qundef) { + 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, - .offset = offset, + + // 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(VALUE self, VALUE io, VALUE length, VALUE offset) +io_buffer_pwrite(int argc, VALUE *argv, VALUE self) { - return rb_io_buffer_pwrite(self, io, RB_NUM2SIZE(length), NUM2OFFT(offset)); + rb_check_arity(argc, 2, 4); + + VALUE io = argv[0]; + rb_off_t from = NUM2OFFT(argv[1]); + + 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); } static inline void @@ -2558,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; } @@ -2598,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; } @@ -2638,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; } @@ -2678,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; } @@ -2704,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 @@ -2735,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; } @@ -2781,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; } @@ -2827,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; } @@ -2873,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); @@ -2891,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); @@ -2910,11 +3318,11 @@ io_buffer_not_inplace(VALUE self) * Empty buffer: * * buffer = IO::Buffer.new(8) # create empty 8-byte buffer - * # => + * # => * # #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL> * # ... * buffer - * # => + * # => * # <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 * buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2 @@ -2924,22 +3332,22 @@ io_buffer_not_inplace(VALUE self) * * \Buffer from string: * - * string = 'data' + * string = 'buffer' * buffer = IO::Buffer.for(string) - * # => + * # => * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> * # ... * 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" * buffer.set_string('---', 1) # write content, starting from offset 1 * # => 3 * buffer - * # => + * # => * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> * # 0x00000000 64 2d 2d 2d d--- * string # original string changed, too @@ -2947,10 +3355,10 @@ 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')) - * # => + * # => * # #<IO::Buffer 0x00007f3f0768c000+9 MAPPED IMMUTABLE> * # ... * buffer.get_string(5, 2) # read 2 bytes, starting from offset 5 @@ -2964,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> */ @@ -3013,6 +3421,7 @@ Init_IO_Buffer(void) rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)); rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL)); rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED)); + rb_define_const(rb_cIOBuffer, "SHARED", RB_INT2NUM(RB_IO_BUFFER_SHARED)); rb_define_const(rb_cIOBuffer, "LOCKED", RB_INT2NUM(RB_IO_BUFFER_LOCKED)); rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE)); rb_define_const(rb_cIOBuffer, "READONLY", RB_INT2NUM(RB_IO_BUFFER_READONLY)); @@ -3028,6 +3437,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "external?", rb_io_buffer_external_p, 0); rb_define_method(rb_cIOBuffer, "internal?", rb_io_buffer_internal_p, 0); rb_define_method(rb_cIOBuffer, "mapped?", rb_io_buffer_mapped_p, 0); + rb_define_method(rb_cIOBuffer, "shared?", rb_io_buffer_shared_p, 0); rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0); rb_define_method(rb_cIOBuffer, "readonly?", io_buffer_readonly_p, 0); @@ -3037,7 +3447,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "locked", rb_io_buffer_locked, 0); // Manipulation: - rb_define_method(rb_cIOBuffer, "slice", rb_io_buffer_slice, 2); + rb_define_method(rb_cIOBuffer, "slice", io_buffer_slice, -1); rb_define_method(rb_cIOBuffer, "<=>", rb_io_buffer_compare, 1); rb_define_method(rb_cIOBuffer, "resize", io_buffer_resize, 1); rb_define_method(rb_cIOBuffer, "clear", io_buffer_clear, -1); @@ -3086,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); @@ -3098,8 +3508,8 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "not!", io_buffer_not_inplace, 0); // IO operations: - rb_define_method(rb_cIOBuffer, "read", io_buffer_read, 2); - rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, 3); - rb_define_method(rb_cIOBuffer, "write", io_buffer_write, 2); - rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, 3); + rb_define_method(rb_cIOBuffer, "read", io_buffer_read, -1); + rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, -1); + rb_define_method(rb_cIOBuffer, "write", io_buffer_write, -1); + rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, -1); } @@ -103,7 +103,8 @@ compile_data_free(struct iseq_compile_data *compile_data) } static void -remove_from_constant_cache(ID id, IC ic) { +remove_from_constant_cache(ID id, IC ic) +{ rb_vm_t *vm = GET_VM(); VALUE lookup_result; st_data_t ic_data = (st_data_t)ic; @@ -112,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); } @@ -125,6 +128,14 @@ remove_from_constant_cache(ID id, IC ic) { static void iseq_clear_ic_references(const rb_iseq_t *iseq) { + // In some cases (when there is a compilation error), we end up with + // ic_size greater than 0, but no allocated is_entries buffer. + // If there's no is_entries buffer to loop through, return early. + // [Bug #19173] + if (!ISEQ_BODY(iseq)->is_entries) { + return; + } + for (unsigned int ic_idx = 0; ic_idx < ISEQ_BODY(iseq)->ic_size; ic_idx++) { IC ic = &ISEQ_IS_IC_ENTRY(ISEQ_BODY(iseq), ic_idx); @@ -178,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); } @@ -230,18 +245,8 @@ rb_iseq_each_value(const rb_iseq_t *iseq, iseq_value_itr_t * func, void *data) union iseq_inline_storage_entry *is_entries = body->is_entries; if (body->is_entries) { - // IVC entries - for (unsigned int i = 0; i < body->ivc_size; i++, is_entries++) { - IVC ivc = (IVC)is_entries; - if (ivc->entry) { - RUBY_ASSERT(!RB_TYPE_P(ivc->entry->class_value, T_NONE)); - - VALUE nv = func(data, ivc->entry->class_value); - if (ivc->entry->class_value != nv) { - ivc->entry->class_value = nv; - } - } - } + // Skip iterating over ivc caches + is_entries += body->ivc_size; // ICVARC entries for (unsigned int i = 0; i < body->icvarc_size; i++, is_entries++) { @@ -341,7 +346,7 @@ rb_iseq_update_references(rb_iseq_t *iseq) for (j = 0; i < body->param.keyword->num; i++, j++) { VALUE obj = body->param.keyword->default_values[j]; - if (obj != Qundef) { + if (!UNDEF_P(obj)) { body->param.keyword->default_values[j] = rb_gc_location(obj); } } @@ -516,15 +521,17 @@ rb_iseq_memsize(const rb_iseq_t *iseq) /* body->is_entries */ size += ISEQ_IS_SIZE(body) * sizeof(union iseq_inline_storage_entry); - /* IC entries constant segments */ - for (unsigned int ic_idx = 0; ic_idx < body->ic_size; ic_idx++) { - IC ic = &ISEQ_IS_IC_ENTRY(body, ic_idx); - const ID *ids = ic->segments; - if (!ids) continue; - while (*ids++) { - size += sizeof(ID); + if (ISEQ_BODY(iseq)->is_entries) { + /* IC entries constant segments */ + for (unsigned int ic_idx = 0; ic_idx < body->ic_size; ic_idx++) { + IC ic = &ISEQ_IS_IC_ENTRY(body, ic_idx); + const ID *ids = ic->segments; + if (!ids) continue; + while (*ids++) { + size += sizeof(ID); + } + size += sizeof(ID); // null terminator } - size += sizeof(ID); // null terminator } /* body->call_data */ @@ -590,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) { @@ -916,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); @@ -931,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); @@ -1445,6 +1474,9 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) f = rb_file_open_str(file, "r"); + rb_execution_context_t *ec = GET_EC(); + VALUE v = rb_vm_push_frame_fname(ec, file); + parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); ast = (rb_ast_t *)rb_parser_load_file(parser, file); @@ -1463,6 +1495,9 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) rb_realpath_internal(Qnil, file, 1), 1, NULL, 0, ISEQ_TYPE_TOP, &option)); rb_ast_dispose(ast); + + rb_vm_pop_frame(ec); + RB_GC_GUARD(v); return ret; } @@ -1552,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); } /* @@ -2495,6 +2534,34 @@ rb_iseq_disasm(const rb_iseq_t *iseq) } /* + * Estimates the number of instance variables that will be set on + * a given `class` with the initialize method defined in + * `initialize_iseq` + */ +attr_index_t +rb_estimate_iv_count(VALUE klass, const rb_iseq_t * initialize_iseq) +{ + struct rb_id_table * iv_names = rb_id_table_create(0); + + for (unsigned int i = 0; i < ISEQ_BODY(initialize_iseq)->ivc_size; i++) { + IVC cache = (IVC)&ISEQ_BODY(initialize_iseq)->is_entries[i]; + + if (cache->iv_set_name) { + rb_id_table_insert(iv_names, cache->iv_set_name, Qtrue); + } + } + + attr_index_t count = (attr_index_t)rb_id_table_size(iv_names); + + VALUE superclass = rb_class_superclass(klass); + count += RCLASS_EXT(superclass)->max_iv_count; + + rb_id_table_free(iv_names); + + return count; +} + +/* * call-seq: * iseq.disasm -> str * iseq.disassemble -> str @@ -2920,7 +2987,7 @@ iseq_data_to_ary(const rb_iseq_t *iseq) } for (j=0; i<keyword->num; i++, j++) { VALUE key = rb_ary_new_from_args(1, ID2SYM(keyword->table[i])); - if (keyword->default_values[j] != Qundef) { + if (!UNDEF_P(keyword->default_values[j])) { rb_ary_push(key, keyword->default_values[j]); } rb_ary_push(keywords, key); @@ -3323,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); @@ -31,6 +31,7 @@ RUBY_EXTERN const int ruby_api_version[]; typedef struct rb_iseq_struct rb_iseq_t; #define rb_iseq_t rb_iseq_t #endif +typedef void (*rb_iseq_callback)(const rb_iseq_t *, void *); extern const ID rb_iseq_shared_exc_local_tbl[]; @@ -113,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/benchmark/version.rb b/lib/benchmark/version.rb index 545575f4ab..645966fd80 100644 --- a/lib/benchmark/version.rb +++ b/lib/benchmark/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module Benchmark - VERSION = "0.2.0" + VERSION = "0.2.1" end diff --git a/lib/bundler.rb b/lib/bundler.rb index dc88bbdcb9..f83268e9cd 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -41,7 +41,6 @@ module Bundler autoload :Definition, File.expand_path("bundler/definition", __dir__) autoload :Dependency, File.expand_path("bundler/dependency", __dir__) - autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__) autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__) autoload :Digest, File.expand_path("bundler/digest", __dir__) autoload :Dsl, File.expand_path("bundler/dsl", __dir__) @@ -76,11 +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 :VersionRanges, File.expand_path("bundler/version_ranges", __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 @@ -210,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 @@ -455,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 @@ -498,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) @@ -513,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) @@ -525,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) @@ -552,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 @@ -574,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 @@ -607,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 3ba4d0f8c4..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>`." @@ -620,7 +627,7 @@ module Bundler method_option "dry-run", :type => :boolean, :default => false, :banner => "Only print out changes, do not clean gems" method_option "force", :type => :boolean, :default => false, :banner => - "Forces clean even if --path is not set" + "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application." def clean require_relative "cli/clean" Clean.new(options.dup).run @@ -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/check.rb b/lib/bundler/cli/check.rb index 65c51337d2..cc1f37f0c3 100644 --- a/lib/bundler/cli/check.rb +++ b/lib/bundler/cli/check.rb @@ -17,7 +17,7 @@ module Bundler begin definition.resolve_only_locally! not_installed = definition.missing_specs - rescue GemNotFound, VersionConflict + rescue GemNotFound, SolveFailure Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies." Bundler.ui.warn "Install missing gems with `bundle install`." exit 1 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 e4f8229c48..246b9d6460 100644 --- a/lib/bundler/cli/init.rb +++ b/lib/bundler/cli/init.rb @@ -32,7 +32,11 @@ module Bundler file << spec.to_gemfile end else - FileUtils.cp(File.expand_path("../templates/#{gemfile}", __dir__), gemfile) + File.open(File.expand_path("../templates/Gemfile", __dir__), "r") do |template| + File.open(gemfile, "wb") do |destination| + IO.copy_stream(template, destination) + end + end end puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}" @@ -41,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 7d613a6644..cb3ed27138 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -15,19 +15,22 @@ module Bundler end print = options[:print] - ui = Bundler.ui - Bundler.ui = UI::Silent.new if print + previous_ui_level = Bundler.ui.level + Bundler.ui.level = "silent" if print Bundler::Fetcher.disable_endpoint = options["full-index"] 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) @@ -61,7 +64,7 @@ module Bundler definition.lock(file) end - Bundler.ui = ui + Bundler.ui.level = previous_ui_level end end end 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 a46d7387de..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 @@ -139,18 +142,18 @@ module Bundler if @unlock[:conservative] @unlock[:gems] ||= @dependencies.map(&:name) else - eager_unlock = expand_dependencies(@unlock[:gems] || [], true) - @unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name) + eager_unlock = (@unlock[:gems] || []).map {|name| Dependency.new(name, ">= 0") } + @unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq end @dependency_changes = converge_dependencies @local_changes = converge_locals - @requires = compute_requires + @missing_lockfile_dep = check_missing_lockfile_dep end def gem_version_promoter - @gem_version_promoter ||= GemVersionPromoter.new(@originally_locked_specs, @unlock[:gems]) + @gem_version_promoter ||= GemVersionPromoter.new end def resolve_only_locally! @@ -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,8 +237,16 @@ 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(@platforms).empty? + d.should_include? && !d.gem_platforms([generic_local_platform]).empty? end end @@ -248,10 +270,9 @@ module Bundler def dependencies_for(groups) groups.map!(&:to_sym) - deps = current_dependencies.reject do |d| + current_dependencies.reject do |d| (d.groups & groups).empty? end - expand_dependencies(deps) end # Resolve all the dependencies specified in Gemfile. It ensures that @@ -263,21 +284,21 @@ 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}") - resolver.start(expanded_dependencies) + Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}" + start_resolution end end @@ -296,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 = Gem::Version.create(Bundler::VERSION).segments.first + current_major = bundler_version_to_lock.segments.first updating_major = locked_major < current_major end @@ -340,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}`." - end - added = [] deleted = [] changed = [] @@ -374,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 @@ -448,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 @@ -462,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? @@ -472,19 +492,31 @@ module Bundler private def resolver - @resolver ||= begin - last_resolve = converge_locked_specs - remove_ruby_from_platforms_if_necessary!(dependencies) - Resolver.new(source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve(last_resolve), platforms) - end + @resolver ||= Resolver.new(resolution_packages, gem_version_promoter) end def expanded_dependencies - @expanded_dependencies ||= expand_dependencies(dependencies + metadata_dependencies, true) + dependencies_with_bundler + metadata_dependencies + end + + def dependencies_with_bundler + return dependencies unless @unlocking_bundler + return dependencies if dependencies.map(&:name).include?("bundler") + + [Dependency.new("bundler", @unlocking_bundler)] + dependencies + end + + 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 def filter_specs(specs, deps) - SpecSet.new(specs).for(expand_dependencies(deps, true), false, platforms) + SpecSet.new(specs).for(deps, false, platforms) end def materialize(dependencies) @@ -508,23 +540,42 @@ module Bundler raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}" end + incomplete_specs = specs.incomplete_specs loop do - incomplete_specs = specs.incomplete_specs 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 = resolver.start(expanded_dependencies, :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.get_package(incomplete_specs.first.name) + resolver.raise_not_found! package + end + + incomplete_specs = still_incomplete_specs end - bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last + bundler = sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last specs["bundler"] = bundler specs end + 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) @@ -554,6 +605,8 @@ module Bundler end def add_current_platform + return if current_ruby_platform_locked? + add_platform(local_platform) end @@ -575,11 +628,13 @@ 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 - def pretty_dep(dep, source = false) - SharedHelpers.pretty_dependency(dep, source) + def pretty_dep(dep) + SharedHelpers.pretty_dependency(dep) end # Check if the specs of the given source changed @@ -616,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 @@ -629,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) @@ -682,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 @@ -728,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) @@ -756,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 } @@ -767,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 @@ -792,23 +871,6 @@ module Bundler ] end - def expand_dependencies(dependencies, remote = false) - deps = [] - dependencies.each do |dep| - dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name) - next unless remote || dep.current_platform? - target_platforms = dep.gem_platforms(remote ? @platforms : [generic_local_platform]) - deps += expand_dependency_with_platforms(dep, target_platforms) - end - deps - end - - def expand_dependency_with_platforms(dep, platforms) - platforms.map do |p| - DepProxy.get_proxy(dep, p) - end - end - def source_requirements # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to @@ -816,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 @@ -824,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? @@ -848,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 @@ -857,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) @@ -880,7 +946,9 @@ module Bundler Bundler.local_platform == Gem::Platform::RUBY || !platforms.include?(Gem::Platform::RUBY) || (@new_platform && platforms.last == Gem::Platform::RUBY) || - !@originally_locked_specs.incomplete_ruby_specs?(expand_dependencies(dependencies)) + @path_changes || + @dependency_changes || + !@originally_locked_specs.incomplete_ruby_specs?(dependencies) remove_platform(Gem::Platform::RUBY) add_current_platform diff --git a/lib/bundler/dep_proxy.rb b/lib/bundler/dep_proxy.rb deleted file mode 100644 index a32dc37b49..0000000000 --- a/lib/bundler/dep_proxy.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class DepProxy - attr_reader :__platform, :dep - - @proxies = {} - - def self.get_proxy(dep, platform) - @proxies[[dep, platform]] ||= new(dep, platform).freeze - end - - def initialize(dep, platform) - @dep = dep - @__platform = platform - end - - private_class_method :new - - alias_method :eql?, :== - - def type - @dep.type - end - - def name - @dep.name - end - - def requirement - @dep.requirement - end - - def to_s - s = name.dup - s << " (#{requirement})" unless requirement == Gem::Requirement.default - s << " #{__platform}" unless __platform == Gem::Platform::RUBY - s - end - - def dup - raise NoMethodError.new("DepProxy cannot be duplicated") - end - - def clone - raise NoMethodError.new("DepProxy cannot be cloned") - end - - private - - def method_missing(*args, &blk) - @dep.send(*args, &blk) - end - end -end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 605000ba53..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,6 +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 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/env.rb b/lib/bundler/env.rb index 1763035a8a..7b1152930e 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -75,7 +75,7 @@ module Bundler end def self.git_version - Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version + Bundler::Source::Git::GitProxy.new(nil, nil).full_version rescue Bundler::Source::Git::GitNotInstalledError "not installed" end @@ -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 0f08e049d8..57013f5d50 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -2,11 +2,12 @@ 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 BUNDLER_VERSION + BUNDLER_SETUP GEM_HOME GEM_PATH MANPATH @@ -15,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/errors.rb b/lib/bundler/errors.rb index 15069065ff..5839fc6a73 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -21,16 +21,7 @@ module Bundler class InstallError < BundlerError; status_code(5); end # Internal error, should be rescued - class VersionConflict < BundlerError - attr_reader :conflicts - - def initialize(conflicts, msg = nil) - super(msg) - @conflicts = conflicts - end - - status_code(6) - end + class SolveFailure < BundlerError; status_code(6); end class GemNotFound < BundlerError; status_code(7); end class InstallHookError < BundlerError; status_code(8); end 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 e399a50cfd..2119799f68 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -29,9 +29,7 @@ module Bundler " is a chance you are experiencing a man-in-the-middle attack, but" \ " most likely your system doesn't have the CA certificates needed" \ " for verification. For information about OpenSSL certificates, see" \ - " https://railsapps.github.io/openssl-certificate-verify-failed.html." \ - " To connect without using SSL, edit your Gemfile" \ - " sources and change 'https' to 'http'." + " https://railsapps.github.io/openssl-certificate-verify-failed.html." end end @@ -39,9 +37,7 @@ module Bundler class SSLError < HTTPError def initialize(msg = nil) super msg || "Could not load OpenSSL.\n" \ - "You must recompile Ruby with OpenSSL support or change the sources in your " \ - "Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \ - "using RVM are available at rvm.io/packages/openssl." + "You must recompile Ruby with OpenSSL support." end end @@ -65,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, @@ -74,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 @@ -106,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_helpers.rb b/lib/bundler/gem_helpers.rb index 0d50d8687b..2e6d788f9c 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -5,7 +5,6 @@ module Bundler GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant GENERICS = [ [Gem::Platform.new("java"), Gem::Platform.new("java")], - [Gem::Platform.new("universal-java"), Gem::Platform.new("java")], [Gem::Platform.new("mswin32"), Gem::Platform.new("mswin32")], [Gem::Platform.new("mswin64"), Gem::Platform.new("mswin64")], [Gem::Platform.new("universal-mingw32"), Gem::Platform.new("universal-mingw32")], diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index ee2c38a6ec..d281f46eeb 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -7,14 +7,13 @@ module Bundler # available dependency versions as found in its index, before returning it to # to the resolution engine to select the best version. class GemVersionPromoter - DEBUG = ENV["BUNDLER_DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER"] - - attr_reader :level, :locked_specs, :unlock_gems + 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 @@ -24,24 +23,13 @@ module Bundler # existing in the referenced source. attr_accessor :strict - attr_accessor :prerelease_specified - - # Given a list of locked_specs and a list of gems to unlock creates a - # GemVersionPromoter instance. + # Creates a GemVersionPromoter instance. # - # @param locked_specs [SpecSet] All current locked specs. Unlike Definition - # where this list is empty if all gems are being updated, this should - # always be populated for all gems so this class can properly function. - # @param unlock_gems [String] List of gem names being unlocked. If empty, - # all gems will be considered unlocked. # @return [GemVersionPromoter] - def initialize(locked_specs = SpecSet.new([]), unlock_gems = []) + def initialize @level = :major @strict = false - @locked_specs = locked_specs - @unlock_gems = unlock_gems - @sort_versions = {} - @prerelease_specified = {} + @pre = false end # @param value [Symbol] One of three Symbols: :major, :minor or :patch. @@ -55,34 +43,19 @@ module Bundler @level = v end - # Given a Dependency and an Array of Specifications of available versions for a - # gem, this method will return the Array of Specifications sorted (and possibly - # truncated if strict is true) in an order to give preference to the current - # level (:major, :minor or :patch) when resolution is deciding what versions - # best resolve all dependencies in the bundle. - # @param dep [Dependency] The Dependency of the gem. - # @param spec_groups [Specification] An array of Specifications for the same gem - # named in the @dep param. + # Given a Resolver::Package and an Array of Specifications of available + # versions for a gem, this method will return the Array of Specifications + # sorted (and possibly truncated if strict is true) in an order to give + # preference to the current level (:major, :minor or :patch) when resolution + # is deciding what versions best resolve all dependencies in the bundle. + # @param package [Resolver::Package] The package being resolved. + # @param specs [Specification] An array of Specifications for the package. # @return [Specification] A new instance of the Specification Array sorted and # possibly filtered. - def sort_versions(dep, spec_groups) - @sort_versions[dep] ||= begin - gem_name = dep.name - - # An Array per version returned, different entries for different platforms. - # We only need the version here so it's ok to hard code this to the first instance. - locked_spec = locked_specs[gem_name].first + def sort_versions(package, specs) + specs = filter_dep_specs(specs, package) if strict - if strict - filter_dep_specs(spec_groups, locked_spec) - else - sort_dep_specs(spec_groups, locked_spec) - end - end - end - - def reset - @sort_versions = {} + sort_dep_specs(specs, package) end # @return [bool] Convenience method for testing value of level variable. @@ -95,79 +68,72 @@ 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(spec_groups, locked_spec) - res = spec_groups.select do |spec_group| - if locked_spec && !major? - gsv = spec_group.version - lsv = locked_spec.version + def filter_dep_specs(specs, package) + locked_version = package.locked_version + return specs if locked_version.nil? || major? - must_match = minor? ? [0] : [0, 1] + specs.select do |spec| + gsv = spec.version - matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] } - matches.uniq == [true] ? (gsv >= lsv) : false - else - true - end - end + must_match = minor? ? [0] : [0, 1] - sort_dep_specs(res, locked_spec) + all_match = must_match.all? {|idx| gsv.segments[idx] == locked_version.segments[idx] } + all_match && gsv >= locked_version + end end - def sort_dep_specs(spec_groups, locked_spec) - @locked_version = locked_spec&.version - @gem_name = locked_spec&.name - - result = spec_groups.sort do |a, b| - @a_ver = a.version - @b_ver = b.version + def sort_dep_specs(specs, package) + locked_version = package.locked_version - unless @gem_name && @prerelease_specified[@gem_name] - a_pre = @a_ver.prerelease? - b_pre = @b_ver.prerelease? + result = specs.sort do |a, b| + unless package.prerelease_specified? || pre? + a_pre = a.prerelease? + b_pre = b.prerelease? next -1 if a_pre && !b_pre next 1 if b_pre && !a_pre end if major? - @a_ver <=> @b_ver - elsif either_version_older_than_locked - @a_ver <=> @b_ver - elsif segments_do_not_match(:major) - @b_ver <=> @a_ver - elsif !minor? && segments_do_not_match(:minor) - @b_ver <=> @a_ver + a <=> b + elsif either_version_older_than_locked?(a, b, locked_version) + a <=> b + elsif segments_do_not_match?(a, b, :major) + b <=> a + elsif !minor? && segments_do_not_match?(a, b, :minor) + b <=> a else - @a_ver <=> @b_ver + a <=> b end end - post_sort(result) + post_sort(result, package.unlock?, locked_version) end - def either_version_older_than_locked - @locked_version && (@a_ver < @locked_version || @b_ver < @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(level) + def segments_do_not_match?(a, b, level) index = [:major, :minor].index(level) - @a_ver.segments[index] != @b_ver.segments[index] - end - - def unlocking_gem? - unlock_gems.empty? || (@gem_name && unlock_gems.include?(@gem_name)) + a.segments[index] != b.segments[index] end # Specific version moves can't always reliably be done during sorting # as not all elements are compared against each other. - def post_sort(result) + def post_sort(result, unlock, locked_version) # default :major behavior in Bundler does not do this return result if major? - if unlocking_gem? || @locked_version.nil? + if unlock || locked_version.nil? result else - move_version_to_end(result, @locked_version) + move_version_to_end(result, locked_version) end end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index d3743adb68..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 = [] @@ -70,8 +70,7 @@ module Bundler case query when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query) when String then specs_by_name(query) - when Gem::Dependency then search_by_dependency(query) - when DepProxy then search_by_dependency(query.dep) + when Array then specs_by_name_and_version(*query) else raise "You can't search for a #{query.inspect}." end @@ -158,20 +157,12 @@ module Bundler private - def specs_by_name(name) - @specs[name].values + def specs_by_name_and_version(name, version) + specs_by_name(name).select {|spec| spec.version == version } end - def search_by_dependency(dependency) - @cache[dependency] ||= begin - specs = specs_by_name(dependency.name) - found = specs.select do |spec| - next true if spec.source.is_a?(Source::Gemspec) - dependency.matches_spec?(spec) - end - - found - end + def specs_by_name(name) + @specs[name].values end EMPTY_SEARCH = [].freeze diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index 82d5bd5880..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) @@ -70,7 +70,7 @@ module Bundler show_warning("No gems were removed from the gemfile.") if deps.empty? - deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep, false)} was removed." } + deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep)} was removed." } end # Invalidate the cached Bundler.definition. @@ -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 25e055fbe4..5c184f67a1 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -31,15 +31,16 @@ # def gemfile(install = false, options = {}, &gemfile) require_relative "../bundler" + Bundler.reset! opts = options.dup ui = opts.delete(:ui) { Bundler::UI::Shell.new } - ui.level = "silent" if opts.delete(:quiet) + ui.level = "silent" if opts.delete(:quiet) || !install + 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? @@ -52,7 +53,6 @@ def gemfile(install = false, options = {}, &gemfile) def definition.lock(*); end definition.validate_runtime! - Bundler.ui = install ? ui : Bundler::UI::Silent.new if install || definition.missing_specs? Bundler.settings.temporary(:inline => true, :no_install => false) do installer = Bundler::Installer.install(Bundler.root, definition, :system => true) @@ -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 ec141cfa27..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,82 +79,71 @@ 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 : Bundler.local_platform + matching_specs = source.specs.search(use_exact_resolved_specifications? ? self : [name, version]) + return self if matching_specs.empty? - source.specs.search(Dependency.new(name, version)).select do |spec| - MatchPlatform.platforms_match?(spec.platform, target_platform) - end + 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?) + if target_platform != platform + installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform) 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)) - 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_string] - end - def git_version return unless source.is_a?(Bundler::Source::Git) " #{source.revision[0..6]}" end - protected - - def platform_string - platform_string = platform.to_s - platform_string == Index::RUBY ? Index::NULL : platform_string - end - 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 # # For backwards compatibility with existing lockfiles, if the most specific - # locked platform is RUBY, we keep the previous behaviour of resolving the + # locked platform is not a specific platform like x86_64-linux or + # universal-java-11, then we keep the previous behaviour of resolving the # best platform variant at materiliazation time. For previous bundler # versions (before 2.2.0) this was always the case (except when the lockfile # only included non-ruby platforms), but we're also keeping this behaviour @@ -159,7 +151,9 @@ module Bundler # explicitly add a more specific platform. # def ruby_platform_materializes_to_ruby_platform? - !Bundler.most_specific_locked_platform?(generic_local_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] + generic_platform = generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY + + !Bundler.most_specific_locked_platform?(generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] end end 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 fd49dd084f..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" "September 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 59bb6a4447..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" "September 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 4346aa00f8..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" "September 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 bb91ed90d5..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" "September 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 eaa8ea35fb..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" "September 2022" "" "" +.TH "BUNDLE\-CLEAN" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory @@ -20,5 +20,5 @@ Print the changes, but do not clean the unused gems\. . .TP \fB\-\-force\fR -Force a clean even if \fB\-\-path\fR is not set\. +Forces cleaning up unused gems even if Bundler is configured to use globally installed gems\. As a consequence, removes all system gems except for the ones in the current application\. diff --git a/lib/bundler/man/bundle-clean.1.ronn b/lib/bundler/man/bundle-clean.1.ronn index de23991782..dae27c21ee 100644 --- a/lib/bundler/man/bundle-clean.1.ronn +++ b/lib/bundler/man/bundle-clean.1.ronn @@ -15,4 +15,4 @@ useful when you have made many changes to your gem dependencies. * `--dry-run`: Print the changes, but do not clean the unused gems. * `--force`: - Force a clean even if `--path` is not set. + Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application. diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 1582e3c464..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" "September 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 6359f44231..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" "September 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 92f5c80df9..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" "September 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 158a9e0bf6..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" "September 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 2c36627559..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" "September 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 ed72024e06..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" "September 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 f1ef32b758..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" "September 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 83f8d75324..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" "September 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 d675dba79b..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" "September 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 858f56e673..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" "September 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 bf15769eaf..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" "September 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 af805f34d3..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" "September 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 f2b10b8808..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" "September 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 6994165838..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" "September 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 848c3024cd..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" "September 2022" "" "" +.TH "BUNDLE\-PLATFORM" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information @@ -65,7 +65,7 @@ It will display the ruby directive information, so you don\'t have to parse it f .SH "SEE ALSO" . .IP "\(bu" 4 -bundle\-lock(1) \fIbundle\-lock\.1\.ronn\fR +bundle\-lock(1) \fIbundle\-lock\.1\.html\fR . .IP "" 0 diff --git a/lib/bundler/man/bundle-platform.1.ronn b/lib/bundler/man/bundle-platform.1.ronn index eb9baa1c62..744acd1b43 100644 --- a/lib/bundler/man/bundle-platform.1.ronn +++ b/lib/bundler/man/bundle-platform.1.ronn @@ -46,4 +46,4 @@ match the running Ruby VM, it tells you what part does not. ## SEE ALSO -* [bundle-lock(1)](bundle-lock.1.ronn) +* [bundle-lock(1)](bundle-lock.1.html) diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 1508b85b38..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" "September 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 9a3a26bbfa..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" "September 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 f9d7d574d3..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" "September 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 ff860c64cc..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" "September 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 608ad74436..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" "September 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 68fc24c448..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" "September 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 4d108a2aea..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" "September 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 1898b15647..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" "September 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 e793500517..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" "September 2022" "" "" +.TH "GEMFILE" "5" "August 2023" "" "" . .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs @@ -73,13 +73,26 @@ Credentials in the source URL will take precedence over credentials set using \f If your application requires a specific Ruby version or engine, specify your requirements using the \fBruby\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\. . .SS "VERSION (required)" -The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this should be the Ruby version that the engine is compatible with\. +The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, TruffleRuby, etc\., this should be the Ruby version that the engine is compatible with\. . .IP "" 4 . .nf -ruby "1\.9\.3" +ruby "3\.1\.2" +. +.fi +. +.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 . @@ -95,7 +108,7 @@ What exactly is an Engine? \- A Ruby engine is an implementation of the Ruby lan For background: the reference or original implementation of the Ruby programming language is called Matz\'s Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\. . .IP "\(bu" 4 -Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include Rubinius \fIhttps://rubinius\.com/\fR, and JRuby \fIhttp://jruby\.org/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. +Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include JRuby \fIhttp://jruby\.org/\fR and TruffleRuby \fIhttps://www\.graalvm\.org/ruby/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM\. . .IP "" 0 . @@ -106,20 +119,23 @@ Each application \fImay\fR specify a Ruby engine version\. If an engine version . .nf -ruby "1\.8\.7", engine: "jruby", engine_version: "1\.6\.7" +ruby "2\.6\.8", engine: "jruby", engine_version: "9\.3\.8\.0" . .fi . .IP "" 0 . .SS "PATCHLEVEL" -Each application \fImay\fR specify a Ruby patchlevel\. +Each application \fImay\fR specify a Ruby patchlevel\. Specifying the patchlevel has been meaningless since Ruby 2\.1\.0 was released as the patchlevel is now uniquely determined by a combination of major, minor, and teeny version numbers\. +. +.P +This option was implemented in Bundler 1\.4\.0 for Ruby 2\.0 or earlier\. . .IP "" 4 . .nf -ruby "2\.0\.0", patchlevel: "247" +ruby "3\.1\.2", patchlevel: "20" . .fi . @@ -265,6 +281,14 @@ C Ruby (MRI) only, but not Windows Windows C Ruby (MRI), including RubyInstaller 32\-bit and 64\-bit versions . .TP +\fBmswin\fR +Windows C Ruby (MRI), including RubyInstaller 32\-bit versions +. +.TP +\fBmswin64\fR +Windows C Ruby (MRI), including RubyInstaller 64\-bit versions +. +.TP \fBrbx\fR Rubinius . @@ -277,13 +301,13 @@ JRuby TruffleRuby . .P -On platforms \fBruby\fR, \fBmri\fR, and \fBwindows\fR, you may additionally specify a version by appending the major and minor version numbers without a delimiter\. For example, to specify that a gem should only be used on platform \fBruby\fR version 2\.3, use: +On platforms \fBruby\fR, \fBmri\fR, \fBmswin\fR, \fBmswin64\fR, and \fBwindows\fR, you may additionally specify a version by appending the major and minor version numbers without a delimiter\. For example, to specify that a gem should only be used on platform \fBruby\fR version 3\.1, use: . .IP "" 4 . .nf -ruby_23 +ruby_31 . .fi . @@ -297,8 +321,8 @@ As with groups (above), you may specify one or more platforms: .nf gem "weakling", platforms: :jruby -gem "ruby\-debug", platforms: :mri_18 -gem "nokogiri", platforms: [:windows_26, :jruby] +gem "ruby\-debug", platforms: :mri_31 +gem "nokogiri", platforms: [:windows_31, :jruby] . .fi . @@ -497,7 +521,7 @@ Are both equivalent to . .nf -gem "rails", git: "git://github\.com/rails/rails\.git" +gem "rails", git: "https://github\.com/rails/rails\.git" . .fi . @@ -691,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 89ebcc7214..6749c33f59 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -64,10 +64,15 @@ All parameters are `OPTIONAL` unless otherwise specified. ### VERSION (required) The version of Ruby that your application requires. If your application -requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this +requires an alternate Ruby engine, such as JRuby, TruffleRuby, etc., this should be the Ruby version that the engine is compatible with. - ruby "1.9.3" + 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 @@ -86,9 +91,10 @@ What exactly is an Engine? - [Other implementations](https://www.ruby-lang.org/en/about/) of Ruby exist. Some of the more well-known implementations include - [Rubinius](https://rubinius.com/), and [JRuby](http://jruby.org/). + [JRuby](http://jruby.org/) and [TruffleRuby](https://www.graalvm.org/ruby/). Rubinius is an alternative implementation of Ruby written in Ruby. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine. + TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM. ### ENGINE VERSION @@ -96,13 +102,17 @@ Each application _may_ specify a Ruby engine version. If an engine version is specified, an engine _must_ also be specified. If the engine is "ruby" the engine version specified _must_ match the Ruby version. - ruby "1.8.7", engine: "jruby", engine_version: "1.6.7" + ruby "2.6.8", engine: "jruby", engine_version: "9.3.8.0" ### PATCHLEVEL -Each application _may_ specify a Ruby patchlevel. +Each application _may_ specify a Ruby patchlevel. Specifying the patchlevel has +been meaningless since Ruby 2.1.0 was released as the patchlevel is now +uniquely determined by a combination of major, minor, and teeny version numbers. + +This option was implemented in Bundler 1.4.0 for Ruby 2.0 or earlier. - ruby "2.0.0", patchlevel: "247" + ruby "3.1.2", patchlevel: "20" ## GEMS @@ -195,6 +205,10 @@ There are a number of `Gemfile` platforms: C Ruby (MRI) only, but not Windows * `windows`: Windows C Ruby (MRI), including RubyInstaller 32-bit and 64-bit versions + * `mswin`: + Windows C Ruby (MRI), including RubyInstaller 32-bit versions + * `mswin64`: + Windows C Ruby (MRI), including RubyInstaller 64-bit versions * `rbx`: Rubinius * `jruby`: @@ -202,17 +216,18 @@ There are a number of `Gemfile` platforms: * `truffleruby`: TruffleRuby -On platforms `ruby`, `mri`, and `windows`, you may additionally specify a version -by appending the major and minor version numbers without a delimiter. For example, -to specify that a gem should only be used on platform `ruby` version 2.3, use: +On platforms `ruby`, `mri`, `mswin`, `mswin64`, and `windows`, you may +additionally specify a version by appending the major and minor version numbers +without a delimiter. For example, to specify that a gem should only be used on +platform `ruby` version 3.1, use: - ruby_23 + ruby_31 As with groups (above), you may specify one or more platforms: gem "weakling", platforms: :jruby - gem "ruby-debug", platforms: :mri_18 - gem "nokogiri", platforms: [:windows_26, :jruby] + gem "ruby-debug", platforms: :mri_31 + gem "nokogiri", platforms: [:windows_31, :jruby] All operations involving groups ([`bundle install`](bundle-install.1.html), `Bundler.setup`, `Bundler.require`) behave exactly the same as if any groups not @@ -369,7 +384,7 @@ same, you can omit one. Are both equivalent to - gem "rails", git: "git://github.com/rails/rails.git" + gem "rails", git: "https://github.com/rails/rails.git" Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument. @@ -504,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 601957746f..f626a3218e 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -30,10 +30,10 @@ module Bundler end def full_name - if @original_platform == Gem::Platform::RUBY + @full_name ||= if @platform == Gem::Platform::RUBY "#{@name}-#{@version}" else - "#{@name}-#{@version}-#{@original_platform}" + "#{@name}-#{@version}-#{@platform}" end end @@ -102,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 161a3c0518..2ad35bc931 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -1,421 +1,427 @@ # frozen_string_literal: true module Bundler + # + # This class implements the interface needed by PubGrub for resolution. It is + # equivalent to the `PubGrub::BasicPackageSource` class provided by PubGrub by + # default and used by the most simple PubGrub consumers. + # class Resolver - require_relative "vendored_molinillo" + require_relative "vendored_pub_grub" require_relative "resolver/base" - require_relative "resolver/spec_group" + require_relative "resolver/candidate" + require_relative "resolver/incompatibility" + require_relative "resolver/root" include GemHelpers - # Figures out the best possible configuration of gems that satisfies - # the list of passed dependencies and any child dependencies without - # causing any gem activation errors. - # - # ==== Parameters - # *dependencies<Gem::Dependency>:: The list of dependencies to resolve - # - # ==== Returns - # <GemBundle>,nil:: If the list of dependencies can be resolved, a - # collection of gemspecs is returned. Otherwise, nil is returned. - def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) - base = SpecSet.new(base) unless base.is_a?(SpecSet) - resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) - resolver.start(requirements) - end - - def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) - @source_requirements = source_requirements - @base = Resolver::Base.new(base, additional_base_requirements) - @resolver = Molinillo::Resolver.new(self, self) - @results_for = {} - @search_for = {} - @platforms = platforms - @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY] + def initialize(base, gem_version_promoter) + @source_requirements = base.source_requirements + @base = base @gem_version_promoter = gem_version_promoter end - def start(requirements, exclude_specs: []) - @metadata_requirements, regular_requirements = requirements.partition {|dep| dep.name.end_with?("\0") } + def start + @requirements = @base.requirements + @packages = @base.packages - exclude_specs.each do |spec| - remove_from_candidates(spec) - end + root, logger = setup_solver - requirements.each {|dep| prerelease_specified[dep.name] ||= dep.prerelease? } + Bundler.ui.info "Resolving dependencies...", true - verify_gemfile_dependencies_are_found!(requirements) - result = @resolver.resolve(requirements). - map(&:payload). - reject {|sg| sg.name.end_with?("\0") }. - map(&:to_specs). - flatten + solve_versions(:root => root, :logger => logger) + end - SpecSet.new(SpecSet.new(result).for(regular_requirements, false, @platforms)) - rescue Molinillo::VersionConflict => e - conflicts = e.conflicts + def setup_solver + root = Resolver::Root.new(name_for_explicit_dependency_source) + root_version = Resolver::Candidate.new(0) - deps_to_unlock = conflicts.values.inject([]) do |deps, conflict| - deps |= conflict.requirement_trees.flatten.map {|req| base_requirements[req.name] }.compact + @all_specs = Hash.new do |specs, name| + 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 - if deps_to_unlock.any? - @base.unlock_deps(deps_to_unlock) - reset_spec_cache - retry + @sorted_versions = Hash.new do |candidates, package| + candidates[package] = if package.root? + [root_version] + else + all_versions_for(package).sort + end end - message = version_conflict_message(e) - raise VersionConflict.new(conflicts.keys.uniq, message) - rescue Molinillo::CircularDependencyError => e - names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" } - raise CyclicDependencyError, "Your bundle requires gems that depend" \ - " on each other, creating an infinite loop. Please remove" \ - " #{names.count > 1 ? "either " : ""}#{names.join(" or ")}" \ - " and try again." - end + root_dependencies = prepare_dependencies(@requirements, @packages) - include Molinillo::UI - - # Conveys debug information to the user. - # - # @param [Integer] depth the current depth of the resolution process. - # @return [void] - def debug(depth = 0) - return unless debug? - debug_info = yield - debug_info = debug_info.inspect unless debug_info.is_a?(String) - puts debug_info.split("\n").map {|s| depth == 0 ? "BUNDLER: #{s}" : "BUNDLER(#{depth}): #{s}" } - end + @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.reject {|d| d.name == package.name }, @packages) + end + end + end - def debug? - return @debug_mode if defined?(@debug_mode) - @debug_mode = - ENV["BUNDLER_DEBUG_RESOLVER"] || - ENV["BUNDLER_DEBUG_RESOLVER_TREE"] || - ENV["DEBUG_RESOLVER"] || - ENV["DEBUG_RESOLVER_TREE"] || - false - end + logger = Bundler::UI::Shell.new + logger.level = debug? ? "debug" : "warn" - def before_resolution - Bundler.ui.info "Resolving dependencies...", debug? + [root, logger] end - def after_resolution - Bundler.ui.info "" - end + def solve_versions(root:, logger:) + solver = PubGrub::VersionSolver.new(:source => self, :root => root, :logger => logger) + result = solver.solve + result.map {|package, version| version.to_specs(package) }.flatten.uniq + rescue PubGrub::SolveFailure => e + incompatibility = e.incompatibility - def indicate_progress - Bundler.ui.info ".", false unless debug? - end + names_to_unlock, names_to_allow_prereleases_for, extended_explanation = find_names_to_relax(incompatibility) - include Molinillo::SpecificationProvider + names_to_relax = names_to_unlock + names_to_allow_prereleases_for - def dependencies_for(specification) - specification.dependencies_for_activated_platforms - end + 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 + + @base.unlock_names(names_to_unlock) + end + + 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 - def search_for(dependency_proxy) - platform = dependency_proxy.__platform - dependency = dependency_proxy.dep - name = dependency.name - @search_for[dependency_proxy] ||= begin - locked_results = @base[name].select {|spec| requirement_satisfied_by?(dependency, nil, spec) } - locked_requirement = base_requirements[name] - results = results_for(dependency) + locked_results - results = results.select {|spec| requirement_satisfied_by?(locked_requirement, nil, spec) } if locked_requirement + @base.include_prereleases(names_to_allow_prereleases_for) + end + + root, logger = setup_solver + + Bundler.ui.debug "Retrying resolution...", true + retry + end - if results.any? - results = @gem_version_promoter.sort_versions(dependency, results) + explanation = e.message - results.group_by(&:version).reduce([]) do |groups, (_, specs)| - next groups unless specs.any? {|spec| spec.match_platform(platform) } + if extended_explanation + explanation << "\n\n" + explanation << extended_explanation + end - specs_by_platform = Hash.new do |current_specs, current_platform| - current_specs[current_platform] = select_best_platform_match(specs, current_platform) - end + raise SolveFailure.new(explanation) + end - spec_group_ruby = SpecGroup.create_for(specs_by_platform, [Gem::Platform::RUBY], Gem::Platform::RUBY) - if spec_group_ruby - spec_group_ruby.force_ruby_platform = dependency.force_ruby_platform - groups << spec_group_ruby - end + def find_names_to_relax(incompatibility) + names_to_unlock = [] + names_to_allow_prereleases_for = [] + extended_explanation = nil - next groups if @resolving_only_for_ruby || dependency.force_ruby_platform + while incompatibility.conflict? + cause = incompatibility.cause + incompatibility = cause.incompatibility - spec_group = SpecGroup.create_for(specs_by_platform, @platforms, platform) - groups << spec_group + incompatibility.terms.each do |term| + package = term.package + name = package.name - groups + if base_requirements[name] + names_to_unlock << name + elsif package.ignores_prereleases? + names_to_allow_prereleases_for << name end - else - [] + + 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 - end - def index_for(dependency) - source_for(dependency.name).specs + [names_to_unlock.uniq, names_to_allow_prereleases_for.uniq, extended_explanation] end - def source_for(name) - @source_requirements[name] || @source_requirements[:default] - end + def parse_dependency(package, dependency) + range = if repository_for(package).is_a?(Source::Gemspec) + PubGrub::VersionRange.any + else + requirement_to_range(dependency) + end - def results_for(dependency) - @results_for[dependency] ||= index_for(dependency).search(dependency) + PubGrub::VersionConstraint.new(package, :range => range) end - def name_for(dependency) - dependency.name - end + def versions_for(package, range=VersionRange.any) + versions = range.select_versions(@sorted_versions[package]) - def name_for_explicit_dependency_source - Bundler.default_gemfile.basename.to_s - rescue StandardError - "Gemfile" + sort_versions(package, versions) end - def requirement_satisfied_by?(requirement, activated, spec) - requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) - end + def no_versions_incompatibility_for(package, unsatisfied_term) + cause = PubGrub::Incompatibility::NoVersions.new(unsatisfied_term) + name = package.name + constraint = unsatisfied_term.constraint + constraint_string = constraint.constraint_string + requirements = constraint_string.split(" OR ").map {|req| Gem::Requirement.new(req.split(",")) } - def dependencies_equal?(dependencies, other_dependencies) - dependencies.map(&:dep) == other_dependencies.map(&:dep) - end + 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(requirements) + else + 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}" - def sort_dependencies(dependencies, activated, conflicts) - dependencies.sort_by do |dependency| - name = name_for(dependency) - vertex = activated.vertex_named(name) - [ - @base[name].any? ? 0 : 1, - vertex.payload ? 0 : 1, - vertex.root? ? 0 : 1, - amount_constrained(dependency), - conflicts[name] ? 0 : 1, - vertex.payload ? 0 : search_for(dependency).count, - self.class.platform_sort_key(dependency.__platform), - ] + label = "#{name} (#{constraint_string})" + extended_explanation = other_specs_matching_message(specs_matching_other_platforms, label) if specs_matching_other_platforms.any? end + + Incompatibility.new([unsatisfied_term], :cause => cause, :custom_explanation => custom_explanation, :extended_explanation => extended_explanation) end - def self.platform_sort_key(platform) - # Prefer specific platform to not specific platform - return ["99-LAST", "", "", ""] if Gem::Platform::RUBY == platform - ["00", *platform.to_a.map {|part| part || "" }] + def debug? + ENV["BUNDLER_DEBUG_RESOLVER"] || + ENV["BUNDLER_DEBUG_RESOLVER_TREE"] || + ENV["DEBUG_RESOLVER"] || + ENV["DEBUG_RESOLVER_TREE"] || + false end - private + def incompatibilities_for(package, version) + package_deps = @cached_dependencies[package] + sorted_versions = @sorted_versions[package] + package_deps[version].map do |dep_package, dep_constraint| + low = high = sorted_versions.index(version) - def base_requirements - @base.base_requirements - end + # find version low such that all >= low share the same dep + while low > 0 && package_deps[sorted_versions[low - 1]][dep_package] == dep_constraint + low -= 1 + end + low = + if low == 0 + nil + else + sorted_versions[low] + end + + # find version high such that all < high share the same dep + while high < sorted_versions.length && package_deps[sorted_versions[high]][dep_package] == dep_constraint + high += 1 + end + high = + if high == sorted_versions.length + nil + else + sorted_versions[high] + end + + range = PubGrub::VersionRange.new(:min => low, :max => high, :include_min => true) + + self_constraint = PubGrub::VersionConstraint.new(package, :range => range) - def prerelease_specified - @gem_version_promoter.prerelease_specified + 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([self_term, dep_term], :cause => :dependency, :custom_explanation => custom_explanation) + end end - def remove_from_candidates(spec) - @base.delete(spec) + def all_versions_for(package) + name = package.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 - @results_for.keys.each do |dep| - next unless dep.name == spec.name + locked_requirement = base_requirements[name] + results = filter_matching_specs(results, locked_requirement) if locked_requirement - @results_for[dep].reject {|s| s.name == spec.name && s.version == spec.version } + versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)| + platform_specs = package.platforms.flat_map {|platform| select_best_platform_match(specs, platform) } + next groups if platform_specs.empty? + + 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 || package.force_ruby_platform? + + groups << Resolver::Candidate.new(version, :specs => platform_specs) + + groups end - reset_spec_cache + sort_versions(package, versions) end - def reset_spec_cache - @search_for = {} - @gem_version_promoter.reset + def source_for(name) + @source_requirements[name] || @source_requirements[:default] end - # returns an integer \in (-\infty, 0] - # a number closer to 0 means the dependency is less constraining - # - # dependencies w/ 0 or 1 possibilities (ignoring version requirements) - # are given very negative values, so they _always_ sort first, - # before dependencies that are unconstrained - def amount_constrained(dependency) - @amount_constrained ||= {} - @amount_constrained[dependency.name] ||= if (base = @base[dependency.name]) && !base.empty? - dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1 - else - all = index_for(dependency).search(dependency.name).size + def default_bundler_source + @source_requirements[:default_bundler] + end - if all <= 1 - all - 1_000_000 - else - search = search_for(dependency) - search = prerelease_specified[dependency.name] ? search.count : search.count {|s| !s.version.prerelease? } - search - all - end - end + def bundler_pinned_to_current_version? + !default_bundler_source.nil? end - def verify_gemfile_dependencies_are_found!(requirements) - requirements.map! do |requirement| - name = requirement.name - next requirement if name == "bundler" - next requirement unless search_for(requirement).empty? - next unless requirement.current_platform? - - if (base = @base[name]) && !base.empty? - version = base.first.version - message = "You have requested:\n" \ - " #{name} #{requirement.requirement}\n\n" \ - "The bundle currently has #{name} locked at #{version}.\n" \ - "Try running `bundle update #{name}`\n\n" \ - "If you are updating multiple gems in your Gemfile at once,\n" \ - "try passing them all to `bundle update`" - else - message = gem_not_found_message(name, requirement, source_for(name)) - end - raise GemNotFound, message - end.compact! + def name_for_explicit_dependency_source + Bundler.default_gemfile.basename.to_s + rescue StandardError + "Gemfile" end - def gem_not_found_message(name, requirement, source, extra_message = "") - specs = source.specs.search(name).sort_by {|s| [s.version, s.platform.to_s] } + def raise_not_found!(package) + name = package.name + source = source_for(name) + specs = @all_specs[name] matching_part = name - requirement_label = SharedHelpers.pretty_dependency(requirement) + requirement_label = SharedHelpers.pretty_dependency(package.dependency) cache_message = begin " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? rescue GemfileNotFound nil end - specs_matching_requirement = specs.select {| spec| requirement.matches_spec?(spec) } + specs_matching_requirement = filter_matching_specs(specs, package.dependency.requirement) if specs_matching_requirement.any? specs = specs_matching_requirement matching_part = requirement_label - requirement_label = "#{requirement_label}' with platform '#{requirement.__platform}" + platforms = package.platforms + platform_label = platforms.size == 1 ? "platform '#{platforms.first}" : "platforms '#{platforms.join("', '")}" + requirement_label = "#{requirement_label}' with #{platform_label}" end - message = String.new("Could not find gem '#{requirement_label}'#{extra_message} in #{source}#{cache_message}.\n") + message = String.new("Could not find gem '#{requirement_label}' in #{source}#{cache_message}.\n") if specs.any? - message << "\nThe source contains the following gems matching '#{matching_part}':\n" - message << specs.map {|s| " * #{s.full_name}" }.join("\n") + message << "\n#{other_specs_matching_message(specs, matching_part)}" end - message + raise GemNotFound, message + end + + private + + 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 version_conflict_message(e) - # only show essential conflicts, if possible - conflicts = e.conflicts.dup + def requirement_satisfied_by?(requirement, spec) + requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec) + end - if conflicts["bundler"] - conflicts.replace("bundler" => conflicts["bundler"]) + def sort_versions(package, versions) + if versions.size > 1 + @gem_version_promoter.sort_versions(package, versions).reverse else - conflicts.delete_if do |_name, conflict| - deps = conflict.requirement_trees.map(&:last).flatten(1) - !Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) - end + versions end + end - e = Molinillo::VersionConflict.new(conflicts, e.specification_provider) unless conflicts.empty? + def repository_for(package) + source_for(package.name) + end - e.message_with_trees( - :full_message_for_conflict => lambda do |name, conflict| - trees = conflict.requirement_trees + def base_requirements + @base.base_requirements + end - # called first, because we want to reduce the amount of work required to find maximal empty sets - trees = trees.uniq {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } } + def prepare_dependencies(requirements, packages) + to_dependency_hash(requirements, packages).map do |dep_package, dep_constraint| + name = dep_package.name - # bail out if tree size is too big for Array#combination to make any sense - if trees.size <= 15 - maximal = 1.upto(trees.size).map do |size| - trees.map(&:last).flatten(1).combination(size).to_a - end.flatten(1).select do |deps| - Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) - end.min_by(&:size) + next [dep_package, dep_constraint] if name == "bundler" - trees.reject! {|t| !maximal.include?(t.last) } if maximal + 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? - trees.sort_by! {|t| t.reverse.map(&:name) } - end + next unless dep_package.current_platform? - if trees.size > 1 || name == "bundler" - o = if name.end_with?("\0") - String.new("Bundler found conflicting requirements for the #{name} version:") - else - String.new("Bundler could not find compatible versions for gem \"#{name}\":") - end - o << %(\n) - o << %( In #{name_for_explicit_dependency_source}:\n) - o << trees.map do |tree| - t = "".dup - depth = 2 - - base_tree = tree.first - base_tree_name = base_tree.name - - if base_tree_name.end_with?("\0") - t = nil - else - tree.each do |req| - t << " " * depth << SharedHelpers.pretty_dependency(req) - unless tree.last == req - if spec = conflict.activated_by_name[req.name] - t << %( was resolved to #{spec.version}, which) - end - t << %( depends on) - end - t << %(\n) - depth += 1 - end - end - t - end.compact.join("\n") - else - o = String.new - end + raise_not_found!(dep_package) + end.compact.to_h + end - if name == "bundler" - o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION})) - - conflict_dependency = conflict.requirement - conflict_requirement = conflict_dependency.requirement - other_bundler_required = !conflict_requirement.satisfied_by?(Gem::Version.new(Bundler::VERSION)) - - if other_bundler_required - o << "\n\n" - - candidate_specs = source_for(:default_bundler).specs.search(conflict_dependency) - if candidate_specs.any? - target_version = candidate_specs.last.version - new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ") - o << "Your bundle requires a different version of Bundler than the one you're running.\n" - o << "Install the necessary version with `gem install bundler:#{target_version}` and rerun bundler using `#{new_command}`\n" - else - o << "Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.\n" - end - end - elsif name.end_with?("\0") - o << %(\n Current #{name} version:\n #{SharedHelpers.pretty_dependency(@metadata_requirements.find {|req| req.name == name })}\n\n) - elsif !conflict.existing - o << "\n" - - relevant_source = conflict.requirement.source || source_for(name) - - extra_message = if trees.first.size > 1 - ", which is required by gem '#{SharedHelpers.pretty_dependency(trees.first[-2])}'," - else - "" - end - - o << gem_not_found_message(name, conflict.requirement, relevant_source, extra_message) - end + def other_specs_matching_message(specs, requirement) + message = String.new("The source contains the following gems matching '#{requirement}':\n") + message << specs.map {|s| " * #{s.full_name}" }.join("\n") + message + end - o + def requirement_to_range(requirement) + ranges = requirement.requirements.map do |(op, version)| + ver = Resolver::Candidate.new(version).generic! + platform_ver = Resolver::Candidate.new(version).platform_specific! + + case op + when "~>" + name = "~> #{ver}" + 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 => platform_ver) + when ">=" + PubGrub::VersionRange.new(:min => ver, :include_min => true) + when "<" + PubGrub::VersionRange.new(:max => ver) + when "<=" + PubGrub::VersionRange.new(:max => platform_ver, :include_max => true) + when "=" + PubGrub::VersionRange.new(:min => ver, :max => platform_ver, :include_min => true, :include_max => true) + when "!=" + PubGrub::VersionRange.new(:min => ver, :max => platform_ver, :include_min => true, :include_max => true).invert + else + raise "bad version specifier: #{op}" + end + end + + ranges.inject(&:intersect) + end + + def to_dependency_hash(dependencies, packages) + dependencies.inject({}) do |deps, dep| + package = packages[dep.name] + + current_req = deps[package] + new_req = parse_dependency(package, dep.requirement) + + deps[package] = if current_req + current_req.intersect(new_req) + else + new_req end - ) + + deps + end + end + + 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(" ") + "Your bundle requires a different version of Bundler than the one you're running.\n" \ + "Install the necessary version with `gem install bundler:#{target_version}` and rerun bundler using `#{new_command}`\n" + else + "Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.\n" + end end end end diff --git a/lib/bundler/resolver/base.rb b/lib/bundler/resolver/base.rb index 84e087b0ae..e5c3763c3f 100644 --- a/lib/bundler/resolver/base.rb +++ b/lib/bundler/resolver/base.rb @@ -1,48 +1,105 @@ # 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 @base_requirements ||= build_base_requirements end - def unlock_deps(deps) - exact, lower_bound = deps.partition(&:specific?) + def unlock_names(names) + indirect_pins = indirect_pins(names) - exact.each do |exact_dep| - @base.delete_by_name_and_version(exact_dep.name, exact_dep.requirement.requirements.first.last) - end + if indirect_pins.any? + loosen_names(indirect_pins) + else + pins = pins(names) - lower_bound.each do |lower_bound_dep| - @additional_base_requirements.delete(lower_bound_dep) + 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| - dep = Dependency.new(ls.name, ls.version) - base_requirements[ls.name] = DepProxy.get_proxy(dep, ls.platform) + req = Gem::Requirement.new(ls.version) + base_requirements[ls.name] = req end - @additional_base_requirements.each {|d| base_requirements[d.name] = d } base_requirements end end diff --git a/lib/bundler/resolver/candidate.rb b/lib/bundler/resolver/candidate.rb new file mode 100644 index 0000000000..e695ef08ee --- /dev/null +++ b/lib/bundler/resolver/candidate.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative "spec_group" + +module Bundler + class Resolver + # + # This class is a PubGrub compatible "Version" class that takes Bundler + # resolution complexities into account. + # + # Each Resolver::Candidate has a underlying `Gem::Version` plus a set of + # platforms. For example, 1.1.0-x86_64-linux is a different resolution candidate + # from 1.1.0 (generic). This is because different platform variants of the + # same gem version can bring different dependencies, so they need to be + # considered separately. + # + # Some candidates may also keep some information explicitly about the + # package the refer to. These candidates are referred to as "canonical" and + # are used when materializing resolution results back into RubyGems + # specifications that can be installed, written to lock files, and so on. + # + class Candidate + include Comparable + + attr_reader :version + + def initialize(version, specs: []) + @spec_group = Resolver::SpecGroup.new(specs) + @version = Gem::Version.new(version) + @ruby_only = specs.map(&:platform).uniq == [Gem::Platform::RUBY] + end + + def dependencies + @spec_group.dependencies + end + + def to_specs(package) + return [] if package.meta? + + @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 + + def segments + @version.segments + end + + def sort_obj + [@version, @ruby_only ? -1 : 1] + end + + def <=>(other) + return unless other.is_a?(self.class) + + sort_obj <=> other.sort_obj + end + + def ==(other) + return unless other.is_a?(self.class) + + sort_obj == other.sort_obj + end + + def eql?(other) + return unless other.is_a?(self.class) + + sort_obj.eql?(other.sort_obj) + end + + def hash + sort_obj.hash + end + + def to_s + @version.to_s + end + end + end +end diff --git a/lib/bundler/resolver/incompatibility.rb b/lib/bundler/resolver/incompatibility.rb new file mode 100644 index 0000000000..c61151fbeb --- /dev/null +++ b/lib/bundler/resolver/incompatibility.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Bundler + class Resolver + class Incompatibility < PubGrub::Incompatibility + attr_reader :extended_explanation + + def initialize(terms, cause:, custom_explanation: nil, extended_explanation: nil) + @extended_explanation = extended_explanation + + super(terms, :cause => cause, :custom_explanation => custom_explanation) + end + end + end +end diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb new file mode 100644 index 0000000000..7499a75006 --- /dev/null +++ b/lib/bundler/resolver/package.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Bundler + class Resolver + # + # Represents a gem being resolved, in a format PubGrub likes. + # + # The class holds the following information: + # + # * Platforms this gem will be resolved on. + # * The locked version of this gem resolution should favor (if any). + # * Whether the gem should be unlocked to its latest version. + # * The dependency explicit set in the Gemfile for this gem (if any). + # + class Package + attr_reader :name, :platforms, :dependency, :locked_version + + 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 + @name.delete("\0") + end + + def root? + false + end + + def meta? + @name.end_with?("\0") + end + + def ==(other) + self.class == other.class && @name == other.name + end + + def hash + @name.hash + end + + def unlock? + @unlock.empty? || @unlock.include?(name) + end + + def ignores_prereleases? + @prerelease == :ignore + end + + def prerelease_specified? + @prerelease == :consider_first + end + + def consider_prereleases! + @prerelease = :consider_last + end + + def force_ruby_platform? + @dependency.force_ruby_platform + end + + def current_platform? + @dependency.current_platform? + end + end + end +end diff --git a/lib/bundler/resolver/root.rb b/lib/bundler/resolver/root.rb new file mode 100644 index 0000000000..e5eb634fb8 --- /dev/null +++ b/lib/bundler/resolver/root.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative "package" + +module Bundler + class Resolver + # + # Represents the Gemfile from the resolver's perspective. It's the root + # package and Gemfile entries depend on it. + # + class Root < Package + def initialize(name) + @name = name + end + + def meta? + true + end + + def root? + true + end + end + end +end diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb index 4e5b0082d3..b44c19a73f 100644 --- a/lib/bundler/resolver/spec_group.rb +++ b/lib/bundler/resolver/spec_group.rb @@ -3,111 +3,79 @@ module Bundler class Resolver class SpecGroup - attr_accessor :name, :version, :source - attr_accessor :activated_platforms, :force_ruby_platform - - def self.create_for(specs, all_platforms, specific_platform) - specific_platform_specs = specs[specific_platform] - return unless specific_platform_specs.any? - - platforms = all_platforms.select {|p| specs[p].any? } - - new(specific_platform_specs.first, specs, platforms) + def initialize(specs) + @specs = specs end - def initialize(exemplary_spec, specs, relevant_platforms) - @exemplary_spec = exemplary_spec - @name = exemplary_spec.name - @version = exemplary_spec.version - @source = exemplary_spec.source - - @activated_platforms = relevant_platforms - @dependencies = Hash.new do |dependencies, platforms| - dependencies[platforms] = dependencies_for(platforms) - end - @specs = specs + def empty? + @specs.empty? end - def to_specs - activated_platforms.map do |p| - specs = @specs[p] - next unless specs.any? - - specs.map do |s| - lazy_spec = LazySpecification.new(name, version, s.platform, source) - lazy_spec.force_ruby_platform = force_ruby_platform - lazy_spec.dependencies.replace s.dependencies - lazy_spec - end - end.flatten.compact.uniq + def name + @name ||= exemplary_spec.name end - def to_s - activated_platforms_string = sorted_activated_platforms.join(", ") - "#{name} (#{version}) (#{activated_platforms_string})" + def version + @version ||= exemplary_spec.version end - def dependencies_for_activated_platforms - @dependencies[activated_platforms] + def source + @source ||= exemplary_spec.source end - def ==(other) - return unless other.is_a?(SpecGroup) - name == other.name && - version == other.version && - sorted_activated_platforms == other.sorted_activated_platforms && - source == other.source + def to_specs(force_ruby_platform) + @specs.map do |s| + lazy_spec = LazySpecification.new(name, version, s.platform, source) + lazy_spec.force_ruby_platform = force_ruby_platform + lazy_spec.dependencies.replace s.dependencies + lazy_spec + end end - def eql?(other) - return unless other.is_a?(SpecGroup) - name.eql?(other.name) && - version.eql?(other.version) && - sorted_activated_platforms.eql?(other.sorted_activated_platforms) && - source.eql?(other.source) + def to_s + sorted_spec_names.join(", ") end - def hash - name.hash ^ version.hash ^ sorted_activated_platforms.hash ^ source.hash + def dependencies + @dependencies ||= @specs.map do |spec| + __dependencies(spec) + metadata_dependencies(spec) + end.flatten.uniq end protected - def sorted_activated_platforms - activated_platforms.sort_by(&:to_s) + def sorted_spec_names + @sorted_spec_names ||= @specs.map(&:full_name).sort end private - def dependencies_for(platforms) - platforms.map do |platform| - __dependencies(platform) + metadata_dependencies(platform) - end.flatten + def exemplary_spec + @specs.first end - def __dependencies(platform) + def __dependencies(spec) dependencies = [] - @specs[platform].first.dependencies.each do |dep| + spec.dependencies.each do |dep| next if dep.type == :development - dependencies << DepProxy.get_proxy(Dependency.new(dep.name, dep.requirement), platform) + dependencies << Dependency.new(dep.name, dep.requirement) end dependencies end - def metadata_dependencies(platform) - spec = @specs[platform].first + def metadata_dependencies(spec) return [] if spec.is_a?(LazySpecification) [ - metadata_dependency("Ruby", spec.required_ruby_version, platform), - metadata_dependency("RubyGems", spec.required_rubygems_version, platform), + metadata_dependency("Ruby", spec.required_ruby_version), + metadata_dependency("RubyGems", spec.required_rubygems_version), ].compact end - def metadata_dependency(name, requirement, platform) + def metadata_dependency(name, requirement) return if requirement.nil? || requirement.none? - DepProxy.get_proxy(Dependency.new("#{name}\0", requirement), platform) + Dependency.new("#{name}\0", requirement) end end end 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 d53d688009..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 @@ -308,6 +319,28 @@ module Gem end end + # On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory. + class Specification + if /^universal\.(?<arch>.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM) + local_platform = Platform.local + if local_platform.cpu == "universal" + ORIGINAL_LOCAL_PLATFORM = local_platform.to_s.freeze + + local_platform.cpu = if arch == "arm64e" # arm64e is only permitted for Apple system binaries + "arm64" + else + arch + end + + def extensions_dir + Gem.default_ext_dir_for(base_dir) || + File.join(base_dir, "extensions", ORIGINAL_LOCAL_PLATFORM, + Gem.extension_api_version) + end + end + end + end + require "rubygems/util" Util.singleton_class.module_eval do @@ -316,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 8c4e26f074..d1d4e1d07a 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -160,10 +160,10 @@ 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, print_source = false) + def pretty_dependency(dep) msg = String.new(dep.name) msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default @@ -172,7 +172,6 @@ module Bundler msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY end - msg << " from the `#{dep.source}` source" if print_source && dep.source msg end @@ -285,6 +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__) 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 fd34edffb7..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 - git_proxy.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(", ")})" @@ -126,7 +138,7 @@ module Bundler path = Pathname.new(path) path = path.expand_path(Bundler.root) unless path.relative? - unless options["branch"] || Bundler.settings[:disable_local_branch_check] + unless branch || Bundler.settings[:disable_local_branch_check] raise GitError, "Cannot use local override for #{name} at #{path} because " \ ":branch is not specified in Gemfile. Specify a branch or run " \ "`bundle config unset local.#{override_for(original_path)}` to remove the local override" @@ -141,14 +153,14 @@ module Bundler # Create a new git proxy without the cached revision # so the Gemfile.lock always picks up the new revision. - @git_proxy = GitProxy.new(path, uri, ref) + @git_proxy = GitProxy.new(path, uri, options) - if git_proxy.branch != options["branch"] && !Bundler.settings[:disable_local_branch_check] + if current_branch != branch && !Bundler.settings[:disable_local_branch_check] raise GitError, "Local override for #{name} at #{path} is using branch " \ - "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}" + "#{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}" @@ -228,6 +241,10 @@ module Bundler git_proxy.revision end + def current_branch + git_proxy.current_branch + end + def allow_git_ops? @allow_remote || @allow_cached end @@ -238,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| @@ -291,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{/$}, "") @@ -313,7 +344,7 @@ module Bundler end def git_proxy - @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision, self) + @git_proxy ||= GitProxy.new(cache_path, uri, options, cached_revision, self) end def fetch diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 745a7fe118..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,24 +47,28 @@ module Bundler # All actions required by the Git source is encapsulated in this # object. class GitProxy - attr_accessor :path, :uri, :ref + attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref attr_writer :revision - def initialize(path, uri, ref, revision = nil, git = nil) + def initialize(path, uri, options = {}, revision = nil, git = nil) @path = path @uri = uri - @ref = ref + @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 branch - @branch ||= allowed_with_path do - git("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip + def current_branch + @current_branch ||= with_path do + git_local("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip end end @@ -76,36 +80,26 @@ module Bundler end def version - git("--version").match(/(git version\s*)?((\.?\d+)+).*/)[2] + @version ||= full_version.match(/((\.?\d+)+).*/)[1] end def full_version - git("--version").sub("git version", "").strip + @full_version ||= git_local("--version").sub(/git version\s*/, "").strip end def checkout - return if path.exist? && has_revision_cached? - extra_ref = "#{ref}:#{ref}" if ref && ref.start_with?("refs/") - - Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" + return if has_revision_cached? - configured_uri = configured_uri_for(uri).to_s + 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", "--", 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 - with_path do - git_retry(*["fetch", "--force", "--quiet", "--tags", "--", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path) - end + git_remote_fetch(unshallow_needed ? ["--unshallow"] : depth_args) end def copy_to(destination, submodules = false) - # method 1 unless File.exist?(destination.join(".git")) begin SharedHelpers.filesystem_access(destination.dirname) do |p| @@ -114,7 +108,7 @@ module Bundler SharedHelpers.filesystem_access(destination) do |p| FileUtils.rm_rf(p) end - git_retry "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s + git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination) rescue Errno::EEXIST => e file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1] @@ -123,14 +117,10 @@ module Bundler "this file and try again." end end - # method 2 - git_retry "fetch", "--force", "--quiet", "--tags", path.to_s, :dir => destination - begin - git "reset", "--hard", @revision, :dir => destination - rescue GitCommandError => e - raise MissingGitRevisionError.new(e.command, destination, @revision, URICredentialsFilter.credential_filtered_uri(uri)) - end + git "fetch", "--force", "--quiet", *extra_fetch_args, :dir => destination if @commit_ref + + git "reset", "--hard", @revision, :dir => destination if submodules git_retry "submodule", "update", "--init", "--recursive", :dir => destination @@ -142,14 +132,117 @@ module Bundler private - def git_null(*command, dir: nil) - check_allowed(command) + 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 - out, status = SharedHelpers.with_clean_git_env do - capture_and_ignore_stderr(*capture3_args_for(command, dir)) + 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? + + ref.start_with?("refs/") + end + + def depth + return @depth if defined?(@depth) + + @depth = if !supports_fetching_unreachable_refs? + nil + elsif not_pinned? || pinned_to_full_sha? + 1 + elsif ref.include?("~") + parsed_depth = ref.split("~").last + parsed_depth.to_i + 1 end + end + + def refspec + 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 + else + "refs/*" + end + + "#{reference}:#{reference}" + end + + def commit + @commit ||= pinned_to_full_sha? ? ref : @revision + end + + def fully_qualified_ref + if branch + "refs/heads/#{branch}" + elsif tag + "refs/tags/#{tag}" + elsif ref.nil? + "refs/heads/#{current_branch}" + end + end + + def not_pinned? + branch_option || ref.nil? + end + + def pinned_to_full_sha? + ref =~ /\A\h{40}\z/ + end - [URICredentialsFilter.credential_filtered_string(out, uri), status] + def git_null(*command, dir: nil) + check_allowed(command) + + capture(command, dir, :ignore_err => true) end def git_retry(*command, dir: nil) @@ -161,51 +254,64 @@ 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? - return unless @revision - with_path { git("cat-file", "-e", @revision, :dir => path) } + return unless @revision && path.exist? + git("cat-file", "-e", @revision, :dir => path) true rescue GitError false end - def remove_cache - FileUtils.rm_rf(path) + def find_local_revision + return head_revision if explicit_ref.nil? + + find_revision_for(explicit_ref) end - def find_local_revision - allowed_with_path do - git("rev-parse", "--verify", ref || "HEAD", :dir => path).strip - end + def head_revision + verify("HEAD") + end + + def find_revision_for(reference) + verify(reference) rescue GitCommandError => e - raise MissingGitRevisionError.new(e.command, path, ref, URICredentialsFilter.credential_filtered_uri(uri)) + raise MissingGitRevisionError.new(e.command, path, reference, credential_filtered_uri) end - # Adds credentials to the URI as Fetcher#configured_uri_for does - def configured_uri_for(uri) - if /https?:/ =~ uri + def verify(reference) + git("rev-parse", "--verify", reference, :dir => path).strip + end + + # Adds credentials to the URI + def configured_uri + if /https?:/.match?(uri) remote = Bundler::URI(uri) config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host] remote.userinfo ||= config_auth remote.to_s + elsif File.exist?(uri) + "file://#{uri}" else - uri + uri.to_s end end + # Removes credentials from the URI + def credential_filtered_uri + URICredentialsFilter.credential_filtered_uri(uri) + end + def allow? allowed = @git ? @git.allow_git_ops? : true @@ -225,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) @@ -254,9 +378,53 @@ module Bundler end end + def extra_clone_args + args = depth_args + return [] if args.empty? + + args += ["--single-branch"] + args.unshift("--no-tags") if supports_cloning_with_no_tags? + + # 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 depth_args + return [] if full_clone? + + ["--depth", depth.to_s] + end + + def extra_fetch_args + extra_args = [path.to_s, *depth_args] + extra_args.push(@commit_ref) + extra_args + end + + def branch_option + branch || tag + end + + def full_clone? + depth.nil? + end + def supports_minus_c? @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5") end + + def supports_fetching_unreachable_refs? + @supports_fetching_unreachable_refs ||= Gem::Version.new(version) >= Gem::Version.new("2.5.0") + end + + def supports_cloning_with_no_tags? + @supports_cloning_with_no_tags ||= Gem::Version.new(version) >= Gem::Version.new("2.14.0-rc0") + end end end end diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb index 23531b8bd4..593da6d1a7 100644 --- a/lib/bundler/source/metadata.rb +++ b/lib/bundler/source/metadata.rb @@ -15,7 +15,6 @@ module Bundler s.version = VERSION s.license = "MIT" s.platform = Gem::Platform::RUBY - s.source = self s.authors = ["bundler team"] s.bindir = "exe" s.homepage = "https://bundler.io" 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 21d57fdab4..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? @@ -122,8 +132,8 @@ module Bundler @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } end - def delete_by_name_and_version(name, version) - @specs.reject! {|spec| spec.name == name && spec.version == version } + def delete_by_name(name) + @specs.reject! {|spec| spec.name == name } @lookup = nil @sorted = nil end @@ -165,7 +175,7 @@ module Bundler cgems = extract_circular_gems(error) raise CyclicDependencyError, "Your bundle requires gems that depend" \ " on each other, creating an infinite loop. Please remove either" \ - " gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again." + " gem '#{cgems[0]}' or gem '#{cgems[1]}' and try again." end end @@ -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/ui/shell.rb b/lib/bundler/ui/shell.rb index 384752a340..4139585c47 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -20,29 +20,52 @@ module Bundler @shell.set_color(string, *color) end - def info(msg, newline = nil) - tell_me(msg, nil, newline) if level("info") + def info(msg = nil, newline = nil) + return unless info? + + tell_me(msg || yield, nil, newline) end - def confirm(msg, newline = nil) - tell_me(msg, :green, newline) if level("confirm") + def confirm(msg = nil, newline = nil) + return unless confirm? + + tell_me(msg || yield, :green, newline) end - def warn(msg, newline = nil, color = :yellow) - return unless level("warn") + def warn(msg = nil, newline = nil, color = :yellow) + return unless warn? return if @warning_history.include? msg @warning_history << msg - tell_err(msg, color, newline) + tell_err(msg || yield, color, newline) + end + + def error(msg = nil, newline = nil, color = :red) + return unless error? + + tell_err(msg || yield, color, newline) + end + + def debug(msg = nil, newline = nil) + return unless debug? + + tell_me(msg || yield, nil, newline) + end + + def info? + level("info") + end + + def confirm? + level("confirm") end - def error(msg, newline = nil, color = :red) - return unless level("error") - tell_err(msg, color, newline) + def warn? + level("warn") end - def debug(msg, newline = nil) - tell_me(msg, nil, newline) if debug? + def error? + level("error") end def debug? diff --git a/lib/bundler/ui/silent.rb b/lib/bundler/ui/silent.rb index dca1b2ac86..fa3292bdc9 100644 --- a/lib/bundler/ui/silent.rb +++ b/lib/bundler/ui/silent.rb @@ -13,30 +13,46 @@ module Bundler string end - def info(message, newline = nil) + def info(message = nil, newline = nil) end - def confirm(message, newline = nil) + def confirm(message = nil, newline = nil) end - def warn(message, newline = nil) + def warn(message = nil, newline = nil) @warnings |= [message] end - def error(message, newline = nil) + def error(message = nil, newline = nil) end - def debug(message, newline = nil) + def debug(message = nil, newline = nil) + end + + def confirm? + false + end + + def error? + false end def debug? false end + def info? + false + end + def quiet? false end + def warn? + false + end + def ask(message) 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/molinillo/lib/molinillo.rb b/lib/bundler/vendor/molinillo/lib/molinillo.rb deleted file mode 100644 index a52b96deaf..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require_relative 'molinillo/gem_metadata' -require_relative 'molinillo/errors' -require_relative 'molinillo/resolver' -require_relative 'molinillo/modules/ui' -require_relative 'molinillo/modules/specification_provider' - -# Bundler::Molinillo is a generic dependency resolution algorithm. -module Bundler::Molinillo -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb deleted file mode 100644 index bcacf35243..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # @!visibility private - module Delegates - # Delegates all {Bundler::Molinillo::ResolutionState} methods to a `#state` property. - module ResolutionState - # (see Bundler::Molinillo::ResolutionState#name) - def name - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.name - end - - # (see Bundler::Molinillo::ResolutionState#requirements) - def requirements - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.requirements - end - - # (see Bundler::Molinillo::ResolutionState#activated) - def activated - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.activated - end - - # (see Bundler::Molinillo::ResolutionState#requirement) - def requirement - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.requirement - end - - # (see Bundler::Molinillo::ResolutionState#possibilities) - def possibilities - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.possibilities - end - - # (see Bundler::Molinillo::ResolutionState#depth) - def depth - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.depth - end - - # (see Bundler::Molinillo::ResolutionState#conflicts) - def conflicts - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.conflicts - end - - # (see Bundler::Molinillo::ResolutionState#unused_unwind_options) - def unused_unwind_options - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.unused_unwind_options - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb deleted file mode 100644 index f8c695c1ed..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - module Delegates - # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a - # `#specification_provider` property. - module SpecificationProvider - # (see Bundler::Molinillo::SpecificationProvider#search_for) - def search_for(dependency) - with_no_such_dependency_error_handling do - specification_provider.search_for(dependency) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#dependencies_for) - def dependencies_for(specification) - with_no_such_dependency_error_handling do - specification_provider.dependencies_for(specification) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#requirement_satisfied_by?) - def requirement_satisfied_by?(requirement, activated, spec) - with_no_such_dependency_error_handling do - specification_provider.requirement_satisfied_by?(requirement, activated, spec) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#dependencies_equal?) - def dependencies_equal?(dependencies, other_dependencies) - with_no_such_dependency_error_handling do - specification_provider.dependencies_equal?(dependencies, other_dependencies) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for) - def name_for(dependency) - with_no_such_dependency_error_handling do - specification_provider.name_for(dependency) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for_explicit_dependency_source) - def name_for_explicit_dependency_source - with_no_such_dependency_error_handling do - specification_provider.name_for_explicit_dependency_source - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for_locking_dependency_source) - def name_for_locking_dependency_source - with_no_such_dependency_error_handling do - specification_provider.name_for_locking_dependency_source - end - end - - # (see Bundler::Molinillo::SpecificationProvider#sort_dependencies) - def sort_dependencies(dependencies, activated, conflicts) - with_no_such_dependency_error_handling do - specification_provider.sort_dependencies(dependencies, activated, conflicts) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#allow_missing?) - def allow_missing?(dependency) - with_no_such_dependency_error_handling do - specification_provider.allow_missing?(dependency) - end - end - - private - - # Ensures any raised {NoSuchDependencyError} has its - # {NoSuchDependencyError#required_by} set. - # @yield - def with_no_such_dependency_error_handling - yield - rescue NoSuchDependencyError => error - if state - vertex = activated.vertex_named(name_for(error.dependency)) - error.required_by += vertex.incoming_edges.map { |e| e.origin.name } - error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? - end - raise - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb deleted file mode 100644 index 4d577213b9..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../../vendored_tsort' - -require_relative 'dependency_graph/log' -require_relative 'dependency_graph/vertex' - -module Bundler::Molinillo - # A directed acyclic graph that is tuned to hold named dependencies - class DependencyGraph - include Enumerable - - # Enumerates through the vertices of the graph. - # @return [Array<Vertex>] The graph's vertices. - def each - return vertices.values.each unless block_given? - vertices.values.each { |v| yield v } - end - - include Bundler::TSort - - # @!visibility private - alias tsort_each_node each - - # @!visibility private - def tsort_each_child(vertex, &block) - vertex.successors.each(&block) - end - - # Topologically sorts the given vertices. - # @param [Enumerable<Vertex>] vertices the vertices to be sorted, which must - # all belong to the same graph. - # @return [Array<Vertex>] The sorted vertices. - def self.tsort(vertices) - Bundler::TSort.tsort( - lambda { |b| vertices.each(&b) }, - lambda { |v, &b| (v.successors & vertices).each(&b) } - ) - end - - # A directed edge of a {DependencyGraph} - # @attr [Vertex] origin The origin of the directed edge - # @attr [Vertex] destination The destination of the directed edge - # @attr [Object] requirement The requirement the directed edge represents - Edge = Struct.new(:origin, :destination, :requirement) - - # @return [{String => Vertex}] the vertices of the dependency graph, keyed - # by {Vertex#name} - attr_reader :vertices - - # @return [Log] the op log for this graph - attr_reader :log - - # Initializes an empty dependency graph - def initialize - @vertices = {} - @log = Log.new - end - - # Tags the current state of the dependency as the given tag - # @param [Object] tag an opaque tag for the current state of the graph - # @return [Void] - def tag(tag) - log.tag(self, tag) - end - - # Rewinds the graph to the state tagged as `tag` - # @param [Object] tag the tag to rewind to - # @return [Void] - def rewind_to(tag) - log.rewind_to(self, tag) - end - - # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} - # are properly copied. - # @param [DependencyGraph] other the graph to copy. - def initialize_copy(other) - super - @vertices = {} - @log = other.log.dup - traverse = lambda do |new_v, old_v| - return if new_v.outgoing_edges.size == old_v.outgoing_edges.size - old_v.outgoing_edges.each do |edge| - destination = add_vertex(edge.destination.name, edge.destination.payload) - add_edge_no_circular(new_v, destination, edge.requirement) - traverse.call(destination, edge.destination) - end - end - other.vertices.each do |name, vertex| - new_vertex = add_vertex(name, vertex.payload, vertex.root?) - new_vertex.explicit_requirements.replace(vertex.explicit_requirements) - traverse.call(new_vertex, vertex) - end - end - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{vertices.values.inspect}" - end - - # @param [Hash] options options for dot output. - # @return [String] Returns a dot format representation of the graph - def to_dot(options = {}) - edge_label = options.delete(:edge_label) - raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? - - dot_vertices = [] - dot_edges = [] - vertices.each do |n, v| - dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" - v.outgoing_edges.each do |e| - label = edge_label ? edge_label.call(e) : e.requirement - dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" - end - end - - dot_vertices.uniq! - dot_vertices.sort! - dot_edges.uniq! - dot_edges.sort! - - dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') - dot.join("\n") - end - - # @param [DependencyGraph] other - # @return [Boolean] whether the two dependency graphs are equal, determined - # by a recursive traversal of each {#root_vertices} and its - # {Vertex#successors} - def ==(other) - return false unless other - return true if equal?(other) - vertices.each do |name, vertex| - other_vertex = other.vertex_named(name) - return false unless other_vertex - return false unless vertex.payload == other_vertex.payload - return false unless other_vertex.successors.to_set == vertex.successors.to_set - end - end - - # @param [String] name - # @param [Object] payload - # @param [Array<String>] parent_names - # @param [Object] requirement the requirement that is requiring the child - # @return [void] - def add_child_vertex(name, payload, parent_names, requirement) - root = !parent_names.delete(nil) { true } - vertex = add_vertex(name, payload, root) - vertex.explicit_requirements << requirement if root - parent_names.each do |parent_name| - parent_vertex = vertex_named(parent_name) - add_edge(parent_vertex, vertex, requirement) - end - vertex - end - - # Adds a vertex with the given name, or updates the existing one. - # @param [String] name - # @param [Object] payload - # @return [Vertex] the vertex that was added to `self` - def add_vertex(name, payload, root = false) - log.add_vertex(self, name, payload, root) - end - - # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively - # removing any non-root vertices that were orphaned in the process - # @param [String] name - # @return [Array<Vertex>] the vertices which have been detached - def detach_vertex_named(name) - log.detach_vertex_named(self, name) - end - - # @param [String] name - # @return [Vertex,nil] the vertex with the given name - def vertex_named(name) - vertices[name] - end - - # @param [String] name - # @return [Vertex,nil] the root vertex with the given name - def root_vertex_named(name) - vertex = vertex_named(name) - vertex if vertex && vertex.root? - end - - # Adds a new {Edge} to the dependency graph - # @param [Vertex] origin - # @param [Vertex] destination - # @param [Object] requirement the requirement that this edge represents - # @return [Edge] the added edge - def add_edge(origin, destination, requirement) - if destination.path_to?(origin) - raise CircularDependencyError.new(path(destination, origin)) - end - add_edge_no_circular(origin, destination, requirement) - end - - # Deletes an {Edge} from the dependency graph - # @param [Edge] edge - # @return [Void] - def delete_edge(edge) - log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) - end - - # Sets the payload of the vertex with the given name - # @param [String] name the name of the vertex - # @param [Object] payload the payload - # @return [Void] - def set_payload(name, payload) - log.set_payload(self, name, payload) - end - - private - - # Adds a new {Edge} to the dependency graph without checking for - # circularity. - # @param (see #add_edge) - # @return (see #add_edge) - def add_edge_no_circular(origin, destination, requirement) - log.add_edge_no_circular(self, origin.name, destination.name, requirement) - end - - # Returns the path between two vertices - # @raise [ArgumentError] if there is no path between the vertices - # @param [Vertex] from - # @param [Vertex] to - # @return [Array<Vertex>] the shortest path from `from` to `to` - def path(from, to) - distances = Hash.new(vertices.size + 1) - distances[from.name] = 0 - predecessors = {} - each do |vertex| - vertex.successors.each do |successor| - if distances[successor.name] > distances[vertex.name] + 1 - distances[successor.name] = distances[vertex.name] + 1 - predecessors[successor] = vertex - end - end - end - - path = [to] - while before = predecessors[to] - path << before - to = before - break if to == from - end - - unless path.last.equal?(from) - raise ArgumentError, "There is no path from #{from.name} to #{to.name}" - end - - path.reverse - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb deleted file mode 100644 index c04c7eec9c..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class DependencyGraph - # An action that modifies a {DependencyGraph} that is reversible. - # @abstract - class Action - # rubocop:disable Lint/UnusedMethodArgument - - # @return [Symbol] The name of the action. - def self.action_name - raise 'Abstract' - end - - # Performs the action on the given graph. - # @param [DependencyGraph] graph the graph to perform the action on. - # @return [Void] - def up(graph) - raise 'Abstract' - end - - # Reverses the action on the given graph. - # @param [DependencyGraph] graph the graph to reverse the action on. - # @return [Void] - def down(graph) - raise 'Abstract' - end - - # @return [Action,Nil] The previous action - attr_accessor :previous - - # @return [Action,Nil] The next action - attr_accessor :next - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb deleted file mode 100644 index 946a08236e..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_edge_no_circular) - class AddEdgeNoCircular < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - delete_first(edge.origin.outgoing_edges, edge) - delete_first(edge.destination.incoming_edges, edge) - end - - # @!group AddEdgeNoCircular - - # @return [String] the name of the origin of the edge - attr_reader :origin - - # @return [String] the name of the destination of the edge - attr_reader :destination - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin the name of the origin of the edge - # @param [String] destination the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin, destination, requirement) - @origin = origin - @destination = destination - @requirement = requirement - end - - private - - def delete_first(array, item) - return unless index = array.index(item) - array.delete_at(index) - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb deleted file mode 100644 index 483527daf8..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_vertex) - class AddVertex < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - if existing = graph.vertices[name] - @existing_payload = existing.payload - @existing_root = existing.root - end - vertex = existing || Vertex.new(name, payload) - graph.vertices[vertex.name] = vertex - vertex.payload ||= payload - vertex.root ||= root - vertex - end - - # (see Action#down) - def down(graph) - if defined?(@existing_payload) - vertex = graph.vertices[name] - vertex.payload = @existing_payload - vertex.root = @existing_root - else - graph.vertices.delete(name) - end - end - - # @!group AddVertex - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # @return [Boolean] whether the vertex is root or not - attr_reader :root - - # Initialize an action to add a vertex to a dependency graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - # @param [Boolean] root whether the vertex is root or not - def initialize(name, payload, root) - @name = name - @payload = payload - @root = root - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb deleted file mode 100644 index d81940585a..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#delete_edge) - class DeleteEdge < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :delete_edge - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges.delete(edge) - edge.destination.incoming_edges.delete(edge) - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # @!group DeleteEdge - - # @return [String] the name of the origin of the edge - attr_reader :origin_name - - # @return [String] the name of the destination of the edge - attr_reader :destination_name - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new( - graph.vertex_named(origin_name), - graph.vertex_named(destination_name), - requirement - ) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin_name the name of the origin of the edge - # @param [String] destination_name the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin_name, destination_name, requirement) - @origin_name = origin_name - @destination_name = destination_name - @requirement = requirement - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb deleted file mode 100644 index 36fce7c526..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#detach_vertex_named - class DetachVertexNamed < Action - # @!group Action - - # (see Action#name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - return [] unless @vertex = graph.vertices.delete(name) - - removed_vertices = [@vertex] - @vertex.outgoing_edges.each do |e| - v = e.destination - v.incoming_edges.delete(e) - if !v.root? && v.incoming_edges.empty? - removed_vertices.concat graph.detach_vertex_named(v.name) - end - end - - @vertex.incoming_edges.each do |e| - v = e.origin - v.outgoing_edges.delete(e) - end - - removed_vertices - end - - # (see Action#down) - def down(graph) - return unless @vertex - graph.vertices[@vertex.name] = @vertex - @vertex.outgoing_edges.each do |e| - e.destination.incoming_edges << e - end - @vertex.incoming_edges.each do |e| - e.origin.outgoing_edges << e - end - end - - # @!group DetachVertexNamed - - # @return [String] the name of the vertex to detach - attr_reader :name - - # Initialize an action to detach a vertex from a dependency graph - # @param [String] name the name of the vertex to detach - def initialize(name) - @name = name - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb deleted file mode 100644 index 6f0de19886..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -require_relative 'add_edge_no_circular' -require_relative 'add_vertex' -require_relative 'delete_edge' -require_relative 'detach_vertex_named' -require_relative 'set_payload' -require_relative 'tag' - -module Bundler::Molinillo - class DependencyGraph - # A log for dependency graph actions - class Log - # Initializes an empty log - def initialize - @current_action = @first_action = nil - end - - # @!macro [new] action - # {include:DependencyGraph#$0} - # @param [Graph] graph the graph to perform the action on - # @param (see DependencyGraph#$0) - # @return (see DependencyGraph#$0) - - # @macro action - def tag(graph, tag) - push_action(graph, Tag.new(tag)) - end - - # @macro action - def add_vertex(graph, name, payload, root) - push_action(graph, AddVertex.new(name, payload, root)) - end - - # @macro action - def detach_vertex_named(graph, name) - push_action(graph, DetachVertexNamed.new(name)) - end - - # @macro action - def add_edge_no_circular(graph, origin, destination, requirement) - push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) - end - - # {include:DependencyGraph#delete_edge} - # @param [Graph] graph the graph to perform the action on - # @param [String] origin_name - # @param [String] destination_name - # @param [Object] requirement - # @return (see DependencyGraph#delete_edge) - def delete_edge(graph, origin_name, destination_name, requirement) - push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) - end - - # @macro action - def set_payload(graph, name, payload) - push_action(graph, SetPayload.new(name, payload)) - end - - # Pops the most recent action from the log and undoes the action - # @param [DependencyGraph] graph - # @return [Action] the action that was popped off the log - def pop!(graph) - return unless action = @current_action - unless @current_action = action.previous - @first_action = nil - end - action.down(graph) - action - end - - extend Enumerable - - # @!visibility private - # Enumerates each action in the log - # @yield [Action] - def each - return enum_for unless block_given? - action = @first_action - loop do - break unless action - yield action - action = action.next - end - self - end - - # @!visibility private - # Enumerates each action in the log in reverse order - # @yield [Action] - def reverse_each - return enum_for(:reverse_each) unless block_given? - action = @current_action - loop do - break unless action - yield action - action = action.previous - end - self - end - - # @macro action - def rewind_to(graph, tag) - loop do - action = pop!(graph) - raise "No tag #{tag.inspect} found" unless action - break if action.class.action_name == :tag && action.tag == tag - end - end - - private - - # Adds the given action to the log, running the action - # @param [DependencyGraph] graph - # @param [Action] action - # @return The value returned by `action.up` - def push_action(graph, action) - action.previous = @current_action - @current_action.next = action if @current_action - @current_action = action - @first_action ||= action - action.up(graph) - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb deleted file mode 100644 index 2e9b90e6cd..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#set_payload - class SetPayload < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :set_payload - end - - # (see Action#up) - def up(graph) - vertex = graph.vertex_named(name) - @old_payload = vertex.payload - vertex.payload = payload - end - - # (see Action#down) - def down(graph) - graph.vertex_named(name).payload = @old_payload - end - - # @!group SetPayload - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # Initialize an action to add set the payload for a vertex in a dependency - # graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - def initialize(name, payload) - @name = name - @payload = payload - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb deleted file mode 100644 index 5b5da3e4f9..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#tag - class Tag < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :tag - end - - # (see Action#up) - def up(graph) - end - - # (see Action#down) - def down(graph) - end - - # @!group Tag - - # @return [Object] An opaque tag - attr_reader :tag - - # Initialize an action to tag a state of a dependency graph - # @param [Object] tag an opaque tag - def initialize(tag) - @tag = tag - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb deleted file mode 100644 index 1185a8ab05..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class DependencyGraph - # A vertex in a {DependencyGraph} that encapsulates a {#name} and a - # {#payload} - class Vertex - # @return [String] the name of the vertex - attr_accessor :name - - # @return [Object] the payload the vertex holds - attr_accessor :payload - - # @return [Array<Object>] the explicit requirements that required - # this vertex - attr_reader :explicit_requirements - - # @return [Boolean] whether the vertex is considered a root vertex - attr_accessor :root - alias root? root - - # Initializes a vertex with the given name and payload. - # @param [String] name see {#name} - # @param [Object] payload see {#payload} - def initialize(name, payload) - @name = name.frozen? ? name : name.dup.freeze - @payload = payload - @explicit_requirements = [] - @outgoing_edges = [] - @incoming_edges = [] - end - - # @return [Array<Object>] all of the requirements that required - # this vertex - def requirements - (incoming_edges.map(&:requirement) + explicit_requirements).uniq - end - - # @return [Array<Edge>] the edges of {#graph} that have `self` as their - # {Edge#origin} - attr_accessor :outgoing_edges - - # @return [Array<Edge>] the edges of {#graph} that have `self` as their - # {Edge#destination} - attr_accessor :incoming_edges - - # @return [Array<Vertex>] the vertices of {#graph} that have an edge with - # `self` as their {Edge#destination} - def predecessors - incoming_edges.map(&:origin) - end - - # @return [Set<Vertex>] the vertices of {#graph} where `self` is a - # {#descendent?} - def recursive_predecessors - _recursive_predecessors - end - - # @param [Set<Vertex>] vertices the set to add the predecessors to - # @return [Set<Vertex>] the vertices of {#graph} where `self` is a - # {#descendent?} - def _recursive_predecessors(vertices = new_vertex_set) - incoming_edges.each do |edge| - vertex = edge.origin - next unless vertices.add?(vertex) - vertex._recursive_predecessors(vertices) - end - - vertices - end - protected :_recursive_predecessors - - # @return [Array<Vertex>] the vertices of {#graph} that have an edge with - # `self` as their {Edge#origin} - def successors - outgoing_edges.map(&:destination) - end - - # @return [Set<Vertex>] the vertices of {#graph} where `self` is an - # {#ancestor?} - def recursive_successors - _recursive_successors - end - - # @param [Set<Vertex>] vertices the set to add the successors to - # @return [Set<Vertex>] the vertices of {#graph} where `self` is an - # {#ancestor?} - def _recursive_successors(vertices = new_vertex_set) - outgoing_edges.each do |edge| - vertex = edge.destination - next unless vertices.add?(vertex) - vertex._recursive_successors(vertices) - end - - vertices - end - protected :_recursive_successors - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{name}(#{payload.inspect})" - end - - # @return [Boolean] whether the two vertices are equal, determined - # by a recursive traversal of each {Vertex#successors} - def ==(other) - return true if equal?(other) - shallow_eql?(other) && - successors.to_set == other.successors.to_set - end - - # @param [Vertex] other the other vertex to compare to - # @return [Boolean] whether the two vertices are equal, determined - # solely by {#name} and {#payload} equality - def shallow_eql?(other) - return true if equal?(other) - other && - name == other.name && - payload == other.payload - end - - alias eql? == - - # @return [Fixnum] a hash for the vertex based upon its {#name} - def hash - name.hash - end - - # Is there a path from `self` to `other` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def path_to?(other) - _path_to?(other) - end - - alias descendent? path_to? - - # @param [Vertex] other the vertex to check if there's a path to - # @param [Set<Vertex>] visited the vertices of {#graph} that have been visited - # @return [Boolean] whether there is a path to `other` from `self` - def _path_to?(other, visited = new_vertex_set) - return false unless visited.add?(self) - return true if equal?(other) - successors.any? { |v| v._path_to?(other, visited) } - end - protected :_path_to? - - # Is there a path from `other` to `self` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def ancestor?(other) - other.path_to?(self) - end - - alias is_reachable_from? ancestor? - - def new_vertex_set - require 'set' - Set.new - end - private :new_vertex_set - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb deleted file mode 100644 index 8c8cafb447..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # An error that occurred during the resolution process - class ResolverError < StandardError; end - - # An error caused by searching for a dependency that is completely unknown, - # i.e. has no versions available whatsoever. - class NoSuchDependencyError < ResolverError - # @return [Object] the dependency that could not be found - attr_accessor :dependency - - # @return [Array<Object>] the specifications that depended upon {#dependency} - attr_accessor :required_by - - # Initializes a new error with the given missing dependency. - # @param [Object] dependency @see {#dependency} - # @param [Array<Object>] required_by @see {#required_by} - def initialize(dependency, required_by = []) - @dependency = dependency - @required_by = required_by.uniq - super() - end - - # The error message for the missing dependency, including the specifications - # that had this dependency. - def message - sources = required_by.map { |r| "`#{r}`" }.join(' and ') - message = "Unable to find a specification for `#{dependency}`" - message += " depended upon by #{sources}" unless sources.empty? - message - end - end - - # An error caused by attempting to fulfil a dependency that was circular - # - # @note This exception will be thrown if and only if a {Vertex} is added to a - # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an - # existing {DependencyGraph::Vertex} - class CircularDependencyError < ResolverError - # [Set<Object>] the dependencies responsible for causing the error - attr_reader :dependencies - - # Initializes a new error with the given circular vertices. - # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency - # that caused the error - def initialize(vertices) - super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" - @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set - end - end - - # An error caused by conflicts in version - class VersionConflict < ResolverError - # @return [{String => Resolution::Conflict}] the conflicts that caused - # resolution to fail - attr_reader :conflicts - - # @return [SpecificationProvider] the specification provider used during - # resolution - attr_reader :specification_provider - - # Initializes a new error with the given version conflicts. - # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} - # @param [SpecificationProvider] specification_provider see {#specification_provider} - def initialize(conflicts, specification_provider) - pairs = [] - conflicts.values.flat_map(&:requirements).each do |conflicting| - conflicting.each do |source, conflict_requirements| - conflict_requirements.each do |c| - pairs << [c, source] - end - end - end - - super "Unable to satisfy the following requirements:\n\n" \ - "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" - - @conflicts = conflicts - @specification_provider = specification_provider - end - - require_relative 'delegates/specification_provider' - include Delegates::SpecificationProvider - - # @return [String] An error message that includes requirement trees, - # which is much more detailed & customizable than the default message - # @param [Hash] opts the options to create a message with. - # @option opts [String] :solver_name The user-facing name of the solver - # @option opts [String] :possibility_type The generic name of a possibility - # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees - # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements - # @option opts [Proc] :additional_message_for_conflict A proc that appends additional - # messages for each conflict - # @option opts [Proc] :version_for_spec A proc that returns the version number for a - # possibility - def message_with_trees(opts = {}) - solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } - possibility_type = opts.delete(:possibility_type) { 'possibility named' } - reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } - printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } - additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } - version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } - incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do - proc do |name, _conflict| - %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) - end - end - - full_message_for_conflict = opts.delete(:full_message_for_conflict) do - proc do |name, conflict| - o = "\n".dup << incompatible_version_message_for_conflict.call(name, conflict) << "\n" - if conflict.locked_requirement - o << %( In snapshot (#{name_for_locking_dependency_source}):\n) - o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) - o << %(\n) - end - o << %( In #{name_for_explicit_dependency_source}:\n) - trees = reduce_trees.call(conflict.requirement_trees) - - o << trees.map do |tree| - t = ''.dup - depth = 2 - tree.each do |req| - t << ' ' * depth << printable_requirement.call(req) - unless tree.last == req - if spec = conflict.activated_by_name[name_for(req)] - t << %( was resolved to #{version_for_spec.call(spec)}, which) - end - t << %( depends on) - end - t << %(\n) - depth += 1 - end - t - end.join("\n") - - additional_message_for_conflict.call(o, name, conflict) - - o - end - end - - conflicts.sort.reduce(''.dup) do |o, (name, conflict)| - o << full_message_for_conflict.call(name, conflict) - end.strip - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb deleted file mode 100644 index a0cfc21672..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # The version of Bundler::Molinillo. - VERSION = '0.8.0'.freeze -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb deleted file mode 100644 index eeae79af3c..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # Provides information about specifications and dependencies to the resolver, - # allowing the {Resolver} class to remain generic while still providing power - # and flexibility. - # - # This module contains the methods that users of Bundler::Molinillo must to implement, - # using knowledge of their own model classes. - module SpecificationProvider - # Search for the specifications that match the given dependency. - # The specifications in the returned array will be considered in reverse - # order, so the latest version ought to be last. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [Array<Object>] the specifications that satisfy the given - # `dependency`. - def search_for(dependency) - [] - end - - # Returns the dependencies of `specification`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `specification` parameter. - # - # @param [Object] specification - # @return [Array<Object>] the dependencies that are required by the given - # `specification`. - def dependencies_for(specification) - [] - end - - # Determines whether the given `requirement` is satisfied by the given - # `spec`, in the context of the current `activated` dependency graph. - # - # @param [Object] requirement - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [Object] spec - # @return [Boolean] whether `requirement` is satisfied by `spec` in the - # context of the current `activated` dependency graph. - def requirement_satisfied_by?(requirement, activated, spec) - true - end - - # Determines whether two arrays of dependencies are equal, and thus can be - # grouped. - # - # @param [Array<Object>] dependencies - # @param [Array<Object>] other_dependencies - # @return [Boolean] whether `dependencies` and `other_dependencies` should - # be considered equal. - def dependencies_equal?(dependencies, other_dependencies) - dependencies == other_dependencies - end - - # Returns the name for the given `dependency`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [String] the name for the given `dependency`. - def name_for(dependency) - dependency.to_s - end - - # @return [String] the name of the source of explicit dependencies, i.e. - # those passed to {Resolver#resolve} directly. - def name_for_explicit_dependency_source - 'user-specified dependency' - end - - # @return [String] the name of the source of 'locked' dependencies, i.e. - # those passed to {Resolver#resolve} directly as the `base` - def name_for_locking_dependency_source - 'Lockfile' - end - - # Sort dependencies so that the ones that are easiest to resolve are first. - # Easiest to resolve is (usually) defined by: - # 1) Is this dependency already activated? - # 2) How relaxed are the requirements? - # 3) Are there any conflicts for this dependency? - # 4) How many possibilities are there to satisfy this dependency? - # - # @param [Array<Object>] dependencies - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [{String => Array<Conflict>}] conflicts - # @return [Array<Object>] a sorted copy of `dependencies`. - def sort_dependencies(dependencies, activated, conflicts) - dependencies.sort_by do |dependency| - name = name_for(dependency) - [ - activated.vertex_named(name).payload ? 0 : 1, - conflicts[name] ? 0 : 1, - ] - end - end - - # Returns whether this dependency, which has no possible matching - # specifications, can safely be ignored. - # - # @param [Object] dependency - # @return [Boolean] whether this dependency can safely be skipped. - def allow_missing?(dependency) - false - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb deleted file mode 100644 index a166bc6991..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # Conveys information about the resolution process to a user. - module UI - # The {IO} object that should be used to print output. `STDOUT`, by default. - # - # @return [IO] - def output - STDOUT - end - - # Called roughly every {#progress_rate}, this method should convey progress - # to the user. - # - # @return [void] - def indicate_progress - output.print '.' unless debug? - end - - # How often progress should be conveyed to the user via - # {#indicate_progress}, in seconds. A third of a second, by default. - # - # @return [Float] - def progress_rate - 0.33 - end - - # Called before resolution begins. - # - # @return [void] - def before_resolution - output.print 'Resolving dependencies...' - end - - # Called after resolution ends (either successfully or with an error). - # By default, prints a newline. - # - # @return [void] - def after_resolution - output.puts - end - - # Conveys debug information to the user. - # - # @param [Integer] depth the current depth of the resolution process. - # @return [void] - def debug(depth = 0) - if debug? - debug_info = yield - debug_info = debug_info.inspect unless debug_info.is_a?(String) - debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } - output.puts debug_info - end - end - - # Whether or not debug messages should be printed. - # By default, whether or not the `MOLINILLO_DEBUG` environment variable is - # set. - # - # @return [Boolean] - def debug? - return @debug_mode if defined?(@debug_mode) - @debug_mode = ENV['MOLINILLO_DEBUG'] - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb deleted file mode 100644 index c689ca7635..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +++ /dev/null @@ -1,839 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class Resolver - # A specific resolution from a given {Resolver} - class Resolution - # A conflict that the resolution process encountered - # @attr [Object] requirement the requirement that immediately led to the conflict - # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict - # @attr [Object, nil] existing the existing spec that was in conflict with - # the {#possibility} - # @attr [Object] possibility_set the set of specs that was unable to be - # activated due to a conflict. - # @attr [Object] locked_requirement the relevant locking requirement. - # @attr [Array<Array<Object>>] requirement_trees the different requirement - # trees that led to every requirement for the conflicting name. - # @attr [{String=>Object}] activated_by_name the already-activated specs. - # @attr [Object] underlying_error an error that has occurred during resolution, and - # will be raised at the end of it if no resolution is found. - Conflict = Struct.new( - :requirement, - :requirements, - :existing, - :possibility_set, - :locked_requirement, - :requirement_trees, - :activated_by_name, - :underlying_error - ) - - class Conflict - # @return [Object] a spec that was unable to be activated due to a conflict - def possibility - possibility_set && possibility_set.latest_version - end - end - - # A collection of possibility states that share the same dependencies - # @attr [Array] dependencies the dependencies for this set of possibilities - # @attr [Array] possibilities the possibilities - PossibilitySet = Struct.new(:dependencies, :possibilities) - - class PossibilitySet - # String representation of the possibility set, for debugging - def to_s - "[#{possibilities.join(', ')}]" - end - - # @return [Object] most up-to-date dependency in the possibility set - def latest_version - possibilities.last - end - end - - # Details of the state to unwind to when a conflict occurs, and the cause of the unwind - # @attr [Integer] state_index the index of the state to unwind to - # @attr [Object] state_requirement the requirement of the state we're unwinding to - # @attr [Array] requirement_tree for the requirement we're relaxing - # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict - # @attr [Array] requirement_trees for the conflict - # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind - UnwindDetails = Struct.new( - :state_index, - :state_requirement, - :requirement_tree, - :conflicting_requirements, - :requirement_trees, - :requirements_unwound_to_instead - ) - - class UnwindDetails - include Comparable - - # We compare UnwindDetails when choosing which state to unwind to. If - # two options have the same state_index we prefer the one most - # removed from a requirement that caused the conflict. Both options - # would unwind to the same state, but a `grandparent` option will - # filter out fewer of its possibilities after doing so - where a state - # is both a `parent` and a `grandparent` to requirements that have - # caused a conflict this is the correct behaviour. - # @param [UnwindDetail] other UnwindDetail to be compared - # @return [Integer] integer specifying ordering - def <=>(other) - if state_index > other.state_index - 1 - elsif state_index == other.state_index - reversed_requirement_tree_index <=> other.reversed_requirement_tree_index - else - -1 - end - end - - # @return [Integer] index of state requirement in reversed requirement tree - # (the conflicting requirement itself will be at position 0) - def reversed_requirement_tree_index - @reversed_requirement_tree_index ||= - if state_requirement - requirement_tree.reverse.index(state_requirement) - else - 999_999 - end - end - - # @return [Boolean] where the requirement of the state we're unwinding - # to directly caused the conflict. Note: in this case, it is - # impossible for the state we're unwinding to to be a parent of - # any of the other conflicting requirements (or we would have - # circularity) - def unwinding_to_primary_requirement? - requirement_tree.last == state_requirement - end - - # @return [Array] array of sub-dependencies to avoid when choosing a - # new possibility for the state we've unwound to. Only relevant for - # non-primary unwinds - def sub_dependencies_to_avoid - @requirements_to_avoid ||= - requirement_trees.map do |tree| - index = tree.index(state_requirement) - tree[index + 1] if index - end.compact - end - - # @return [Array] array of all the requirements that led to the need for - # this unwind - def all_requirements - @all_requirements ||= requirement_trees.flatten(1) - end - end - - # @return [SpecificationProvider] the provider that knows about - # dependencies, requirements, specifications, versions, etc. - attr_reader :specification_provider - - # @return [UI] the UI that knows how to communicate feedback about the - # resolution process back to the user - attr_reader :resolver_ui - - # @return [DependencyGraph] the base dependency graph to which - # dependencies should be 'locked' - attr_reader :base - - # @return [Array] the dependencies that were explicitly required - attr_reader :original_requested - - # Initializes a new resolution. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui see {#resolver_ui} - # @param [Array] requested see {#original_requested} - # @param [DependencyGraph] base see {#base} - def initialize(specification_provider, resolver_ui, requested, base) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - @original_requested = requested - @base = base - @states = [] - @iteration_counter = 0 - @parents_of = Hash.new { |h, k| h[k] = [] } - end - - # Resolves the {#original_requested} dependencies into a full dependency - # graph - # @raise [ResolverError] if successful resolution is impossible - # @return [DependencyGraph] the dependency graph of successfully resolved - # dependencies - def resolve - start_resolution - - while state - break if !state.requirement && state.requirements.empty? - indicate_progress - if state.respond_to?(:pop_possibility_state) # DependencyState - debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } - state.pop_possibility_state.tap do |s| - if s - states.push(s) - activated.tag(s) - end - end - end - process_topmost_state - end - - resolve_activated_specs - ensure - end_resolution - end - - # @return [Integer] the number of resolver iterations in between calls to - # {#resolver_ui}'s {UI#indicate_progress} method - attr_accessor :iteration_rate - private :iteration_rate - - # @return [Time] the time at which resolution began - attr_accessor :started_at - private :started_at - - # @return [Array<ResolutionState>] the stack of states for the resolution - attr_accessor :states - private :states - - private - - # Sets up the resolution process - # @return [void] - def start_resolution - @started_at = Time.now - - push_initial_state - - debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } - resolver_ui.before_resolution - end - - def resolve_activated_specs - activated.vertices.each do |_, vertex| - next unless vertex.payload - - latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| - vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } - end - - activated.set_payload(vertex.name, latest_version) - end - activated.freeze - end - - # Ends the resolution process - # @return [void] - def end_resolution - resolver_ui.after_resolution - debug do - "Finished resolution (#{@iteration_counter} steps) " \ - "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" - end - debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state - debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state - end - - require_relative 'state' - require_relative 'modules/specification_provider' - - require_relative 'delegates/resolution_state' - require_relative 'delegates/specification_provider' - - include Bundler::Molinillo::Delegates::ResolutionState - include Bundler::Molinillo::Delegates::SpecificationProvider - - # Processes the topmost available {RequirementState} on the stack - # @return [void] - def process_topmost_state - if possibility - attempt_to_activate - else - create_conflict - unwind_for_conflict - end - rescue CircularDependencyError => underlying_error - create_conflict(underlying_error) - unwind_for_conflict - end - - # @return [Object] the current possibility that the resolution is trying - # to activate - def possibility - possibilities.last - end - - # @return [RequirementState] the current state the resolution is - # operating upon - def state - states.last - end - - # Creates and pushes the initial state for the resolution, based upon the - # {#requested} dependencies - # @return [void] - def push_initial_state - graph = DependencyGraph.new.tap do |dg| - original_requested.each do |requested| - vertex = dg.add_vertex(name_for(requested), nil, true) - vertex.explicit_requirements << requested - end - dg.tag(:initial_state) - end - - push_state_for_requirements(original_requested, true, graph) - end - - # Unwinds the states stack because a conflict has been encountered - # @return [void] - def unwind_for_conflict - details_for_unwind = build_details_for_unwind - unwind_options = unused_unwind_options - debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } - conflicts.tap do |c| - sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) - raise_error_unless_state(c) - activated.rewind_to(sliced_states.first || :initial_state) if sliced_states - state.conflicts = c - state.unused_unwind_options = unwind_options - filter_possibilities_after_unwind(details_for_unwind) - index = states.size - 1 - @parents_of.each { |_, a| a.reject! { |i| i >= index } } - state.unused_unwind_options.reject! { |uw| uw.state_index >= index } - end - end - - # Raises a VersionConflict error, or any underlying error, if there is no - # current state - # @return [void] - def raise_error_unless_state(conflicts) - return if state - - error = conflicts.values.map(&:underlying_error).compact.first - raise error || VersionConflict.new(conflicts, specification_provider) - end - - # @return [UnwindDetails] Details of the nearest index to which we could unwind - def build_details_for_unwind - # Get the possible unwinds for the current conflict - current_conflict = conflicts[name] - binding_requirements = binding_requirements_for_conflict(current_conflict) - unwind_details = unwind_options_for_requirements(binding_requirements) - - last_detail_for_current_unwind = unwind_details.sort.last - current_detail = last_detail_for_current_unwind - - # Look for past conflicts that could be unwound to affect the - # requirement tree for the current conflict - all_reqs = last_detail_for_current_unwind.all_requirements - all_reqs_size = all_reqs.size - relevant_unused_unwinds = unused_unwind_options.select do |alternative| - diff_reqs = all_reqs - alternative.requirements_unwound_to_instead - next if diff_reqs.size == all_reqs_size - # Find the highest index unwind whilst looping through - current_detail = alternative if alternative > current_detail - alternative - end - - # Add the current unwind options to the `unused_unwind_options` array. - # The "used" option will be filtered out during `unwind_for_conflict`. - state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } - - # Update the requirements_unwound_to_instead on any relevant unused unwinds - relevant_unused_unwinds.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - unwind_details.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - - current_detail - end - - # @param [Array<Object>] binding_requirements array of requirements that combine to create a conflict - # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance - # of resolving the passed requirements - def unwind_options_for_requirements(binding_requirements) - unwind_details = [] - - trees = [] - binding_requirements.reverse_each do |r| - partial_tree = [r] - trees << partial_tree - unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) - - # If this requirement has alternative possibilities, check if any would - # satisfy the other requirements that created this conflict - requirement_state = find_state_for(r) - if conflict_fixing_possibilities?(requirement_state, binding_requirements) - unwind_details << UnwindDetails.new( - states.index(requirement_state), - r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Next, look at the parent of this requirement, and check if the requirement - # could have been avoided if an alternative PossibilitySet had been chosen - parent_r = parent_of(r) - next if parent_r.nil? - partial_tree.unshift(parent_r) - requirement_state = find_state_for(parent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - parent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Finally, look at the grandparent and up of this requirement, looking - # for any possibilities that wouldn't create their parent requirement - grandparent_r = parent_of(parent_r) - until grandparent_r.nil? - partial_tree.unshift(grandparent_r) - requirement_state = find_state_for(grandparent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - grandparent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - parent_r = grandparent_r - grandparent_r = parent_of(parent_r) - end - end - - unwind_details - end - - # @param [DependencyState] state - # @param [Array] binding_requirements array of requirements - # @return [Boolean] whether or not the given state has any possibilities - # that could satisfy the given requirements - def conflict_fixing_possibilities?(state, binding_requirements) - return false unless state - - state.possibilities.any? do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, binding_requirements) - end - end - end - - # Filter's a state's possibilities to remove any that would not fix the - # conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just - # unwound from - # @return [void] - def filter_possibilities_after_unwind(unwind_details) - return unless state && !state.possibilities.empty? - - if unwind_details.unwinding_to_primary_requirement? - filter_possibilities_for_primary_unwind(unwind_details) - else - filter_possibilities_for_parent_unwind(unwind_details) - end - end - - # Filter's a state's possibilities to remove any that would not satisfy - # the requirements in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_primary_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) - - state.possibilities.reject! do |possibility_set| - possibility_set.possibilities.none? do |poss| - unwind_requirement_sets.any? do |requirements| - possibility_satisfies_requirements?(poss, requirements) - end - end - end - end - - # @param [Object] possibility a single possibility - # @param [Array] requirements an array of requirements - # @return [Boolean] whether the possibility satisfies all of the - # given requirements - def possibility_satisfies_requirements?(possibility, requirements) - name = name_for(possibility) - - activated.tag(:swap) - activated.set_payload(name, possibility) if activated.vertex_named(name) - satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } - activated.rewind_to(:swap) - - satisfied - end - - # Filter's a state's possibilities to remove any that would (eventually) - # create a requirement in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_parent_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - - primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq - parent_unwinds = unwinds_to_state.uniq - primary_unwinds - - allowed_possibility_sets = primary_unwinds.flat_map do |unwind| - states[unwind.state_index].possibilities.select do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) - end - end - end - - requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) - - state.possibilities.reject! do |possibility_set| - !allowed_possibility_sets.include?(possibility_set) && - (requirements_to_avoid - possibility_set.dependencies).empty? - end - end - - # @param [Conflict] conflict - # @return [Array] minimal array of requirements that would cause the passed - # conflict to occur. - def binding_requirements_for_conflict(conflict) - return [conflict.requirement] if conflict.possibility.nil? - - possible_binding_requirements = conflict.requirements.values.flatten(1).uniq - - # When there's a `CircularDependency` error the conflicting requirement - # (the one causing the circular) won't be `conflict.requirement` - # (which won't be for the right state, because we won't have created it, - # because it's circular). - # We need to make sure we have that requirement in the conflict's list, - # otherwise we won't be able to unwind properly, so we just return all - # the requirements for the conflict. - return possible_binding_requirements if conflict.underlying_error - - possibilities = search_for(conflict.requirement) - - # If all the requirements together don't filter out all possibilities, - # then the only two requirements we need to consider are the initial one - # (where the dependency's version was first chosen) and the last - if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) - return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact - end - - # Loop through the possible binding requirements, removing each one - # that doesn't bind. Use a `reverse_each` as we want the earliest set of - # binding requirements, and don't use `reject!` as we wish to refine the - # array *on each iteration*. - binding_requirements = possible_binding_requirements.dup - possible_binding_requirements.reverse_each do |req| - next if req == conflict.requirement - unless binding_requirement_in_set?(req, binding_requirements, possibilities) - binding_requirements -= [req] - end - end - - binding_requirements - end - - # @param [Object] requirement we wish to check - # @param [Array] possible_binding_requirements array of requirements - # @param [Array] possibilities array of possibilities the requirements will be used to filter - # @return [Boolean] whether or not the given requirement is required to filter - # out all elements of the array of possibilities. - def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) - possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) - end - end - - # @param [Object] requirement - # @return [Object] the requirement that led to `requirement` being added - # to the list of requirements. - def parent_of(requirement) - return unless requirement - return unless index = @parents_of[requirement].last - return unless parent_state = @states[index] - parent_state.requirement - end - - # @param [String] name - # @return [Object] the requirement that led to a version of a possibility - # with the given name being activated. - def requirement_for_existing_name(name) - return nil unless vertex = activated.vertex_named(name) - return nil unless vertex.payload - states.find { |s| s.name == name }.requirement - end - - # @param [Object] requirement - # @return [ResolutionState] the state whose `requirement` is the given - # `requirement`. - def find_state_for(requirement) - return nil unless requirement - states.find { |i| requirement == i.requirement } - end - - # @param [Object] underlying_error - # @return [Conflict] a {Conflict} that reflects the failure to activate - # the {#possibility} in conjunction with the current {#state} - def create_conflict(underlying_error = nil) - vertex = activated.vertex_named(name) - locked_requirement = locked_requirement_named(name) - - requirements = {} - unless vertex.explicit_requirements.empty? - requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements - end - requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement - vertex.incoming_edges.each do |edge| - (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) - end - - activated_by_name = {} - activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } - conflicts[name] = Conflict.new( - requirement, - requirements, - vertex.payload && vertex.payload.latest_version, - possibility, - locked_requirement, - requirement_trees, - activated_by_name, - underlying_error - ) - end - - # @return [Array<Array<Object>>] The different requirement - # trees that led to every requirement for the current spec. - def requirement_trees - vertex = activated.vertex_named(name) - vertex.requirements.map { |r| requirement_tree_for(r) } - end - - # @param [Object] requirement - # @return [Array<Object>] the list of requirements that led to - # `requirement` being required. - def requirement_tree_for(requirement) - tree = [] - while requirement - tree.unshift(requirement) - requirement = parent_of(requirement) - end - tree - end - - # Indicates progress roughly once every second - # @return [void] - def indicate_progress - @iteration_counter += 1 - @progress_rate ||= resolver_ui.progress_rate - if iteration_rate.nil? - if Time.now - started_at >= @progress_rate - self.iteration_rate = @iteration_counter - end - end - - if iteration_rate && (@iteration_counter % iteration_rate) == 0 - resolver_ui.indicate_progress - end - end - - # Calls the {#resolver_ui}'s {UI#debug} method - # @param [Integer] depth the depth of the {#states} stack - # @param [Proc] block a block that yields a {#to_s} - # @return [void] - def debug(depth = 0, &block) - resolver_ui.debug(depth, &block) - end - - # Attempts to activate the current {#possibility} - # @return [void] - def attempt_to_activate - debug(depth) { 'Attempting to activate ' + possibility.to_s } - existing_vertex = activated.vertex_named(name) - if existing_vertex.payload - debug(depth) { "Found existing spec (#{existing_vertex.payload})" } - attempt_to_filter_existing_spec(existing_vertex) - else - latest = possibility.latest_version - possibility.possibilities.select! do |possibility| - requirement_satisfied_by?(requirement, activated, possibility) - end - if possibility.latest_version.nil? - # ensure there's a possibility for better error messages - possibility.possibilities << latest if latest - create_conflict - unwind_for_conflict - else - activate_new_spec - end - end - end - - # Attempts to update the existing vertex's `PossibilitySet` with a filtered version - # @return [void] - def attempt_to_filter_existing_spec(vertex) - filtered_set = filtered_possibility_set(vertex) - if !filtered_set.possibilities.empty? - activated.set_payload(name, filtered_set) - new_requirements = requirements.dup - push_state_for_requirements(new_requirements, false) - else - create_conflict - debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } - unwind_for_conflict - end - end - - # Generates a filtered version of the existing vertex's `PossibilitySet` using the - # current state's `requirement` - # @param [Object] vertex existing vertex - # @return [PossibilitySet] filtered possibility set - def filtered_possibility_set(vertex) - PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) - end - - # @param [String] requirement_name the spec name to search for - # @return [Object] the locked spec named `requirement_name`, if one - # is found on {#base} - def locked_requirement_named(requirement_name) - vertex = base.vertex_named(requirement_name) - vertex && vertex.payload - end - - # Add the current {#possibility} to the dependency graph of the current - # {#state} - # @return [void] - def activate_new_spec - conflicts.delete(name) - debug(depth) { "Activated #{name} at #{possibility}" } - activated.set_payload(name, possibility) - require_nested_dependencies_for(possibility) - end - - # Requires the dependencies that the recently activated spec has - # @param [Object] possibility_set the PossibilitySet that has just been - # activated - # @return [void] - def require_nested_dependencies_for(possibility_set) - nested_dependencies = dependencies_for(possibility_set.latest_version) - debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } - nested_dependencies.each do |d| - activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) - parent_index = states.size - 1 - parents = @parents_of[d] - parents << parent_index if parents.empty? - end - - push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) - end - - # Pushes a new {DependencyState} that encapsulates both existing and new - # requirements - # @param [Array] new_requirements - # @param [Boolean] requires_sort - # @param [Object] new_activated - # @return [void] - def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) - new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort - new_requirement = nil - loop do - new_requirement = new_requirements.shift - break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } - end - new_name = new_requirement ? name_for(new_requirement) : ''.freeze - possibilities = possibilities_for_requirement(new_requirement) - handle_missing_or_push_dependency_state DependencyState.new( - new_name, new_requirements, new_activated, - new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup - ) - end - - # Checks a proposed requirement with any existing locked requirement - # before generating an array of possibilities for it. - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibilities - def possibilities_for_requirement(requirement, activated = self.activated) - return [] unless requirement - if locked_requirement_named(name_for(requirement)) - return locked_requirement_possibility_set(requirement, activated) - end - - group_possibilities(search_for(requirement)) - end - - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibility set containing only the locked requirement, if any - def locked_requirement_possibility_set(requirement, activated = self.activated) - all_possibilities = search_for(requirement) - locked_requirement = locked_requirement_named(name_for(requirement)) - - # Longwinded way to build a possibilities array with either the locked - # requirement or nothing in it. Required, since the API for - # locked_requirement isn't guaranteed. - locked_possibilities = all_possibilities.select do |possibility| - requirement_satisfied_by?(locked_requirement, activated, possibility) - end - - group_possibilities(locked_possibilities) - end - - # Build an array of PossibilitySets, with each element representing a group of - # dependency versions that all have the same sub-dependency version constraints - # and are contiguous. - # @param [Array] possibilities an array of possibilities - # @return [Array<PossibilitySet>] an array of possibility sets - def group_possibilities(possibilities) - possibility_sets = [] - current_possibility_set = nil - - possibilities.reverse_each do |possibility| - dependencies = dependencies_for(possibility) - if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) - current_possibility_set.possibilities.unshift(possibility) - else - possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) - current_possibility_set = possibility_sets.first - end - end - - possibility_sets - end - - # Pushes a new {DependencyState}. - # If the {#specification_provider} says to - # {SpecificationProvider#allow_missing?} that particular requirement, and - # there are no possibilities for that requirement, then `state` is not - # pushed, and the vertex in {#activated} is removed, and we continue - # resolving the remaining requirements. - # @param [DependencyState] state - # @return [void] - def handle_missing_or_push_dependency_state(state) - if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) - state.activated.detach_vertex_named(state.name) - push_state_for_requirements(state.requirements.dup, false, state.activated) - else - states.push(state).tap { activated.tag(state) } - end - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb deleted file mode 100644 index 95eaab5991..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'dependency_graph' - -module Bundler::Molinillo - # This class encapsulates a dependency resolver. - # The resolver is responsible for determining which set of dependencies to - # activate, with feedback from the {#specification_provider} - # - # - class Resolver - require_relative 'resolution' - - # @return [SpecificationProvider] the specification provider used - # in the resolution process - attr_reader :specification_provider - - # @return [UI] the UI module used to communicate back to the user - # during the resolution process - attr_reader :resolver_ui - - # Initializes a new resolver. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui - # see {#resolver_ui} - def initialize(specification_provider, resolver_ui) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - end - - # Resolves the requested dependencies into a {DependencyGraph}, - # locking to the base dependency graph (if specified) - # @param [Array] requested an array of 'requested' dependencies that the - # {#specification_provider} can understand - # @param [DependencyGraph,nil] base the base dependency graph to which - # dependencies should be 'locked' - def resolve(requested, base = DependencyGraph.new) - Resolution.new(specification_provider, - resolver_ui, - requested, - base). - resolve - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb deleted file mode 100644 index 68fa1f54e3..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # A state that a {Resolution} can be in - # @attr [String] name the name of the current requirement - # @attr [Array<Object>] requirements currently unsatisfied requirements - # @attr [DependencyGraph] activated the graph of activated dependencies - # @attr [Object] requirement the current requirement - # @attr [Object] possibilities the possibilities to satisfy the current requirement - # @attr [Integer] depth the depth of the resolution - # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name - # @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored - ResolutionState = Struct.new( - :name, - :requirements, - :activated, - :requirement, - :possibilities, - :depth, - :conflicts, - :unused_unwind_options - ) - - class ResolutionState - # Returns an empty resolution state - # @return [ResolutionState] an empty state - def self.empty - new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) - end - end - - # A state that encapsulates a set of {#requirements} with an {Array} of - # possibilities - class DependencyState < ResolutionState - # Removes a possibility from `self` - # @return [PossibilityState] a state with a single possibility, - # the possibility that was removed from `self` - def pop_possibility_state - PossibilityState.new( - name, - requirements.dup, - activated, - requirement, - [possibilities.pop], - depth + 1, - conflicts.dup, - unused_unwind_options.dup - ).tap do |state| - state.activated.tag(state) - end - end - end - - # A state that encapsulates a single possibility to fulfill the given - # {#requirement} - class PossibilityState < ResolutionState - end -end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub.rb new file mode 100644 index 0000000000..eaaba3fc98 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub.rb @@ -0,0 +1,31 @@ +require_relative "pub_grub/package" +require_relative "pub_grub/static_package_source" +require_relative "pub_grub/term" +require_relative "pub_grub/version_range" +require_relative "pub_grub/version_constraint" +require_relative "pub_grub/version_union" +require_relative "pub_grub/version_solver" +require_relative "pub_grub/incompatibility" +require_relative 'pub_grub/solve_failure' +require_relative 'pub_grub/failure_writer' +require_relative 'pub_grub/version' + +module Bundler::PubGrub + class << self + attr_writer :logger + + def logger + @logger || default_logger + end + + private + + def default_logger + require "logger" + + logger = ::Logger.new(STDERR) + logger.level = $DEBUG ? ::Logger::DEBUG : ::Logger::WARN + @logger = logger + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb new file mode 100644 index 0000000000..2236a97b5b --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb @@ -0,0 +1,20 @@ +module Bundler::PubGrub + class Assignment + attr_reader :term, :cause, :decision_level, :index + def initialize(term, cause, decision_level, index) + @term = term + @cause = cause + @decision_level = decision_level + @index = index + end + + def self.decision(package, version, decision_level, index) + term = Term.new(VersionConstraint.exact(package, version), true) + new(term, :decision, decision_level, index) + end + + def decision? + cause == :decision + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb new file mode 100644 index 0000000000..dce20d37ad --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb @@ -0,0 +1,189 @@ +require_relative 'version_constraint' +require_relative 'incompatibility' + +module Bundler::PubGrub + # Types: + # + # Where possible, Bundler::PubGrub will accept user-defined types, so long as they quack. + # + # ## "Package": + # + # This class will be used to represent the various packages being solved for. + # .to_s will be called when displaying errors and debugging info, it should + # probably return the package's name. + # It must also have a reasonable definition of #== and #hash + # + # Example classes: String ("rails") + # + # + # ## "Version": + # + # This class will be used to represent a single version number. + # + # Versions don't need to store their associated package, however they will + # only be compared against other versions of the same package. + # + # It must be Comparible (and implement <=> reasonably) + # + # Example classes: Gem::Version, Integer + # + # + # ## "Dependency" + # + # This class represents the requirement one package has on another. It is + # returned by dependencies_for(package, version) and will be passed to + # parse_dependency to convert it to a format Bundler::PubGrub understands. + # + # It must also have a reasonable definition of #== + # + # Example classes: String ("~> 1.0"), Gem::Requirement + # + class BasicPackageSource + # Override me! + # + # This is called per package to find all possible versions of a package. + # + # It is called at most once per-package + # + # Returns: Array of versions for a package, in preferred order of selection + def all_versions_for(package) + raise NotImplementedError + end + + # Override me! + # + # Returns: Hash in the form of { package => requirement, ... } + def dependencies_for(package, version) + raise NotImplementedError + end + + # Override me! + # + # Convert a (user-defined) dependency into a format Bundler::PubGrub understands. + # + # Package is passed to this method but for many implementations is not + # needed. + # + # Returns: either a Bundler::PubGrub::VersionRange, Bundler::PubGrub::VersionUnion, or a + # Bundler::PubGrub::VersionConstraint + def parse_dependency(package, dependency) + raise NotImplementedError + end + + # Override me! + # + # If not overridden, this will call dependencies_for with the root package. + # + # Returns: Hash in the form of { package => requirement, ... } (see dependencies_for) + def root_dependencies + dependencies_for(@root_package, @root_version) + end + + # Override me (maybe) + # + # If not overridden, the order returned by all_versions_for will be used + # + # Returns: Array of versions in preferred order + def sort_versions_by_preferred(package, sorted_versions) + indexes = @version_indexes[package] + sorted_versions.sort_by { |version| indexes[version] } + end + + def initialize + @root_package = Package.root + @root_version = Package.root_version + + @cached_versions = Hash.new do |h,k| + if k == @root_package + h[k] = [@root_version] + else + h[k] = all_versions_for(k) + end + end + @sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort } + @version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h } + + @cached_dependencies = Hash.new do |packages, package| + if package == @root_package + packages[package] = { + @root_version => root_dependencies + } + else + packages[package] = Hash.new do |versions, version| + versions[version] = dependencies_for(package, version) + end + end + end + end + + def versions_for(package, range=VersionRange.any) + versions = range.select_versions(@sorted_versions[package]) + + # Conditional avoids (among other things) calling + # sort_versions_by_preferred with the root package + if versions.size > 1 + sort_versions_by_preferred(package, versions) + else + versions + end + end + + def no_versions_incompatibility_for(_package, unsatisfied_term) + cause = Incompatibility::NoVersions.new(unsatisfied_term) + + Incompatibility.new([unsatisfied_term], cause: cause) + 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_name| + low = high = sorted_versions.index(version) + + # find version low such that all >= low share the same dep + while low > 0 && + package_deps[sorted_versions[low - 1]][dep_package] == dep_constraint_name + low -= 1 + end + low = + if low == 0 + nil + else + sorted_versions[low] + end + + # find version high such that all < high share the same dep + while high < sorted_versions.length && + package_deps[sorted_versions[high]][dep_package] == dep_constraint_name + high += 1 + end + high = + if high == sorted_versions.length + nil + else + sorted_versions[high] + end + + range = VersionRange.new(min: low, max: high, include_min: true) + + self_constraint = VersionConstraint.new(package, range: range) + + if !@packages.include?(dep_package) + # no such package -> this version is invalid + end + + dep_constraint = parse_dependency(dep_package, dep_constraint_name) + if !dep_constraint + # falsey indicates this dependency was invalid + cause = Bundler::PubGrub::Incompatibility::InvalidDependency.new(dep_package, dep_constraint_name) + return [Incompatibility.new([Term.new(self_constraint, true)], cause: cause)] + elsif !dep_constraint.is_a?(VersionConstraint) + # Upgrade range/union to VersionConstraint + dep_constraint = VersionConstraint.new(dep_package, range: dep_constraint) + end + + Incompatibility.new([Term.new(self_constraint, true), Term.new(dep_constraint, false)], cause: :dependency) + end + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb new file mode 100644 index 0000000000..ee099b23f4 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb @@ -0,0 +1,182 @@ +module Bundler::PubGrub + class FailureWriter + def initialize(root) + @root = root + + # { Incompatibility => Integer } + @derivations = {} + + # [ [ String, Integer or nil ] ] + @lines = [] + + # { Incompatibility => Integer } + @line_numbers = {} + + count_derivations(root) + end + + def write + return @root.to_s unless @root.conflict? + + visit(@root) + + padding = @line_numbers.empty? ? 0 : "(#{@line_numbers.values.last}) ".length + + @lines.map do |message, number| + next "" if message.empty? + + lead = number ? "(#{number}) " : "" + lead = lead.ljust(padding) + message = message.gsub("\n", "\n" + " " * (padding + 2)) + "#{lead}#{message}" + end.join("\n") + end + + private + + def write_line(incompatibility, message, numbered:) + if numbered + number = @line_numbers.length + 1 + @line_numbers[incompatibility] = number + end + + @lines << [message, number] + end + + def visit(incompatibility, conclusion: false) + raise unless incompatibility.conflict? + + numbered = conclusion || @derivations[incompatibility] > 1; + conjunction = conclusion || incompatibility == @root ? "So," : "And" + + cause = incompatibility.cause + + if cause.conflict.conflict? && cause.other.conflict? + conflict_line = @line_numbers[cause.conflict] + other_line = @line_numbers[cause.other] + + if conflict_line && other_line + write_line( + incompatibility, + "Because #{cause.conflict} (#{conflict_line})\nand #{cause.other} (#{other_line}),\n#{incompatibility}.", + numbered: numbered + ) + elsif conflict_line || other_line + with_line = conflict_line ? cause.conflict : cause.other + without_line = conflict_line ? cause.other : cause.conflict + line = @line_numbers[with_line] + + visit(without_line); + write_line( + incompatibility, + "#{conjunction} because #{with_line} (#{line}),\n#{incompatibility}.", + numbered: numbered + ) + else + single_line_conflict = single_line?(cause.conflict.cause) + single_line_other = single_line?(cause.other.cause) + + if single_line_conflict || single_line_other + first = single_line_other ? cause.conflict : cause.other + second = single_line_other ? cause.other : cause.conflict + visit(first) + visit(second) + write_line( + incompatibility, + "Thus, #{incompatibility}.", + numbered: numbered + ) + else + visit(cause.conflict, conclusion: true) + @lines << ["", nil] + visit(cause.other) + + write_line( + incompatibility, + "#{conjunction} because #{cause.conflict} (#{@line_numbers[cause.conflict]}),\n#{incompatibility}.", + numbered: numbered + ) + end + end + elsif cause.conflict.conflict? || cause.other.conflict? + derived = cause.conflict.conflict? ? cause.conflict : cause.other + ext = cause.conflict.conflict? ? cause.other : cause.conflict + + derived_line = @line_numbers[derived] + if derived_line + write_line( + incompatibility, + "Because #{ext}\nand #{derived} (#{derived_line}),\n#{incompatibility}.", + numbered: numbered + ) + elsif collapsible?(derived) + derived_cause = derived.cause + if derived_cause.conflict.conflict? + collapsed_derived = derived_cause.conflict + collapsed_ext = derived_cause.other + else + collapsed_derived = derived_cause.other + collapsed_ext = derived_cause.conflict + end + + visit(collapsed_derived) + + write_line( + incompatibility, + "#{conjunction} because #{collapsed_ext}\nand #{ext},\n#{incompatibility}.", + numbered: numbered + ) + else + visit(derived) + write_line( + incompatibility, + "#{conjunction} because #{ext},\n#{incompatibility}.", + numbered: numbered + ) + end + else + write_line( + incompatibility, + "Because #{cause.conflict}\nand #{cause.other},\n#{incompatibility}.", + numbered: numbered + ) + end + end + + def single_line?(cause) + !cause.conflict.conflict? && !cause.other.conflict? + end + + def collapsible?(incompatibility) + return false if @derivations[incompatibility] > 1 + + cause = incompatibility.cause + # If incompatibility is derived from two derived incompatibilities, + # there are too many transitive causes to display concisely. + return false if cause.conflict.conflict? && cause.other.conflict? + + # If incompatibility is derived from two external incompatibilities, it + # tends to be confusing to collapse it. + return false unless cause.conflict.conflict? || cause.other.conflict? + + # If incompatibility's internal cause is numbered, collapsing it would + # get too noisy. + complex = cause.conflict.conflict? ? cause.conflict : cause.other + + !@line_numbers.has_key?(complex) + end + + def count_derivations(incompatibility) + if @derivations.has_key?(incompatibility) + @derivations[incompatibility] += 1 + else + @derivations[incompatibility] = 1 + if incompatibility.conflict? + cause = incompatibility.cause + count_derivations(cause.conflict) + count_derivations(cause.other) + end + end + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb new file mode 100644 index 0000000000..239eaf3401 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb @@ -0,0 +1,150 @@ +module Bundler::PubGrub + class Incompatibility + ConflictCause = Struct.new(:incompatibility, :satisfier) do + alias_method :conflict, :incompatibility + alias_method :other, :satisfier + end + + InvalidDependency = Struct.new(:package, :constraint) do + end + + NoVersions = Struct.new(:constraint) do + end + + attr_reader :terms, :cause + + def initialize(terms, cause:, custom_explanation: nil) + @cause = cause + @terms = cleanup_terms(terms) + @custom_explanation = custom_explanation + + if cause == :dependency && @terms.length != 2 + raise ArgumentError, "a dependency Incompatibility must have exactly two terms. Got #{@terms.inspect}" + end + end + + def hash + cause.hash ^ terms.hash + end + + def eql?(other) + cause.eql?(other.cause) && + terms.eql?(other.terms) + end + + def failure? + terms.empty? || (terms.length == 1 && Package.root?(terms[0].package) && terms[0].positive?) + end + + def conflict? + ConflictCause === cause + end + + # Returns all external incompatibilities in this incompatibility's + # derivation graph + def external_incompatibilities + if conflict? + [ + cause.conflict, + cause.other + ].flat_map(&:external_incompatibilities) + else + [this] + end + end + + def to_s + return @custom_explanation if @custom_explanation + + case cause + when :root + "(root dependency)" + when :dependency + "#{terms[0].to_s(allow_every: true)} depends on #{terms[1].invert}" + when Bundler::PubGrub::Incompatibility::InvalidDependency + "#{terms[0].to_s(allow_every: true)} depends on unknown package #{cause.package}" + when Bundler::PubGrub::Incompatibility::NoVersions + "no versions satisfy #{cause.constraint}" + when Bundler::PubGrub::Incompatibility::ConflictCause + if failure? + "version solving has failed" + elsif terms.length == 1 + term = terms[0] + if term.positive? + if term.constraint.any? + "#{term.package} cannot be used" + else + "#{term.to_s(allow_every: true)} cannot be used" + end + else + "#{term.invert} is required" + end + else + if terms.all?(&:positive?) + if terms.length == 2 + "#{terms[0].to_s(allow_every: true)} is incompatible with #{terms[1]}" + else + "one of #{terms.map(&:to_s).join(" or ")} must be false" + end + elsif terms.all?(&:negative?) + if terms.length == 2 + "either #{terms[0].invert} or #{terms[1].invert}" + else + "one of #{terms.map(&:invert).join(" or ")} must be true"; + end + else + positive = terms.select(&:positive?) + negative = terms.select(&:negative?).map(&:invert) + + if positive.length == 1 + "#{positive[0].to_s(allow_every: true)} requires #{negative.join(" or ")}" + else + "if #{positive.join(" and ")} then #{negative.join(" or ")}" + end + end + end + else + raise "unhandled cause: #{cause.inspect}" + end + end + + def inspect + "#<#{self.class} #{to_s}>" + end + + def pretty_print(q) + q.group 2, "#<#{self.class}", ">" do + q.breakable + q.text to_s + + q.breakable + q.text " caused by " + q.pp @cause + end + end + + private + + def cleanup_terms(terms) + terms.each do |term| + raise "#{term.inspect} must be a term" unless term.is_a?(Term) + end + + if terms.length != 1 && ConflictCause === cause + terms = terms.reject do |term| + term.positive? && Package.root?(term.package) + end + end + + # Optimized simple cases + return terms if terms.length <= 1 + return terms if terms.length == 2 && terms[0].package != terms[1].package + + terms.group_by(&:package).map do |package, common_terms| + common_terms.inject do |acc, term| + acc.intersect(term) + end + end + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb new file mode 100644 index 0000000000..efb9d3da16 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Bundler::PubGrub + class Package + + attr_reader :name + + def initialize(name) + @name = name + end + + def inspect + "#<#{self.class} #{name.inspect}>" + end + + def <=>(other) + name <=> other.name + end + + ROOT = Package.new(:root) + ROOT_VERSION = 0 + + def self.root + ROOT + end + + def self.root_version + ROOT_VERSION + end + + def self.root?(package) + if package.respond_to?(:root?) + package.root? + else + package == root + end + end + + def to_s + name.to_s + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb new file mode 100644 index 0000000000..4c4b8ca844 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb @@ -0,0 +1,121 @@ +require_relative 'assignment' + +module Bundler::PubGrub + class PartialSolution + attr_reader :assignments, :decisions + attr_reader :attempted_solutions + + def initialize + reset! + + @attempted_solutions = 1 + @backtracking = false + end + + def decision_level + @decisions.length + end + + def relation(term) + package = term.package + return :overlap if !@terms.key?(package) + + @relation_cache[package][term] ||= + @terms[package].relation(term) + end + + def satisfies?(term) + relation(term) == :subset + end + + def derive(term, cause) + add_assignment(Assignment.new(term, cause, decision_level, assignments.length)) + end + + def satisfier(term) + assignment = + @assignments_by[term.package].bsearch do |assignment_by| + @cumulative_assignments[assignment_by].satisfies?(term) + end + + assignment || raise("#{term} unsatisfied") + end + + # A list of unsatisfied terms + def unsatisfied + @required.keys.reject do |package| + @decisions.key?(package) + end.map do |package| + @terms[package] + end + end + + def decide(package, version) + @attempted_solutions += 1 if @backtracking + @backtracking = false; + + decisions[package] = version + assignment = Assignment.decision(package, version, decision_level, assignments.length) + add_assignment(assignment) + end + + def backtrack(previous_level) + @backtracking = true + + new_assignments = assignments.select do |assignment| + assignment.decision_level <= previous_level + end + + new_decisions = Hash[decisions.first(previous_level)] + + reset! + + @decisions = new_decisions + + new_assignments.each do |assignment| + add_assignment(assignment) + end + end + + private + + def reset! + # { Array<Assignment> } + @assignments = [] + + # { Package => Array<Assignment> } + @assignments_by = Hash.new { |h,k| h[k] = [] } + @cumulative_assignments = {}.compare_by_identity + + # { Package => Package::Version } + @decisions = {} + + # { Package => Term } + @terms = {} + @relation_cache = Hash.new { |h,k| h[k] = {} } + + # { Package => Boolean } + @required = {} + end + + def add_assignment(assignment) + term = assignment.term + package = term.package + + @assignments << assignment + @assignments_by[package] << assignment + + @required[package] = true if term.positive? + + if @terms.key?(package) + old_term = @terms[package] + @terms[package] = old_term.intersect(term) + else + @terms[package] = term + end + @relation_cache[package].clear + + @cumulative_assignments[assignment] = @terms[package] + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb new file mode 100644 index 0000000000..245c23be22 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb @@ -0,0 +1,45 @@ +module Bundler::PubGrub + module RubyGems + extend self + + def requirement_to_range(requirement) + ranges = requirement.requirements.map do |(op, ver)| + case op + when "~>" + name = "~> #{ver}" + bump = ver.class.new(ver.bump.to_s + ".A") + VersionRange.new(name: name, min: ver, max: bump, include_min: true) + when ">" + VersionRange.new(min: ver) + when ">=" + VersionRange.new(min: ver, include_min: true) + when "<" + VersionRange.new(max: ver) + when "<=" + VersionRange.new(max: ver, include_max: true) + when "=" + VersionRange.new(min: ver, max: ver, include_min: true, include_max: true) + when "!=" + VersionRange.new(min: ver, max: ver, include_min: true, include_max: true).invert + else + raise "bad version specifier: #{op}" + end + end + + ranges.inject(&:intersect) + end + + def requirement_to_constraint(package, requirement) + Bundler::PubGrub::VersionConstraint.new(package, range: requirement_to_range(requirement)) + end + + def parse_range(dep) + requirement_to_range(Gem::Requirement.new(dep)) + end + + def parse_constraint(package, dep) + range = parse_range(dep) + Bundler::PubGrub::VersionConstraint.new(package, range: range) + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb new file mode 100644 index 0000000000..961a7a7c0c --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb @@ -0,0 +1,19 @@ +require_relative 'failure_writer' + +module Bundler::PubGrub + class SolveFailure < StandardError + attr_reader :incompatibility + + def initialize(incompatibility) + @incompatibility = incompatibility + end + + def to_s + "Could not find compatible versions\n\n#{explanation}" + end + + def explanation + @explanation ||= FailureWriter.new(@incompatibility).write + end + end +end 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 new file mode 100644 index 0000000000..4bf61461b2 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb @@ -0,0 +1,60 @@ +require_relative 'package' +require_relative 'version_constraint' +require_relative 'incompatibility' +require_relative 'basic_package_source' + +module Bundler::PubGrub + class StaticPackageSource < BasicPackageSource + class DSL + def initialize(packages, root_deps) + @packages = packages + @root_deps = root_deps + end + + def root(deps:) + @root_deps.update(deps) + end + + def add(name, version, deps: {}) + version = Gem::Version.new(version) + @packages[name] ||= {} + raise ArgumentError, "#{name} #{version} declared twice" if @packages[name].key?(version) + @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 + + def initialize + @root_deps = {} + @packages = {} + + yield DSL.new(@packages, @root_deps) + + super() + end + + def all_versions_for(package) + @packages[package].keys + end + + def root_dependencies + @root_deps + end + + def dependencies_for(package, version) + @packages[package][version] + end + + def parse_dependency(package, dependency) + return false unless @packages.key?(package) + + Bundler::PubGrub::RubyGems.parse_constraint(package, dependency) + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb new file mode 100644 index 0000000000..1d0f763378 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb @@ -0,0 +1,105 @@ +module Bundler::PubGrub + class Term + attr_reader :package, :constraint, :positive + + def initialize(constraint, positive) + @constraint = constraint + @package = @constraint.package + @positive = positive + end + + def to_s(allow_every: false) + if positive + @constraint.to_s(allow_every: allow_every) + else + "not #{@constraint}" + end + end + + def hash + constraint.hash ^ positive.hash + end + + def eql?(other) + positive == other.positive && + constraint.eql?(other.constraint) + end + + def invert + self.class.new(@constraint, !@positive) + end + alias_method :inverse, :invert + + def intersect(other) + raise ArgumentError, "packages must match" if package != other.package + + if positive? && other.positive? + self.class.new(constraint.intersect(other.constraint), true) + elsif negative? && other.negative? + self.class.new(constraint.union(other.constraint), false) + else + positive = positive? ? self : other + negative = negative? ? self : other + self.class.new(positive.constraint.intersect(negative.constraint.invert), true) + end + end + + def difference(other) + intersect(other.invert) + end + + def relation(other) + if positive? && other.positive? + constraint.relation(other.constraint) + elsif negative? && other.positive? + if constraint.allows_all?(other.constraint) + :disjoint + else + :overlap + end + elsif positive? && other.negative? + if !other.constraint.allows_any?(constraint) + :subset + elsif other.constraint.allows_all?(constraint) + :disjoint + else + :overlap + end + elsif negative? && other.negative? + if constraint.allows_all?(other.constraint) + :subset + else + :overlap + end + else + raise + end + end + + def normalized_constraint + @normalized_constraint ||= positive ? constraint : constraint.invert + end + + def satisfies?(other) + raise ArgumentError, "packages must match" unless package == other.package + + relation(other) == :subset + end + + def positive? + @positive + end + + def negative? + !positive? + end + + def empty? + @empty ||= normalized_constraint.empty? + end + + def inspect + "#<#{self.class} #{self}>" + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb new file mode 100644 index 0000000000..d7984b3863 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb @@ -0,0 +1,3 @@ +module Bundler::PubGrub + VERSION = "0.5.0" +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 new file mode 100644 index 0000000000..b71f3eaf53 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb @@ -0,0 +1,129 @@ +require_relative 'version_range' + +module Bundler::PubGrub + class VersionConstraint + attr_reader :package, :range + + # @param package [Bundler::PubGrub::Package] + # @param range [Bundler::PubGrub::VersionRange] + def initialize(package, range: nil) + @package = package + @range = range + end + + def hash + 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) + end + + class << self + def exact(package, version) + range = VersionRange.new(min: version, max: version, include_min: true, include_max: true) + new(package, range: range) + end + + def any(package) + new(package, range: VersionRange.any) + end + + def empty(package) + new(package, range: VersionRange.empty) + end + end + + def intersect(other) + unless package == other.package + raise ArgumentError, "Can only intersect between VersionConstraint of the same package" + end + + self.class.new(package, range: range.intersect(other.range)) + end + + def union(other) + unless package == other.package + raise ArgumentError, "Can only intersect between VersionConstraint of the same package" + end + + self.class.new(package, range: range.union(other.range)) + end + + def invert + new_range = range.invert + self.class.new(package, range: new_range) + end + + def difference(other) + intersect(other.invert) + end + + def allows_all?(other) + range.allows_all?(other.range) + end + + def allows_any?(other) + range.intersects?(other.range) + end + + def subset?(other) + other.allows_all?(self) + end + + def overlap?(other) + other.allows_any?(self) + end + + def disjoint?(other) + !overlap?(other) + end + + def relation(other) + if subset?(other) + :subset + elsif overlap?(other) + :overlap + else + :disjoint + end + end + + def to_s(allow_every: false) + if Package.root?(package) + package.to_s + elsif allow_every && any? + "every version of #{package}" + else + "#{package} #{constraint_string}" + end + end + + def constraint_string + if any? + ">= 0" + else + range.to_s + end + end + + def empty? + range.empty? + end + + # Does this match every version of the package + def any? + range.any? + end + + def inspect + "#<#{self.class} #{self}>" + end + end +end 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 new file mode 100644 index 0000000000..8d73c3f7b5 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +module Bundler::PubGrub + class VersionRange + attr_reader :min, :max, :include_min, :include_max + + alias_method :include_min?, :include_min + alias_method :include_max?, :include_max + + class Empty < VersionRange + undef_method :min, :max + undef_method :include_min, :include_min? + undef_method :include_max, :include_max? + + def initialize + end + + def empty? + true + end + + def eql?(other) + other.empty? + end + + def hash + [].hash + end + + def intersects?(_) + false + end + + def intersect(other) + self + end + + def allows_all?(other) + other.empty? + end + + def include?(_) + false + end + + def any? + false + end + + def to_s + "(no versions)" + end + + def ==(other) + other.class == self.class + end + + def invert + VersionRange.any + end + + def select_versions(_) + [] + end + end + + EMPTY = Empty.new + Empty.singleton_class.undef_method(:new) + + def self.empty + EMPTY + end + + def self.any + new + end + + def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil) + @min = min + @max = max + @include_min = include_min + @include_max = include_max + @name = name + end + + def hash + @hash ||= min.hash ^ max.hash ^ include_min.hash ^ include_max.hash + end + + def eql?(other) + if other.is_a?(VersionRange) + !other.empty? && + min.eql?(other.min) && + max.eql?(other.max) && + include_min.eql?(other.include_min) && + include_max.eql?(other.include_max) + else + ranges.eql?(other.ranges) + end + end + + def ranges + [self] + end + + def include?(version) + compare_version(version) == 0 + end + + # Partitions passed versions into [lower, within, higher] + # + # versions must be sorted + def partition_versions(versions) + min_index = + if !min || versions.empty? + 0 + elsif include_min? + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= min } + else + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > min } + end + + lower = versions.slice(0, min_index) + versions = versions.slice(min_index, versions.size) + + max_index = + if !max || versions.empty? + versions.size + elsif include_max? + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > max } + else + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= max } + end + + [ + lower, + versions.slice(0, max_index), + versions.slice(max_index, versions.size) + ] + end + + # Returns versions which are included by this range. + # + # versions must be sorted + def select_versions(versions) + return versions if any? + + partition_versions(versions)[1] + end + + def compare_version(version) + if min + case version <=> min + when -1 + return -1 + when 0 + return -1 if !include_min + when 1 + end + end + + if max + case version <=> max + when -1 + when 0 + return 1 if !include_max + when 1 + return 1 + end + end + + 0 + end + + def strictly_lower?(other) + return false if !max || !other.min + + case max <=> other.min + when 0 + !include_max || !other.include_min + when -1 + true + when 1 + false + end + end + + def strictly_higher?(other) + other.strictly_lower?(self) + end + + def intersects?(other) + return false if other.empty? + return other.intersects?(self) if other.is_a?(VersionUnion) + !strictly_lower?(other) && !strictly_higher?(other) + end + alias_method :allows_any?, :intersects? + + def intersect(other) + return other if other.empty? + return other.intersect(self) if other.is_a?(VersionUnion) + + min_range = + if !min + other + elsif !other.min + self + else + case min <=> other.min + when 0 + include_min ? other : self + when -1 + other + when 1 + self + end + end + + max_range = + if !max + other + elsif !other.max + self + else + case max <=> other.max + when 0 + include_max ? other : self + when -1 + self + when 1 + other + end + end + + if !min_range.equal?(max_range) && min_range.min && max_range.max + case min_range.min <=> max_range.max + when -1 + when 0 + if !min_range.include_min || !max_range.include_max + return EMPTY + end + when 1 + return EMPTY + end + end + + VersionRange.new( + min: min_range.min, + include_min: min_range.include_min, + max: max_range.max, + include_max: max_range.include_max + ) + end + + # The span covered by two ranges + # + # If self and other are contiguous, this builds a union of the two ranges. + # (if they aren't you are probably calling the wrong method) + def span(other) + return self if other.empty? + + min_range = + if !min + self + elsif !other.min + other + else + case min <=> other.min + when 0 + include_min ? self : other + when -1 + self + when 1 + other + end + end + + max_range = + if !max + self + elsif !other.max + other + else + case max <=> other.max + when 0 + include_max ? self : other + when -1 + other + when 1 + self + end + end + + VersionRange.new( + min: min_range.min, + include_min: min_range.include_min, + max: max_range.max, + include_max: max_range.include_max + ) + end + + def union(other) + return other.union(self) if other.is_a?(VersionUnion) + + if contiguous_to?(other) + span(other) + else + VersionUnion.union([self, other]) + end + end + + def contiguous_to?(other) + return false if other.empty? + + intersects?(other) || + (min == other.max && (include_min || other.include_max)) || + (max == other.min && (include_max || other.include_min)) + end + + def allows_all?(other) + return true if other.empty? + + if other.is_a?(VersionUnion) + return VersionUnion.new([self]).allows_all?(other) + end + + return false if max && !other.max + return false if min && !other.min + + if min + case min <=> other.min + when -1 + when 0 + return false if !include_min && other.include_min + when 1 + return false + end + end + + if max + case max <=> other.max + when -1 + return false + when 0 + return false if !include_max && other.include_max + when 1 + end + end + + true + end + + def any? + !min && !max + end + + def empty? + false + end + + def to_s + @name ||= constraints.join(", ") + end + + def inspect + "#<#{self.class} #{to_s}>" + end + + def upper_invert + return self.class.empty unless max + + VersionRange.new(min: max, include_min: !include_max) + end + + def invert + return self.class.empty if any? + + low = VersionRange.new(max: min, include_max: !include_min) + high = VersionRange.new(min: max, include_min: !include_max) + + if !min + high + elsif !max + low + else + low.union(high) + end + end + + def ==(other) + self.class == other.class && + min == other.min && + max == other.max && + include_min == other.include_min && + include_max == other.include_max + end + + private + + def constraints + return ["any"] if any? + return ["= #{min}"] if min.to_s == max.to_s + + c = [] + c << "#{include_min ? ">=" : ">"} #{min}" if min + c << "#{include_max ? "<=" : "<"} #{max}" if max + c + end + + end +end 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 new file mode 100644 index 0000000000..4caf6b355b --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb @@ -0,0 +1,248 @@ +require_relative 'partial_solution' +require_relative 'term' +require_relative 'incompatibility' +require_relative 'solve_failure' + +module Bundler::PubGrub + class VersionSolver + attr_reader :logger + attr_reader :source + attr_reader :solution + + def initialize(source:, root: Package.root, logger: Bundler::PubGrub.logger) + @logger = logger + + @source = source + + # { package => [incompatibility, ...]} + @incompatibilities = Hash.new do |h, k| + h[k] = [] + end + + @seen_incompatibilities = {} + + @solution = PartialSolution.new + + add_incompatibility Incompatibility.new([ + Term.new(VersionConstraint.any(root), false) + ], cause: :root) + + propagate(root) + end + + def solved? + solution.unsatisfied.empty? + end + + # Returns true if there is more work to be done, false otherwise + def work + return false if solved? + + next_package = choose_package_version + propagate(next_package) + + if solved? + logger.info { "Solution found after #{solution.attempted_solutions} attempts:" } + solution.decisions.each do |package, version| + next if Package.root?(package) + logger.info { "* #{package} #{version}" } + end + + false + else + true + end + end + + def solve + work until solved? + + solution.decisions + end + + alias_method :result, :solve + + private + + def propagate(initial_package) + changed = [initial_package] + while package = changed.shift + @incompatibilities[package].reverse_each do |incompatibility| + result = propagate_incompatibility(incompatibility) + if result == :conflict + root_cause = resolve_conflict(incompatibility) + changed.clear + changed << propagate_incompatibility(root_cause) + elsif result # should be a Package + changed << result + end + end + changed.uniq! + end + end + + def propagate_incompatibility(incompatibility) + unsatisfied = nil + incompatibility.terms.each do |term| + relation = solution.relation(term) + if relation == :disjoint + return nil + elsif relation == :overlap + # If more than one term is inconclusive, we can't deduce anything + return nil if unsatisfied + unsatisfied = term + end + end + + if !unsatisfied + return :conflict + end + + logger.debug { "derived: #{unsatisfied.invert}" } + + solution.derive(unsatisfied.invert, incompatibility) + + unsatisfied.package + end + + def next_package_to_try + solution.unsatisfied.min_by do |term| + package = term.package + range = term.constraint.range + matching_versions = source.versions_for(package, range) + higher_versions = source.versions_for(package, range.upper_invert) + + [matching_versions.count <= 1 ? 0 : 1, higher_versions.count] + end.package + end + + def choose_package_version + if solution.unsatisfied.empty? + logger.info "No packages unsatisfied. Solving complete!" + return nil + end + + 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) + return package + end + + conflict = false + + source.incompatibilities_for(package, version).each do |incompatibility| + if @seen_incompatibilities.include?(incompatibility) + logger.debug { "knew: #{incompatibility}" } + next + end + @seen_incompatibilities[incompatibility] = true + + add_incompatibility incompatibility + + conflict ||= incompatibility.terms.all? do |term| + term.package == package || solution.satisfies?(term) + end + end + + unless conflict + logger.info { "selected #{package} #{version}" } + + solution.decide(package, version) + else + logger.info { "conflict: #{conflict.inspect}" } + end + + package + end + + def resolve_conflict(incompatibility) + logger.info { "conflict: #{incompatibility}" } + + new_incompatibility = nil + + while !incompatibility.failure? + most_recent_term = nil + most_recent_satisfier = nil + difference = nil + + previous_level = 1 + + incompatibility.terms.each do |term| + satisfier = solution.satisfier(term) + + if most_recent_satisfier.nil? + most_recent_term = term + most_recent_satisfier = satisfier + elsif most_recent_satisfier.index < satisfier.index + previous_level = [previous_level, most_recent_satisfier.decision_level].max + most_recent_term = term + most_recent_satisfier = satisfier + difference = nil + else + previous_level = [previous_level, satisfier.decision_level].max + end + + if most_recent_term == term + difference = most_recent_satisfier.term.difference(most_recent_term) + if difference.empty? + difference = nil + else + difference_satisfier = solution.satisfier(difference.inverse) + previous_level = [previous_level, difference_satisfier.decision_level].max + end + end + end + + if previous_level < most_recent_satisfier.decision_level || + most_recent_satisfier.decision? + + logger.info { "backtracking to #{previous_level}" } + solution.backtrack(previous_level) + + if new_incompatibility + add_incompatibility(new_incompatibility) + end + + return incompatibility + end + + new_terms = [] + new_terms += incompatibility.terms - [most_recent_term] + new_terms += most_recent_satisfier.cause.terms.reject { |term| + term.package == most_recent_satisfier.term.package + } + if difference + new_terms << difference.invert + end + + 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 + + incompatibility = new_incompatibility + + partially = difference ? " partially" : "" + logger.info { "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}" } + logger.info { "! which is caused by #{most_recent_satisfier.cause}" } + logger.info { "! thus #{incompatibility}" } + end + + raise SolveFailure.new(incompatibility) + end + + def add_incompatibility(incompatibility) + logger.debug { "fact: #{incompatibility}" } + incompatibility.terms.each do |term| + package = term.package + @incompatibilities[package] << incompatibility + end + end + end +end 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 new file mode 100644 index 0000000000..bbc10c3804 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +module Bundler::PubGrub + class VersionUnion + attr_reader :ranges + + def self.normalize_ranges(ranges) + ranges = ranges.flat_map do |range| + range.ranges + end + + ranges.reject!(&:empty?) + + return [] if ranges.empty? + + mins, ranges = ranges.partition { |r| !r.min } + original_ranges = mins + ranges.sort_by { |r| [r.min, r.include_min ? 0 : 1] } + ranges = [original_ranges.shift] + original_ranges.each do |range| + if ranges.last.contiguous_to?(range) + ranges << ranges.pop.span(range) + else + ranges << range + end + end + + ranges + end + + def self.union(ranges, normalize: true) + ranges = normalize_ranges(ranges) if normalize + + if ranges.size == 0 + VersionRange.empty + elsif ranges.size == 1 + ranges[0] + else + new(ranges) + end + end + + def initialize(ranges) + raise ArgumentError unless ranges.all? { |r| r.instance_of?(VersionRange) } + @ranges = ranges + end + + def hash + ranges.hash + end + + def eql?(other) + ranges.eql?(other.ranges) + end + + def include?(version) + !!ranges.bsearch {|r| r.compare_version(version) } + end + + def select_versions(all_versions) + versions = [] + ranges.inject(all_versions) do |acc, range| + _, matching, higher = range.partition_versions(acc) + versions.concat matching + higher + end + versions + end + + def intersects?(other) + my_ranges = ranges.dup + other_ranges = other.ranges.dup + + my_range = my_ranges.shift + other_range = other_ranges.shift + while my_range && other_range + if my_range.intersects?(other_range) + return true + end + + 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 + end + end + end + alias_method :allows_any?, :intersects? + + def allows_all?(other) + my_ranges = ranges.dup + + my_range = my_ranges.shift + + other.ranges.all? do |other_range| + while my_range + break if my_range.allows_all?(other_range) + my_range = my_ranges.shift + end + + !!my_range + end + end + + def empty? + false + end + + def any? + false + end + + def intersect(other) + my_ranges = ranges.dup + other_ranges = other.ranges.dup + new_ranges = [] + + my_range = my_ranges.shift + other_range = other_ranges.shift + while my_range && other_range + new_ranges << my_range.intersect(other_range) + + 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 + end + end + new_ranges.reject!(&:empty?) + VersionUnion.union(new_ranges, normalize: false) + end + + def upper_invert + ranges.last.upper_invert + end + + def invert + ranges.map(&:invert).inject(:intersect) + end + + def union(other) + VersionUnion.union([self, other]) + end + + def to_s + output = [] + + ranges = self.ranges.dup + while !ranges.empty? + ne = [] + range = ranges.shift + while !ranges.empty? && ranges[0].min.to_s == range.max.to_s + ne << range.max + range = range.span(ranges.shift) + end + + ne.map! {|x| "!= #{x}" } + if ne.empty? + output << range.to_s + elsif range.any? + output << ne.join(', ') + else + output << "#{range}, #{ne.join(', ')}" + end + end + + output.join(" OR ") + end + + def inspect + "#<#{self.class} #{to_s}>" + end + + def ==(other) + self.class == other.class && + self.ranges == other.ranges + end + end +end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index 8eff00bf3d..ef97d52ae7 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -425,7 +425,7 @@ class Bundler::Thor end def unix? - RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i end def truncate(string, width) 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_molinillo.rb b/lib/bundler/vendored_molinillo.rb deleted file mode 100644 index d1976f5cb4..0000000000 --- a/lib/bundler/vendored_molinillo.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module Bundler; end -require_relative "vendor/molinillo/lib/molinillo" diff --git a/lib/bundler/vendored_tmpdir.rb b/lib/bundler/vendored_pub_grub.rb index 43b4fa75fe..b36a996b29 100644 --- a/lib/bundler/vendored_tmpdir.rb +++ b/lib/bundler/vendored_pub_grub.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module Bundler; end -require_relative "vendor/tmpdir/lib/tmpdir" +require_relative "vendor/pub_grub/lib/pub_grub" diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 1acb00fd3a..8ef7be935b 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,9 +1,13 @@ # 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 end + + def self.gem_version + @gem_version ||= Gem::Version.create(VERSION) + end end diff --git a/lib/bundler/version_ranges.rb b/lib/bundler/version_ranges.rb deleted file mode 100644 index 12a956d6a0..0000000000 --- a/lib/bundler/version_ranges.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -module Bundler - module VersionRanges - NEq = Struct.new(:version) - ReqR = Struct.new(:left, :right) - class ReqR - Endpoint = Struct.new(:version, :inclusive) do - def <=>(other) - if version.equal?(INFINITY) - return 0 if other.version.equal?(INFINITY) - return 1 - elsif other.version.equal?(INFINITY) - return -1 - end - - comp = version <=> other.version - return comp unless comp.zero? - - if inclusive && !other.inclusive - 1 - elsif !inclusive && other.inclusive - -1 - else - 0 - end - end - end - - def to_s - "#{left.inclusive ? "[" : "("}#{left.version}, #{right.version}#{right.inclusive ? "]" : ")"}" - end - INFINITY = begin - inf = Object.new - def inf.to_s - "∞" - end - def inf.<=>(other) - return 0 if other.equal?(self) - 1 - end - inf.freeze - end - ZERO = Gem::Version.new("0.a") - - def cover?(v) - return false if left.inclusive && left.version > v - return false if !left.inclusive && left.version >= v - - if right.version != INFINITY - return false if right.inclusive && right.version < v - return false if !right.inclusive && right.version <= v - end - - true - end - - def empty? - left.version == right.version && !(left.inclusive && right.inclusive) - end - - def single? - left.version == right.version - end - - def <=>(other) - return -1 if other.equal?(INFINITY) - - comp = left <=> other.left - return comp unless comp.zero? - - right <=> other.right - end - - UNIVERSAL = ReqR.new(ReqR::Endpoint.new(Gem::Version.new("0.a"), true), ReqR::Endpoint.new(ReqR::INFINITY, false)).freeze - end - - def self.for_many(requirements) - requirements = requirements.map(&:requirements).flatten(1).map {|r| r.join(" ") } - requirements << ">= 0.a" if requirements.empty? - requirement = Gem::Requirement.new(requirements) - self.for(requirement) - end - - def self.for(requirement) - ranges = requirement.requirements.map do |op, v| - case op - when "=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v, true)) - when "!=" then NEq.new(v) - when ">=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(ReqR::INFINITY, false)) - when ">" then ReqR.new(ReqR::Endpoint.new(v, false), ReqR::Endpoint.new(ReqR::INFINITY, false)) - when "<" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, false)) - when "<=" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, true)) - when "~>" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v.bump, false)) - else raise "unknown version op #{op} in requirement #{requirement}" - end - end.uniq - ranges, neqs = ranges.partition {|r| !r.is_a?(NEq) } - - [ranges.sort, neqs.map(&:version)] - end - - def self.empty?(ranges, neqs) - !ranges.reduce(ReqR::UNIVERSAL) do |last_range, curr_range| - next false unless last_range - next false if curr_range.single? && neqs.include?(curr_range.left.version) - next curr_range if last_range.right.version == ReqR::INFINITY - case last_range.right.version <=> curr_range.left.version - # higher - when 1 then next ReqR.new(curr_range.left, last_range.right) - # equal - when 0 - if last_range.right.inclusive && curr_range.left.inclusive && !neqs.include?(curr_range.left.version) - ReqR.new(curr_range.left, [curr_range.right, last_range.right].max) - end - # lower - when -1 then next false - end - end - end - end -end 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 8d51cb24fe..7dc3a64941 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -288,7 +288,7 @@ # class CGI - VERSION = "0.3.3" + VERSION = "0.3.7" end require 'cgi/core' diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb index 6b0d89ca3b..1c4ef6a600 100644 --- a/lib/cgi/cookie.rb +++ b/lib/cgi/cookie.rb @@ -40,6 +40,10 @@ class CGI class Cookie < Array @@accept_charset="UTF-8" unless defined?(@@accept_charset) + TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z" + PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z" + DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z" + # Create a new CGI::Cookie object. # # :call-seq: @@ -72,8 +76,8 @@ class CGI @domain = nil @expires = nil if name.kind_of?(String) - @name = name - @path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.name = name + self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") @secure = false @httponly = false return super(value) @@ -84,11 +88,11 @@ class CGI raise ArgumentError, "`name' required" end - @name = options["name"] + self.name = options["name"] value = Array(options["value"]) # simple support for IE - @path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @domain = options["domain"] + self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.domain = options["domain"] @expires = options["expires"] @secure = options["secure"] == true @httponly = options["httponly"] == true @@ -97,11 +101,35 @@ class CGI end # Name of this cookie, as a +String+ - attr_accessor :name + attr_reader :name + # Set name of this cookie + def name=(str) + if str and !TOKEN_RE.match?(str) + raise ArgumentError, "invalid name: #{str.dump}" + end + @name = str + end + # Path for which this cookie applies, as a +String+ - attr_accessor :path + attr_reader :path + # Set path for which this cookie applies + def path=(str) + if str and !PATH_VALUE_RE.match?(str) + raise ArgumentError, "invalid path: #{str.dump}" + end + @path = str + end + # Domain for which this cookie applies, as a +String+ - attr_accessor :domain + attr_reader :domain + # Set domain for which this cookie applies + def domain=(str) + if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str)) + raise ArgumentError, "invalid domain: #{str.dump}" + end + @domain = str + end + # Time at which this cookie expires, as a +Time+ attr_accessor :expires # True if this cookie is secure; false otherwise @@ -162,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/core.rb b/lib/cgi/core.rb index bec76e0749..62e606837a 100644 --- a/lib/cgi/core.rb +++ b/lib/cgi/core.rb @@ -188,17 +188,28 @@ class CGI # Using #header with the HTML5 tag maker will create a <header> element. alias :header :http_header + def _no_crlf_check(str) + if str + str = str.to_s + raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/ + str + else + nil + end + end + private :_no_crlf_check + def _header_for_string(content_type) #:nodoc: buf = ''.dup if nph?() - buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}" + buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}" buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}" + buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}" buf << "Connection: close#{EOL}" end - buf << "Content-Type: #{content_type}#{EOL}" + buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}" if @output_cookies - @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" } + @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" } end return buf end # _header_for_string @@ -213,9 +224,9 @@ class CGI ## NPH options.delete('nph') if defined?(MOD_RUBY) if options.delete('nph') || nph?() - protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0' + protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0' status = options.delete('status') - status = HTTP_STATUS[status] || status || '200 OK' + status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK' buf << "#{protocol} #{status}#{EOL}" buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || '' @@ -223,38 +234,38 @@ class CGI end ## common headers status = options.delete('status') - buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status + buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status server = options.delete('server') - buf << "Server: #{server}#{EOL}" if server + buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server connection = options.delete('connection') - buf << "Connection: #{connection}#{EOL}" if connection + buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection type = options.delete('type') - buf << "Content-Type: #{type}#{EOL}" #if type + buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type length = options.delete('length') - buf << "Content-Length: #{length}#{EOL}" if length + buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length language = options.delete('language') - buf << "Content-Language: #{language}#{EOL}" if language + buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language expires = options.delete('expires') buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires ## cookie if cookie = options.delete('cookie') case cookie when String, Cookie - buf << "Set-Cookie: #{cookie}#{EOL}" + buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" when Array arr = cookie - arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" } + arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } when Hash hash = cookie - hash.each_value {|c| buf << "Set-Cookie: #{c}#{EOL}" } + hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } end end if @output_cookies - @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" } + @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } end ## other headers options.each do |key, value| - buf << "#{key}: #{value}#{EOL}" + buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}" end return buf end # _header_for_hash 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/csv.rb b/lib/csv.rb index 06a490f34c..0307033941 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -48,7 +48,7 @@ # # === Interface # -# * CSV now uses Hash-style parameters to set options. +# * CSV now uses keyword parameters to set options. # * CSV no longer has generate_row() or parse_row(). # * The old CSV's Reader and Writer classes have been dropped. # * CSV::open() is now more like Ruby's open(). @@ -95,16 +95,24 @@ require "stringio" require_relative "csv/fields_converter" require_relative "csv/input_record_separator" -require_relative "csv/match_p" require_relative "csv/parser" require_relative "csv/row" require_relative "csv/table" require_relative "csv/writer" -using CSV::MatchP if CSV.const_defined?(:MatchP) - # == \CSV -# \CSV (comma-separated variables) data is a text representation of a table: +# +# === In a Hurry? +# +# If you are familiar with \CSV data and have a particular task in mind, +# you may want to go directly to the: +# - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html]. +# +# Otherwise, read on here, about the API: classes, methods, and constants. +# +# === \CSV Data +# +# \CSV (comma-separated values) data is a text representation of a table: # - A _row_ _separator_ delimits table rows. # A common row separator is the newline character <tt>"\n"</tt>. # - A _column_ _separator_ delimits fields in a row. @@ -346,7 +354,9 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) # - +row_sep+: Specifies the row separator; used to delimit rows. # - +col_sep+: Specifies the column separator; used to delimit fields. # - +quote_char+: Specifies the quote character; used to quote fields. -# - +field_size_limit+: Specifies the maximum field size allowed. +# - +field_size_limit+: Specifies the maximum field size + 1 allowed. +# Deprecated since 3.2.3. Use +max_field_size+ instead. +# - +max_field_size+: Specifies the maximum field size allowed. # - +converters+: Specifies the field converters to be used. # - +unconverted_fields+: Specifies whether unconverted fields are to be available. # - +headers+: Specifies whether data contains headers, @@ -703,7 +713,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) # Header converters operate only on headers (and not on other rows). # # There are three ways to use header \converters; -# these examples use built-in header converter +:dowhcase+, +# these examples use built-in header converter +:downcase+, # which downcases each parsed header. # # - Option +header_converters+ with a singleton parsing method: @@ -853,8 +863,9 @@ class CSV # <b><tt>index</tt></b>:: The zero-based index of the field in its row. # <b><tt>line</tt></b>:: The line of the data source this row is from. # <b><tt>header</tt></b>:: The header for the column, when available. + # <b><tt>quoted?</tt></b>:: True or false, whether the original value is quoted or not. # - FieldInfo = Struct.new(:index, :line, :header) + FieldInfo = Struct.new(:index, :line, :header, :quoted?) # A Regexp used to find and convert some common Date formats. DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} | @@ -862,10 +873,9 @@ class CSV # A Regexp used to find and convert some common DateTime formats. DateTimeMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} | - \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} | - # ISO-8601 + # ISO-8601 and RFC-3339 (space instead of T) recognized by DateTime.parse \d{4}-\d{2}-\d{2} - (?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)? + (?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)? )\z /x # The encoding used by all converters. @@ -915,7 +925,8 @@ class CSV symbol: lambda { |h| h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip. gsub(/\s+/, "_").to_sym - } + }, + symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym } } # Default values for method options. @@ -926,6 +937,7 @@ class CSV quote_char: '"', # For parsing. field_size_limit: nil, + max_field_size: nil, converters: nil, unconverted_fields: nil, headers: false, @@ -993,7 +1005,7 @@ class CSV def instance(data = $stdout, **options) # create a _signature_ for this method call, data object and options sig = [data.object_id] + - options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s }) + options.values_at(*DEFAULT_OPTIONS.keys) # fetch or create the instance for this signature @@instances ||= Hash.new @@ -1007,65 +1019,190 @@ class CSV end # :call-seq: - # filter(**options) {|row| ... } - # filter(in_string, **options) {|row| ... } - # filter(in_io, **options) {|row| ... } - # filter(in_string, out_string, **options) {|row| ... } - # filter(in_string, out_io, **options) {|row| ... } - # filter(in_io, out_string, **options) {|row| ... } - # filter(in_io, out_io, **options) {|row| ... } - # - # Reads \CSV input and writes \CSV output. - # - # For each input row: - # - Forms the data into: - # - A CSV::Row object, if headers are in use. - # - An \Array of Arrays, otherwise. - # - Calls the block with that object. - # - Appends the block's return value to the output. + # filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table + # filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table + # filter(**options) {|row| ... } -> array_of_arrays or csv_table # - # Arguments: - # * \CSV source: - # * Argument +in_string+, if given, should be a \String object; - # it will be put into a new StringIO object positioned at the beginning. - # * Argument +in_io+, if given, should be an IO object that is - # open for reading; on return, the IO object will be closed. - # * If neither +in_string+ nor +in_io+ is given, - # the input stream defaults to {ARGF}[https://ruby-doc.org/core/ARGF.html]. - # * \CSV output: - # * Argument +out_string+, if given, should be a \String object; - # it will be put into a new StringIO object positioned at the beginning. - # * Argument +out_io+, if given, should be an IO object that is - # ppen for writing; on return, the IO object will be closed. - # * If neither +out_string+ nor +out_io+ is given, - # the output stream defaults to <tt>$stdout</tt>. - # * Argument +options+ should be keyword arguments. - # - Each argument name that is prefixed with +in_+ or +input_+ - # is stripped of its prefix and is treated as an option - # for parsing the input. - # Option +input_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>. - # - Each argument name that is prefixed with +out_+ or +output_+ - # is stripped of its prefix and is treated as an option - # for generating the output. - # Option +output_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>. - # - Each argument not prefixed as above is treated as an option - # both for parsing the input and for generating the output. - # - See {Options for Parsing}[#class-CSV-label-Options+for+Parsing] - # and {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # - Parses \CSV from a source (\String, \IO stream, or ARGF). + # - Calls the given block with each parsed row: + # - Without headers, each row is an \Array. + # - With headers, each row is a CSV::Row. + # - Generates \CSV to an output (\String, \IO stream, or STDOUT). + # - Returns the parsed source: + # - Without headers, an \Array of \Arrays. + # - With headers, a CSV::Table. # - # Example: - # in_string = "foo,0\nbar,1\nbaz,2\n" + # When +in_string_or_io+ is given, but not +out_string_or_io+, + # parses from the given +in_string_or_io+ + # and generates to STDOUT. + # + # \String input without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" + # CSV.filter(in_string) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \String input with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # CSV.filter(in_string, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => #<CSV::Table mode:col_or_row row_count:4> + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \IO stream input without headers: + # + # File.write('t.csv', "foo,0\nbar,1\nbaz,2") + # File.open('t.csv') do |in_io| + # CSV.filter(in_io) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \IO stream input with headers: + # + # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") + # File.open('t.csv') do |in_io| + # CSV.filter(in_io, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => #<CSV::Table mode:col_or_row row_count:4> + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # When both +in_string_or_io+ and +out_string_or_io+ are given, + # parses from +in_string_or_io+ and generates to +out_string_or_io+. + # + # \String output without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" # out_string = '' # CSV.filter(in_string, out_string) do |row| - # row[0] = row[0].upcase - # row[1] *= 4 - # end - # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n" + # + # \String output with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # out_string = '' + # CSV.filter(in_string, out_string, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => #<CSV::Table mode:col_or_row row_count:4> + # out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n" + # + # \IO stream output without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" + # File.open('t.csv', 'w') do |out_io| + # CSV.filter(in_string, out_io) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n" + # + # \IO stream output with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # File.open('t.csv', 'w') do |out_io| + # CSV.filter(in_string, out_io, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => #<CSV::Table mode:col_or_row row_count:4> + # File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n" + # + # When neither +in_string_or_io+ nor +out_string_or_io+ given, + # parses from {ARGF}[rdoc-ref:ARGF] + # and generates to STDOUT. + # + # Without headers: + # + # # Put Ruby code into a file. + # ruby = <<-EOT + # require 'csv' + # CSV.filter do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # EOT + # File.write('t.rb', ruby) + # # Put some CSV into a file. + # File.write('t.csv', "foo,0\nbar,1\nbaz,2") + # # Run the Ruby code with CSV filename as argument. + # system(Gem.ruby, "t.rb", "t.csv") + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # With headers: + # + # # Put Ruby code into a file. + # ruby = <<-EOT + # require 'csv' + # CSV.filter(headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # EOT + # File.write('t.rb', ruby) + # # Put some CSV into a file. + # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") + # # Run the Ruby code with CSV filename as argument. + # system(Gem.ruby, "t.rb", "t.csv") + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # Arguments: + # + # * Argument +in_string_or_io+ must be a \String or an \IO stream. + # * Argument +out_string_or_io+ must be a \String or an \IO stream. + # * Arguments <tt>**options</tt> must be keyword options. + # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]. def filter(input=nil, output=nil, **options) # parse options for input, output, or both in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value} options.each do |key, value| - case key.to_s + case key when /\Ain(?:put)?_(.+)\Z/ in_options[$1.to_sym] = value when /\Aout(?:put)?_(.+)\Z/ @@ -1107,111 +1244,90 @@ class CSV # # :call-seq: - # foreach(path, mode='r', **options) {|row| ... ) - # foreach(io, mode='r', **options {|row| ... ) - # foreach(path, mode='r', headers: ..., **options) {|row| ... ) - # foreach(io, mode='r', headers: ..., **options {|row| ... ) - # foreach(path, mode='r', **options) -> new_enumerator - # foreach(io, mode='r', **options -> new_enumerator + # foreach(path_or_io, mode='r', **options) {|row| ... ) + # foreach(path_or_io, mode='r', **options) -> new_enumerator # - # Calls the block with each row read from source +path+ or +io+. + # Calls the block with each row read from source +path_or_io+. # - # * Argument +path+, if given, must be the path to a file. - # :include: ../doc/csv/arguments/io.rdoc - # * Argument +mode+, if given, must be a \File mode - # See {Open Mode}[IO.html#method-c-new-label-Open+Mode]. - # * Arguments <tt>**options</tt> must be keyword options. - # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]. - # * This method optionally accepts an additional <tt>:encoding</tt> option - # that you can use to specify the Encoding of the data read from +path+ or +io+. - # You must provide this unless your data is in the encoding - # given by <tt>Encoding::default_external</tt>. - # Parsing will use this to determine how to parse the data. - # You may provide a second Encoding to - # have the data transcoded as it is read. For example, - # encoding: 'UTF-32BE:UTF-8' - # would read +UTF-32BE+ data from the file - # but transcode it to +UTF-8+ before parsing. - # - # ====== Without Option +headers+ + # \Path input without headers: # - # Without option +headers+, returns each row as an \Array object. - # - # These examples assume prior execution of: # string = "foo,0\nbar,1\nbaz,2\n" - # path = 't.csv' - # File.write(path, string) + # in_path = 't.csv' + # File.write(in_path, string) + # CSV.foreach(in_path) {|row| p row } # - # Read rows from a file at +path+: - # CSV.foreach(path) {|row| p row } # Output: - # ["foo", "0"] - # ["bar", "1"] - # ["baz", "2"] # - # Read rows from an \IO object: - # File.open(path) do |file| - # CSV.foreach(file) {|row| p row } - # end - # - # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # - # Returns a new \Enumerator if no block given: - # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")> - # CSV.foreach(File.open(path)) # => #<Enumerator: CSV:foreach(#<File:t.csv>, "r")> + # \Path input with headers: + # + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # in_path = 't.csv' + # File.write(in_path, string) + # CSV.foreach(in_path, headers: true) {|row| p row } # - # Issues a warning if an encoding is unsupported: - # CSV.foreach(File.open(path), encoding: 'foo:bar') {|row| } # Output: - # warning: Unsupported encoding foo ignored - # warning: Unsupported encoding bar ignored # - # ====== With Option +headers+ + # <CSV::Row "Name":"foo" "Value":"0"> + # <CSV::Row "Name":"bar" "Value":"1"> + # <CSV::Row "Name":"baz" "Value":"2"> # - # With {option +headers+}[#class-CSV-label-Option+headers], - # returns each row as a CSV::Row object. + # \IO stream input without headers: # - # These examples assume prior execution of: - # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" + # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) - # - # Read rows from a file at +path+: - # CSV.foreach(path, headers: true) {|row| p row } + # File.open('t.csv') do |in_io| + # CSV.foreach(in_io) {|row| p row } + # end # # Output: - # #<CSV::Row "Name":"foo" "Count":"0"> - # #<CSV::Row "Name":"bar" "Count":"1"> - # #<CSV::Row "Name":"baz" "Count":"2"> # - # Read rows from an \IO object: - # File.open(path) do |file| - # CSV.foreach(file, headers: true) {|row| p row } + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # \IO stream input with headers: + # + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # File.open('t.csv') do |in_io| + # CSV.foreach(in_io, headers: true) {|row| p row } # end # # Output: - # #<CSV::Row "Name":"foo" "Count":"0"> - # #<CSV::Row "Name":"bar" "Count":"1"> - # #<CSV::Row "Name":"baz" "Count":"2"> - # - # --- # - # Raises an exception if +path+ is a \String, but not the path to a readable file: - # # Raises Errno::ENOENT (No such file or directory @ rb_sysopen - nosuch.csv): - # CSV.foreach('nosuch.csv') {|row| } + # <CSV::Row "Name":"foo" "Value":"0"> + # <CSV::Row "Name":"bar" "Value":"1"> + # <CSV::Row "Name":"baz" "Value":"2"> # - # Raises an exception if +io+ is an \IO object, but not open for reading: - # io = File.open(path, 'w') {|row| } - # # Raises TypeError (no implicit conversion of nil into String): - # CSV.foreach(io) {|row| } + # With no block given, returns an \Enumerator: # - # Raises an exception if +mode+ is invalid: - # # Raises ArgumentError (invalid access mode nosuch): - # CSV.foreach(path, 'nosuch') {|row| } + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")> # + # Arguments: + # * Argument +path_or_io+ must be a file path or an \IO stream. + # * Argument +mode+, if given, must be a \File mode + # See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode]. + # * Arguments <tt>**options</tt> must be keyword options. + # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]. + # * This method optionally accepts an additional <tt>:encoding</tt> option + # that you can use to specify the Encoding of the data read from +path+ or +io+. + # You must provide this unless your data is in the encoding + # given by <tt>Encoding::default_external</tt>. + # Parsing will use this to determine how to parse the data. + # You may provide a second Encoding to + # have the data transcoded as it is read. For example, + # encoding: 'UTF-32BE:UTF-8' + # would read +UTF-32BE+ data from the file + # but transcode it to +UTF-8+ before parsing. def foreach(path, mode="r", **options, &block) return to_enum(__method__, path, mode, **options) unless block_given? open(path, mode, **options) do |csv| @@ -1349,6 +1465,46 @@ class CSV (new(str, **options) << row).string end + # :call-seq: + # CSV.generate_lines(rows) + # CSV.generate_lines(rows, **options) + # + # Returns the \String created by generating \CSV from + # using the specified +options+. + # + # Argument +rows+ must be an \Array of row. Row is \Array of \String or \CSV::Row. + # + # Special options: + # * Option <tt>:row_sep</tt> defaults to <tt>"\n"</tt> on Ruby 3.0 or later + # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.: + # $INPUT_RECORD_SEPARATOR # => "\n" + # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base + # Encoding for the output. This method will try to guess your Encoding from + # the first non-+nil+ field in +row+, if possible, but you may need to use + # this parameter as a backup plan. + # + # For other +options+, + # see {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # + # --- + # + # Returns the \String generated from an + # CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n" + # + # --- + # + # Raises an exception + # # Raises NoMethodError (undefined method `each' for :foo:Symbol) + # CSV.generate_lines(:foo) + # + def generate_lines(rows, **options) + self.generate(**options) do |csv| + rows.each do |row| + csv << row + end + end + end + # # :call-seq: # open(file_path, mode = "rb", **options ) -> new_csv @@ -1357,7 +1513,7 @@ class CSV # open(io, mode = "rb", **options ) { |csv| ... } -> object # # possible options elements: - # hash form: + # keyword form: # :invalid => nil # raise error on invalid byte sequence (default) # :invalid => :replace # replace invalid byte sequence # :undef => :replace # replace undefined conversion @@ -1424,10 +1580,14 @@ class CSV def open(filename, mode="r", **options) # wrap a File opened with the remaining +args+ with no newline # decorator - file_opts = {universal_newline: false}.merge(options) + file_opts = options.dup + unless file_opts.key?(:newline) + file_opts[:universal_newline] ||= false + end options.delete(:invalid) options.delete(:undef) options.delete(:replace) + options.delete_if {|k, _| /newline\z/.match?(k)} begin f = File.open(filename, mode, **file_opts) @@ -1746,6 +1906,7 @@ class CSV row_sep: :auto, quote_char: '"', field_size_limit: nil, + max_field_size: nil, converters: nil, unconverted_fields: nil, headers: false, @@ -1769,8 +1930,19 @@ class CSV raise ArgumentError.new("Cannot parse nil as CSV") if data.nil? if data.is_a?(String) + if encoding + if encoding.is_a?(String) + data_external_encoding, data_internal_encoding = encoding.split(":", 2) + if data_internal_encoding + data = data.encode(data_internal_encoding, data_external_encoding) + else + data = data.dup.force_encoding(data_external_encoding) + end + else + data = data.dup.force_encoding(encoding) + end + end @io = StringIO.new(data) - @io.set_encoding(encoding || data.encoding) else @io = data end @@ -1788,11 +1960,14 @@ class CSV @initial_header_converters = header_converters @initial_write_converters = write_converters + if max_field_size.nil? and field_size_limit + max_field_size = field_size_limit - 1 + end @parser_options = { column_separator: col_sep, row_separator: row_sep, quote_character: quote_char, - field_size_limit: field_size_limit, + max_field_size: max_field_size, unconverted_fields: unconverted_fields, headers: headers, return_headers: return_headers, @@ -1860,11 +2035,25 @@ class CSV # Returns the limit for field size; used for parsing; # see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]: # CSV.new('').field_size_limit # => nil + # + # Deprecated since 3.2.3. Use +max_field_size+ instead. def field_size_limit parser.field_size_limit end # :call-seq: + # csv.max_field_size -> integer or nil + # + # Returns the limit for field size; used for parsing; + # see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]: + # CSV.new('').max_field_size # => nil + # + # Since 3.2.3. + def max_field_size + parser.max_field_size + end + + # :call-seq: # csv.skip_lines -> regexp or nil # # Returns the \Regexp used to identify comment lines; used for parsing; @@ -1994,7 +2183,7 @@ class CSV end # :call-seq: - # csv.encoding -> endcoding + # csv.encoding -> encoding # # Returns the encoding used for parsing and generating; # see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]: @@ -2362,7 +2551,13 @@ class CSV # p row # end def each(&block) - parser_enumerator.each(&block) + return to_enum(__method__) unless block_given? + begin + while true + yield(parser_enumerator.next) + end + rescue StopIteration + end end # :call-seq: diff --git a/lib/csv/fields_converter.rb b/lib/csv/fields_converter.rb index b206118d99..d15977d379 100644 --- a/lib/csv/fields_converter.rb +++ b/lib/csv/fields_converter.rb @@ -44,7 +44,7 @@ class CSV @converters.empty? end - def convert(fields, headers, lineno) + def convert(fields, headers, lineno, quoted_fields) return fields unless need_convert? fields.collect.with_index do |field, index| @@ -63,7 +63,8 @@ class CSV else header = nil end - field = converter[field, FieldInfo.new(index, lineno, header)] + quoted = quoted_fields[index] + field = converter[field, FieldInfo.new(index, lineno, header, quoted)] end break unless field.is_a?(String) # short-circuit pipeline for speed end diff --git a/lib/csv/input_record_separator.rb b/lib/csv/input_record_separator.rb index bbf13479f7..7a99343c0c 100644 --- a/lib/csv/input_record_separator.rb +++ b/lib/csv/input_record_separator.rb @@ -4,20 +4,7 @@ require "stringio" class CSV module InputRecordSeparator class << self - is_input_record_separator_deprecated = false - verbose, $VERBOSE = $VERBOSE, true - stderr, $stderr = $stderr, StringIO.new - input_record_separator = $INPUT_RECORD_SEPARATOR - begin - $INPUT_RECORD_SEPARATOR = "\r\n" - is_input_record_separator_deprecated = (not $stderr.string.empty?) - ensure - $INPUT_RECORD_SEPARATOR = input_record_separator - $stderr = stderr - $VERBOSE = verbose - end - - if is_input_record_separator_deprecated + if RUBY_VERSION >= "3.0.0" def value "\n" end diff --git a/lib/csv/parser.rb b/lib/csv/parser.rb index 7e943acf21..afb3131cd5 100644 --- a/lib/csv/parser.rb +++ b/lib/csv/parser.rb @@ -2,15 +2,10 @@ require "strscan" -require_relative "delete_suffix" require_relative "input_record_separator" -require_relative "match_p" require_relative "row" require_relative "table" -using CSV::DeleteSuffix if CSV.const_defined?(:DeleteSuffix) -using CSV::MatchP if CSV.const_defined?(:MatchP) - class CSV # Note: Don't use this class directly. This is an internal class. class Parser @@ -27,6 +22,10 @@ class CSV class InvalidEncoding < StandardError end + # Raised when unexpected case is happen. + class UnexpectedError < StandardError + end + # # CSV::Scanner receives a CSV output, scans it and return the content. # It also controls the life cycle of the object with its methods +keep_start+, @@ -78,10 +77,10 @@ class CSV # +keep_end+, +keep_back+, +keep_drop+. # # CSV::InputsScanner.scan() tries to match with pattern at the current position. - # If there's a match, the scanner advances the “scan pointer” and returns the matched string. + # If there's a match, the scanner advances the "scan pointer" and returns the matched string. # Otherwise, the scanner returns nil. # - # CSV::InputsScanner.rest() returns the “rest” of the string (i.e. everything after the scan pointer). + # CSV::InputsScanner.rest() returns the "rest" of the string (i.e. everything after the scan pointer). # If there is no more data (eos? = true), it returns "". # class InputsScanner @@ -96,11 +95,13 @@ class CSV end def each_line(row_separator) + return enum_for(__method__, row_separator) unless block_given? buffer = nil input = @scanner.rest position = @scanner.pos offset = 0 n_row_separator_chars = row_separator.size + # trace(__method__, :start, line, input) while true input.each_line(row_separator) do |line| @scanner.pos += line.bytesize @@ -140,25 +141,28 @@ class CSV end def scan(pattern) + # trace(__method__, pattern, :start) value = @scanner.scan(pattern) + # trace(__method__, pattern, :done, :last, value) if @last_scanner return value if @last_scanner - if value - read_chunk if @scanner.eos? - return value - else - nil - end + read_chunk if value and @scanner.eos? + # trace(__method__, pattern, :done, value) + value end def scan_all(pattern) + # trace(__method__, pattern, :start) value = @scanner.scan(pattern) + # trace(__method__, pattern, :done, :last, value) if @last_scanner return value if @last_scanner return nil if value.nil? while @scanner.eos? and read_chunk and (sub_value = @scanner.scan(pattern)) + # trace(__method__, pattern, :sub, sub_value) value << sub_value end + # trace(__method__, pattern, :done, value) value end @@ -167,68 +171,126 @@ class CSV end def keep_start - @keeps.push([@scanner.pos, nil]) + # trace(__method__, :start) + adjust_last_keep + @keeps.push([@scanner, @scanner.pos, nil]) + # trace(__method__, :done) end def keep_end - start, buffer = @keeps.pop - keep = @scanner.string.byteslice(start, @scanner.pos - start) + # trace(__method__, :start) + scanner, start, buffer = @keeps.pop + if scanner == @scanner + keep = @scanner.string.byteslice(start, @scanner.pos - start) + else + keep = @scanner.string.byteslice(0, @scanner.pos) + end if buffer buffer << keep keep = buffer end + # trace(__method__, :done, keep) keep end def keep_back - start, buffer = @keeps.pop + # trace(__method__, :start) + scanner, start, buffer = @keeps.pop if buffer + # trace(__method__, :rescan, start, buffer) string = @scanner.string - keep = string.byteslice(start, string.bytesize - start) + if scanner == @scanner + keep = string.byteslice(start, string.bytesize - start) + else + keep = string + end if keep and not keep.empty? @inputs.unshift(StringIO.new(keep)) @last_scanner = false end @scanner = StringScanner.new(buffer) else + if @scanner != scanner + message = "scanners are different but no buffer: " + message += "#{@scanner.inspect}(#{@scanner.object_id}): " + message += "#{scanner.inspect}(#{scanner.object_id})" + raise UnexpectedError, message + end + # trace(__method__, :repos, start, buffer) @scanner.pos = start end read_chunk if @scanner.eos? end def keep_drop - @keeps.pop + _, _, buffer = @keeps.pop + # trace(__method__, :done, :empty) unless buffer + return unless buffer + + last_keep = @keeps.last + # trace(__method__, :done, :no_last_keep) unless last_keep + return unless last_keep + + if last_keep[2] + last_keep[2] << buffer + else + last_keep[2] = buffer + end + # trace(__method__, :done) end def rest @scanner.rest end + def check(pattern) + @scanner.check(pattern) + end + private - def read_chunk - return false if @last_scanner + def trace(*args) + pp([*args, @scanner, @scanner&.string, @scanner&.pos, @keeps]) + end - unless @keeps.empty? - keep = @keeps.last - keep_start = keep[0] - string = @scanner.string - keep_data = string.byteslice(keep_start, @scanner.pos - keep_start) - if keep_data - keep_buffer = keep[1] - if keep_buffer - keep_buffer << keep_data - else - keep[1] = keep_data.dup - end + def adjust_last_keep + # trace(__method__, :start) + + keep = @keeps.last + # trace(__method__, :done, :empty) if keep.nil? + return if keep.nil? + + scanner, start, buffer = keep + string = @scanner.string + if @scanner != scanner + start = 0 + end + if start == 0 and @scanner.eos? + keep_data = string + else + keep_data = string.byteslice(start, @scanner.pos - start) + end + if keep_data + if buffer + buffer << keep_data + else + keep[2] = keep_data.dup end - keep[0] = 0 end + # trace(__method__, :done) + end + + def read_chunk + return false if @last_scanner + + adjust_last_keep + input = @inputs.first case input when StringIO string = input.read raise InvalidEncoding unless string.valid_encoding? + # trace(__method__, :stringio, string) @scanner = StringScanner.new(string) @inputs.shift @last_scanner = @inputs.empty? @@ -237,6 +299,7 @@ class CSV chunk = input.gets(@row_separator, @chunk_size) if chunk raise InvalidEncoding unless chunk.valid_encoding? + # trace(__method__, :chunk, chunk) @scanner = StringScanner.new(chunk) if input.respond_to?(:eof?) and input.eof? @inputs.shift @@ -244,6 +307,7 @@ class CSV end true else + # trace(__method__, :no_chunk) @scanner = StringScanner.new("".encode(@encoding)) @inputs.shift @last_scanner = @inputs.empty? @@ -278,7 +342,11 @@ class CSV end def field_size_limit - @field_size_limit + @max_field_size&.succ + end + + def max_field_size + @max_field_size end def skip_lines @@ -346,6 +414,16 @@ class CSV end message = "Invalid byte sequence in #{@encoding}" raise MalformedCSVError.new(message, lineno) + rescue UnexpectedError => error + if @scanner + ignore_broken_line + lineno = @lineno + else + lineno = @lineno + 1 + end + message = "This should not be happen: #{error.message}: " + message += "Please report this to https://github.com/ruby/csv/issues" + raise MalformedCSVError.new(message, lineno) end end @@ -390,7 +468,7 @@ class CSV @backslash_quote = false end @unconverted_fields = @options[:unconverted_fields] - @field_size_limit = @options[:field_size_limit] + @max_field_size = @options[:max_field_size] @skip_blanks = @options[:skip_blanks] @fields_converter = @options[:fields_converter] @header_fields_converter = @options[:header_fields_converter] @@ -680,9 +758,10 @@ class CSV case headers when Array @raw_headers = headers + quoted_fields = [false] * @raw_headers.size @use_headers = true when String - @raw_headers = parse_headers(headers) + @raw_headers, quoted_fields = parse_headers(headers) @use_headers = true when nil, false @raw_headers = nil @@ -692,21 +771,28 @@ class CSV @use_headers = true end if @raw_headers - @headers = adjust_headers(@raw_headers) + @headers = adjust_headers(@raw_headers, quoted_fields) else @headers = nil end end def parse_headers(row) - CSV.parse_line(row, - col_sep: @column_separator, - row_sep: @row_separator, - quote_char: @quote_character) + quoted_fields = [] + converter = lambda do |field, info| + quoted_fields << info.quoted? + field + end + headers = CSV.parse_line(row, + col_sep: @column_separator, + row_sep: @row_separator, + quote_char: @quote_character, + converters: [converter]) + [headers, quoted_fields] end - def adjust_headers(headers) - adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno) + def adjust_headers(headers, quoted_fields) + adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno, quoted_fields) adjusted_headers.each {|h| h.freeze if h.is_a? String} adjusted_headers end @@ -729,28 +815,28 @@ class CSV sample[0, 128].index(@quote_character) end - SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes") - if SCANNER_TEST - class UnoptimizedStringIO - def initialize(string) - @io = StringIO.new(string, "rb:#{string.encoding}") - end + class UnoptimizedStringIO # :nodoc: + def initialize(string) + @io = StringIO.new(string, "rb:#{string.encoding}") + end - def gets(*args) - @io.gets(*args) - end + def gets(*args) + @io.gets(*args) + end - def each_line(*args, &block) - @io.each_line(*args, &block) - end + def each_line(*args, &block) + @io.each_line(*args, &block) + end - def eof? - @io.eof? - end + def eof? + @io.eof? end + end - SCANNER_TEST_CHUNK_SIZE = - Integer((ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"), 10) + SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes") + if SCANNER_TEST + SCANNER_TEST_CHUNK_SIZE_NAME = "CSV_PARSER_SCANNER_TEST_CHUNK_SIZE" + SCANNER_TEST_CHUNK_SIZE_VALUE = ENV[SCANNER_TEST_CHUNK_SIZE_NAME] def build_scanner inputs = @samples.collect do |sample| UnoptimizedStringIO.new(sample) @@ -760,10 +846,17 @@ class CSV else inputs << @input end + begin + chunk_size_value = ENV[SCANNER_TEST_CHUNK_SIZE_NAME] + rescue # Ractor::IsolationError + # Ractor on Ruby 3.0 can't read ENV value. + chunk_size_value = SCANNER_TEST_CHUNK_SIZE_VALUE + end + chunk_size = Integer((chunk_size_value || "1"), 10) InputsScanner.new(inputs, @encoding, @row_separator, - chunk_size: SCANNER_TEST_CHUNK_SIZE) + chunk_size: chunk_size) end else def build_scanner @@ -826,6 +919,14 @@ class CSV end end + def validate_field_size(field) + return unless @max_field_size + return if field.size <= @max_field_size + ignore_broken_line + message = "Field size exceeded: #{field.size} > #{@max_field_size}" + raise MalformedCSVError.new(message, @lineno) + end + def parse_no_quote(&block) @scanner.each_line(@row_separator) do |line| next if @skip_lines and skip_line?(line) @@ -835,9 +936,16 @@ class CSV if line.empty? next if @skip_blanks row = [] + quoted_fields = [] else line = strip_value(line) row = line.split(@split_column_separator, -1) + quoted_fields = [false] * row.size + if @max_field_size + row.each do |column| + validate_field_size(column) + end + end n_columns = row.size i = 0 while i < n_columns @@ -846,7 +954,7 @@ class CSV end end @last_line = original_line - emit_row(row, &block) + emit_row(row, quoted_fields, &block) end end @@ -868,31 +976,37 @@ class CSV next end row = [] + quoted_fields = [] elsif line.include?(@cr) or line.include?(@lf) @scanner.keep_back @need_robust_parsing = true return parse_quotable_robust(&block) else row = line.split(@split_column_separator, -1) + quoted_fields = [] n_columns = row.size i = 0 while i < n_columns column = row[i] if column.empty? + quoted_fields << false row[i] = nil else n_quotes = column.count(@quote_character) if n_quotes.zero? + quoted_fields << false # no quote elsif n_quotes == 2 and column.start_with?(@quote_character) and column.end_with?(@quote_character) + quoted_fields << true row[i] = column[1..-2] else @scanner.keep_back @need_robust_parsing = true return parse_quotable_robust(&block) end + validate_field_size(row[i]) end i += 1 end @@ -900,13 +1014,14 @@ class CSV @scanner.keep_drop @scanner.keep_start @last_line = original_line - emit_row(row, &block) + emit_row(row, quoted_fields, &block) end @scanner.keep_drop end def parse_quotable_robust(&block) row = [] + quoted_fields = [] skip_needless_lines start_row while true @@ -916,32 +1031,39 @@ class CSV value = parse_column_value if value @scanner.scan_all(@strip_value) if @strip_value - if @field_size_limit and value.size >= @field_size_limit - ignore_broken_line - raise MalformedCSVError.new("Field size exceeded", @lineno) - end + validate_field_size(value) end if parse_column_end row << value + quoted_fields << @quoted_column_value elsif parse_row_end if row.empty? and value.nil? - emit_row([], &block) unless @skip_blanks + emit_row([], [], &block) unless @skip_blanks else row << value - emit_row(row, &block) + quoted_fields << @quoted_column_value + emit_row(row, quoted_fields, &block) row = [] + quoted_fields = [] end skip_needless_lines start_row elsif @scanner.eos? break if row.empty? and value.nil? row << value - emit_row(row, &block) + quoted_fields << @quoted_column_value + emit_row(row, quoted_fields, &block) break else if @quoted_column_value + if liberal_parsing? and (new_line = @scanner.check(@line_end)) + message = + "Illegal end-of-line sequence outside of a quoted field " + + "<#{new_line.inspect}>" + else + message = "Any value after quoted field isn't allowed" + end ignore_broken_line - message = "Any value after quoted field isn't allowed" raise MalformedCSVError.new(message, @lineno) elsif @unquoted_column_value and (new_line = @scanner.scan(@line_end)) @@ -1034,7 +1156,7 @@ class CSV if (n_quotes % 2).zero? quotes[0, (n_quotes - 2) / 2] else - value = quotes[0, (n_quotes - 1) / 2] + value = quotes[0, n_quotes / 2] while true quoted_value = @scanner.scan_all(@quoted_value) value << quoted_value if quoted_value @@ -1058,11 +1180,9 @@ class CSV n_quotes = quotes.size if n_quotes == 1 break - elsif (n_quotes % 2) == 1 - value << quotes[0, (n_quotes - 1) / 2] - break else value << quotes[0, n_quotes / 2] + break if (n_quotes % 2) == 1 end end value @@ -1098,18 +1218,15 @@ class CSV def strip_value(value) return value unless @strip - return nil if value.nil? + return value if value.nil? case @strip when String - size = value.size - while value.start_with?(@strip) - size -= 1 - value = value[1, size] + while value.delete_prefix!(@strip) + # do nothing end - while value.end_with?(@strip) - size -= 1 - value = value[0, size] + while value.delete_suffix!(@strip) + # do nothing end else value.strip! @@ -1132,22 +1249,22 @@ class CSV @scanner.keep_start end - def emit_row(row, &block) + def emit_row(row, quoted_fields, &block) @lineno += 1 raw_row = row if @use_headers if @headers.nil? - @headers = adjust_headers(row) + @headers = adjust_headers(row, quoted_fields) return unless @return_headers row = Row.new(@headers, row, true) else row = Row.new(@headers, - @fields_converter.convert(raw_row, @headers, @lineno)) + @fields_converter.convert(raw_row, @headers, @lineno, quoted_fields)) end else # convert fields, if needed... - row = @fields_converter.convert(raw_row, nil, @lineno) + row = @fields_converter.convert(raw_row, nil, @lineno, quoted_fields) end # inject unconverted fields and accessor, if requested... diff --git a/lib/csv/row.rb b/lib/csv/row.rb index 0f465ea2a3..86323f7d0a 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -3,30 +3,105 @@ require "forwardable" class CSV + # = \CSV::Row + # A \CSV::Row instance represents a \CSV table row. + # (see {class CSV}[../CSV.html]). # - # A CSV::Row is part Array and part Hash. It retains an order for the fields - # and allows duplicates just as an Array would, but also allows you to access - # fields by name just as you could if they were in a Hash. + # The instance may have: + # - Fields: each is an object, not necessarily a \String. + # - Headers: each serves a key, and also need not be a \String. # - # All rows returned by CSV will be constructed from this class, if header row - # processing is activated. + # === Instance Methods + # + # \CSV::Row has three groups of instance methods: + # - Its own internally defined instance methods. + # - Methods included by module Enumerable. + # - Methods delegated to class Array.: + # * Array#empty? + # * Array#length + # * Array#size + # + # == Creating a \CSV::Row Instance + # + # Commonly, a new \CSV::Row instance is created by parsing \CSV source + # that has headers: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.each {|row| p row } + # Output: + # #<CSV::Row "Name":"foo" "Value":"0"> + # #<CSV::Row "Name":"bar" "Value":"1"> + # #<CSV::Row "Name":"baz" "Value":"2"> + # + # You can also create a row directly. See ::new. + # + # == Headers + # + # Like a \CSV::Table, a \CSV::Row has headers. + # + # A \CSV::Row that was created by parsing \CSV source + # inherits its headers from the table: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table.first + # row.headers # => ["Name", "Value"] + # + # You can also create a new row with headers; + # like the keys in a \Hash, the headers need not be Strings: + # row = CSV::Row.new([:name, :value], ['foo', 0]) + # row.headers # => [:name, :value] + # + # The new row retains its headers even if added to a table + # that has headers: + # table << row # => #<CSV::Table mode:col_or_row row_count:5> + # row.headers # => [:name, :value] + # row[:name] # => "foo" + # row['Name'] # => nil + # + # + # + # == Accessing Fields + # + # You may access a field in a \CSV::Row with either its \Integer index + # (\Array-style) or its header (\Hash-style). + # + # Fetch a field using method #[]: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row[1] # => 0 + # row['Value'] # => 0 + # + # Set a field using method #[]=: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row # => #<CSV::Row "Name":"foo" "Value":0> + # row[0] = 'bar' + # row['Value'] = 1 + # row # => #<CSV::Row "Name":"bar" "Value":1> # class Row - # - # Constructs a new CSV::Row from +headers+ and +fields+, which are expected - # to be Arrays. If one Array is shorter than the other, it will be padded - # with +nil+ objects. - # - # The optional +header_row+ parameter can be set to +true+ to indicate, via - # CSV::Row.header_row?() and CSV::Row.field_row?(), that this is a header - # row. Otherwise, the row assumes to be a field row. - # - # A CSV::Row object supports the following Array methods through delegation: - # - # * empty?() - # * length() - # * size() - # + # :call-seq: + # CSV::Row.new(headers, fields, header_row = false) -> csv_row + # + # Returns the new \CSV::Row instance constructed from + # arguments +headers+ and +fields+; both should be Arrays; + # note that the fields need not be Strings: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row # => #<CSV::Row "Name":"foo" "Value":0> + # + # If the \Array lengths are different, the shorter is +nil+-filled: + # row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0]) + # row # => #<CSV::Row "Name":"foo" "Value":0 "Date":nil "Size":nil> + # + # Each \CSV::Row object is either a <i>field row</i> or a <i>header row</i>; + # by default, a new row is a field row; for the row created above: + # row.field_row? # => true + # row.header_row? # => false + # + # If the optional argument +header_row+ is given as +true+, + # the created row is a header row: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true) + # row # => #<CSV::Row "Name":"foo" "Value":0> + # row.field_row? # => false + # row.header_row? # => true def initialize(headers, fields, header_row = false) @header_row = header_row headers.each { |h| h.freeze if h.is_a? String } @@ -48,6 +123,10 @@ class CSV extend Forwardable def_delegators :@row, :empty?, :length, :size + # :call-seq: + # row.initialize_copy(other_row) -> self + # + # Calls superclass method. def initialize_copy(other) super_return_value = super @row = @row.collect(&:dup) @@ -71,7 +150,7 @@ class CSV end # :call-seq: - # row.headers + # row.headers -> array_of_headers # # Returns the headers for this row: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" @@ -83,9 +162,9 @@ class CSV end # :call-seq: - # field(index) - # field(header) - # field(header, offset) + # field(index) -> value + # field(header) -> value + # field(header, offset) -> value # # Returns the field value for the given +index+ or +header+. # @@ -137,9 +216,9 @@ class CSV # # :call-seq: - # fetch(header) - # fetch(header, default) - # fetch(header) {|row| ... } + # fetch(header) -> value + # fetch(header, default) -> value + # fetch(header) {|row| ... } -> value # # Returns the field value as specified by +header+. # @@ -193,7 +272,7 @@ class CSV end # :call-seq: - # row.has_key?(header) + # row.has_key?(header) -> true or false # # Returns +true+ if there is a field with the given +header+, # +false+ otherwise. @@ -320,7 +399,7 @@ class CSV end # :call-seq: - # row.push(*values) ->self + # row.push(*values) -> self # # Appends each of the given +values+ to +self+ as a field; returns +self+: # source = "Name,Name,Name\nFoo,Bar,Baz\n" @@ -403,7 +482,7 @@ class CSV end # :call-seq: - # self.fields(*specifiers) + # self.fields(*specifiers) -> array_of_fields # # Returns field values per the given +specifiers+, which may be any mixture of: # - \Integer index. @@ -471,15 +550,26 @@ class CSV end alias_method :values_at, :fields - # # :call-seq: - # index( header ) - # index( header, offset ) + # index(header) -> index + # index(header, offset) -> index # - # This method will return the index of a field with the provided +header+. - # The +offset+ can be used to locate duplicate header names, as described in - # CSV::Row.field(). + # Returns the index for the given header, if it exists; + # otherwise returns +nil+. # + # With the single argument +header+, returns the index + # of the first-found field with the given +header+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.index('Name') # => 0 + # row.index('NAME') # => nil + # + # With arguments +header+ and +offset+, + # returns the index of the first-found field with given +header+, + # but ignoring the first +offset+ fields: + # row.index('Name', 1) # => 1 + # row.index('Name', 3) # => nil def index(header, minimum_index = 0) # find the pair index = headers[minimum_index..-1].index(header) @@ -487,24 +577,36 @@ class CSV index.nil? ? nil : index + minimum_index end + # :call-seq: + # row.field?(value) -> true or false # - # Returns +true+ if +data+ matches a field in this row, and +false+ - # otherwise. - # + # Returns +true+ if +value+ is a field in this row, +false+ otherwise: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.field?('Bar') # => true + # row.field?('BAR') # => false def field?(data) fields.include? data end include Enumerable + # :call-seq: + # row.each {|header, value| ... } -> self # - # Yields each pair of the row as header and field tuples (much like - # iterating over a Hash). This method returns the row for chaining. - # - # If no block is given, an Enumerator is returned. - # - # Support for Enumerable. + # Calls the block with each header-value pair; returns +self+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.each {|header, value| p [header, value] } + # Output: + # ["Name", "Foo"] + # ["Name", "Bar"] + # ["Name", "Baz"] # + # If no block is given, returns a new Enumerator: + # row.each # => #<Enumerator: #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz">:each> def each(&block) return enum_for(__method__) { size } unless block_given? @@ -515,10 +617,19 @@ class CSV alias_method :each_pair, :each + # :call-seq: + # row == other -> true or false # - # Returns +true+ if this row contains the same headers and fields in the - # same order as +other+. - # + # Returns +true+ if +other+ is a /CSV::Row that has the same + # fields (headers and values) in the same order as +self+; + # otherwise returns +false+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # other_row = table[0] + # row == other_row # => true + # other_row = table[1] + # row == other_row # => false def ==(other) return @row == other.row if other.is_a? CSV::Row @row == other @@ -548,9 +659,31 @@ class CSV end alias_method :to_hash, :to_h + # :call-seq: + # row.deconstruct_keys(keys) -> hash + # + # Returns the new \Hash suitable for pattern matching containing only the + # keys specified as an argument. + def deconstruct_keys(keys) + if keys.nil? + to_h + else + keys.to_h { |key| [key, self[key]] } + end + end + alias_method :to_ary, :to_a # :call-seq: + # row.deconstruct -> array + # + # Returns the new \Array suitable for pattern matching containing the values + # of the row. + def deconstruct + fields + end + + # :call-seq: # row.to_csv -> csv_string # # Returns the row as a \CSV String. Headers are not included: @@ -570,7 +703,7 @@ class CSV # by +index_or_header+ and +specifiers+. # # The nested objects may be instances of various classes. - # See {Dig Methods}[https://docs.ruby-lang.org/en/master/doc/dig_methods_rdoc.html]. + # See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. # # Examples: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" diff --git a/lib/csv/table.rb b/lib/csv/table.rb index 1ce0dd6daf..fb19f5453f 100644 --- a/lib/csv/table.rb +++ b/lib/csv/table.rb @@ -3,31 +3,199 @@ require "forwardable" class CSV + # = \CSV::Table + # A \CSV::Table instance represents \CSV data. + # (see {class CSV}[../CSV.html]). # - # A CSV::Table is a two-dimensional data structure for representing CSV - # documents. Tables allow you to work with the data by row or column, - # manipulate the data, and even convert the results back to CSV, if needed. + # The instance may have: + # - Rows: each is a Table::Row object. + # - Headers: names for the columns. # - # All tables returned by CSV will be constructed from this class, if header - # row processing is activated. + # === Instance Methods # + # \CSV::Table has three groups of instance methods: + # - Its own internally defined instance methods. + # - Methods included by module Enumerable. + # - Methods delegated to class Array.: + # * Array#empty? + # * Array#length + # * Array#size + # + # == Creating a \CSV::Table Instance + # + # Commonly, a new \CSV::Table instance is created by parsing \CSV source + # using headers: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.class # => CSV::Table + # + # You can also create an instance directly. See ::new. + # + # == Headers + # + # If a table has headers, the headers serve as labels for the columns of data. + # Each header serves as the label for its column. + # + # The headers for a \CSV::Table object are stored as an \Array of Strings. + # + # Commonly, headers are defined in the first row of \CSV source: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.headers # => ["Name", "Value"] + # + # If no headers are defined, the \Array is empty: + # table = CSV::Table.new([]) + # table.headers # => [] + # + # == Access Modes + # + # \CSV::Table provides three modes for accessing table data: + # - \Row mode. + # - Column mode. + # - Mixed mode (the default for a new table). + # + # The access mode for a\CSV::Table instance affects the behavior + # of some of its instance methods: + # - #[] + # - #[]= + # - #delete + # - #delete_if + # - #each + # - #values_at + # + # === \Row Mode + # + # Set a table to row mode with method #by_row!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => #<CSV::Table mode:row row_count:4> + # + # Specify a single row by an \Integer index: + # # Get a row. + # table[1] # => #<CSV::Row "Name":"bar" "Value":"1"> + # # Set a row, then get it. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) + # table[1] # => #<CSV::Row "Name":"bam" "Value":3> + # + # Specify a sequence of rows by a \Range: + # # Get rows. + # table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">] + # # Set rows, then get them. + # table[1..2] = [ + # CSV::Row.new(['Name', 'Value'], ['bat', 4]), + # CSV::Row.new(['Name', 'Value'], ['bad', 5]), + # ] + # table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]] + # + # === Column Mode + # + # Set a table to column mode with method #by_col!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # + # Specify a column by an \Integer index: + # # Get a column. + # table[0] + # # Set a column, then get it. + # table[0] = ['FOO', 'BAR', 'BAZ'] + # table[0] # => ["FOO", "BAR", "BAZ"] + # + # Specify a column by its \String header: + # # Get a column. + # table['Name'] # => ["FOO", "BAR", "BAZ"] + # # Set a column, then get it. + # table['Name'] = ['Foo', 'Bar', 'Baz'] + # table['Name'] # => ["Foo", "Bar", "Baz"] + # + # === Mixed Mode + # + # In mixed mode, you can refer to either rows or columns: + # - An \Integer index refers to a row. + # - A \Range index refers to multiple rows. + # - A \String index refers to a column. + # + # Set a table to mixed mode with method #by_col_or_row!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4> + # + # Specify a single row by an \Integer index: + # # Get a row. + # table[1] # => #<CSV::Row "Name":"bar" "Value":"1"> + # # Set a row, then get it. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) + # table[1] # => #<CSV::Row "Name":"bam" "Value":3> + # + # Specify a sequence of rows by a \Range: + # # Get rows. + # table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">] + # # Set rows, then get them. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bat', 4]) + # table[2] = CSV::Row.new(['Name', 'Value'], ['bad', 5]) + # table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]] + # + # Specify a column by its \String header: + # # Get a column. + # table['Name'] # => ["foo", "bat", "bad"] + # # Set a column, then get it. + # table['Name'] = ['Foo', 'Bar', 'Baz'] + # table['Name'] # => ["Foo", "Bar", "Baz"] class Table + # :call-seq: + # CSV::Table.new(array_of_rows, headers = nil) -> csv_table + # + # Returns a new \CSV::Table object. + # + # - Argument +array_of_rows+ must be an \Array of CSV::Row objects. + # - Argument +headers+, if given, may be an \Array of Strings. + # + # --- + # + # Create an empty \CSV::Table object: + # table = CSV::Table.new([]) + # table # => #<CSV::Table mode:col_or_row row_count:1> + # + # Create a non-empty \CSV::Table object: + # rows = [ + # CSV::Row.new([], []), + # CSV::Row.new([], []), + # CSV::Row.new([], []), + # ] + # table = CSV::Table.new(rows) + # table # => #<CSV::Table mode:col_or_row row_count:4> + # + # --- # - # Constructs a new CSV::Table from +array_of_rows+, which are expected - # to be CSV::Row objects. All rows are assumed to have the same headers. + # If argument +headers+ is an \Array of Strings, + # those Strings become the table's headers: + # table = CSV::Table.new([], headers: ['Name', 'Age']) + # table.headers # => ["Name", "Age"] # - # The optional +headers+ parameter can be set to Array of headers. - # If headers aren't set, headers are fetched from CSV::Row objects. - # Otherwise, headers() method will return headers being set in - # headers argument. + # If argument +headers+ is not given and the table has rows, + # the headers are taken from the first row: + # rows = [ + # CSV::Row.new(['Foo', 'Bar'], []), + # CSV::Row.new(['foo', 'bar'], []), + # CSV::Row.new(['FOO', 'BAR'], []), + # ] + # table = CSV::Table.new(rows) + # table.headers # => ["Foo", "Bar"] # - # A CSV::Table object supports the following Array methods through - # delegation: + # If argument +headers+ is not given and the table is empty (has no rows), + # the headers are also empty: + # table = CSV::Table.new([]) + # table.headers # => [] # - # * empty?() - # * length() - # * size() + # --- # + # Raises an exception if argument +array_of_rows+ is not an \Array object: + # # Raises NoMethodError (undefined method `first' for :foo:Symbol): + # CSV::Table.new(:foo) + # + # Raises an exception if an element of +array_of_rows+ is not a \CSV::Table object: + # # Raises NoMethodError (undefined method `headers' for :foo:Symbol): + # CSV::Table.new([:foo]) def initialize(array_of_rows, headers: nil) @table = array_of_rows @headers = headers @@ -54,88 +222,141 @@ class CSV extend Forwardable def_delegators :@table, :empty?, :length, :size + # :call-seq: + # table.by_col -> table_dup # - # Returns a duplicate table object, in column mode. This is handy for - # chaining in a single call without changing the table mode, but be aware - # that this method can consume a fair amount of memory for bigger data sets. + # Returns a duplicate of +self+, in column mode + # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # dup_table = table.by_col + # dup_table.mode # => :col + # dup_table.equal?(table) # => false # It's a dup # - # This method returns the duplicate table for chaining. Don't chain - # destructive methods (like []=()) this way though, since you are working - # with a duplicate. + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_col['Name'] # + # Also note that changes to the duplicate table will not affect the original. def by_col self.class.new(@table.dup).by_col! end + # :call-seq: + # table.by_col! -> self # - # Switches the mode of this table to column mode. All calls to indexing and - # iteration methods will work with columns until the mode is changed again. - # - # This method returns the table and is safe to chain. - # + # Sets the mode for +self+ to column mode + # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # table1 = table.by_col! + # table.mode # => :col + # table1.equal?(table) # => true # Returned self def by_col! @mode = :col self end + # :call-seq: + # table.by_col_or_row -> table_dup # - # Returns a duplicate table object, in mixed mode. This is handy for - # chaining in a single call without changing the table mode, but be aware - # that this method can consume a fair amount of memory for bigger data sets. + # Returns a duplicate of +self+, in mixed mode + # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true).by_col! + # table.mode # => :col + # dup_table = table.by_col_or_row + # dup_table.mode # => :col_or_row + # dup_table.equal?(table) # => false # It's a dup # - # This method returns the duplicate table for chaining. Don't chain - # destructive methods (like []=()) this way though, since you are working - # with a duplicate. + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_col_or_row['Name'] # + # Also note that changes to the duplicate table will not affect the original. def by_col_or_row self.class.new(@table.dup).by_col_or_row! end + # :call-seq: + # table.by_col_or_row! -> self # - # Switches the mode of this table to mixed mode. All calls to indexing and - # iteration methods will use the default intelligent indexing system until - # the mode is changed again. In mixed mode an index is assumed to be a row - # reference while anything else is assumed to be column access by headers. - # - # This method returns the table and is safe to chain. - # + # Sets the mode for +self+ to mixed mode + # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true).by_col! + # table.mode # => :col + # table1 = table.by_col_or_row! + # table.mode # => :col_or_row + # table1.equal?(table) # => true # Returned self def by_col_or_row! @mode = :col_or_row self end + # :call-seq: + # table.by_row -> table_dup # - # Returns a duplicate table object, in row mode. This is handy for chaining - # in a single call without changing the table mode, but be aware that this - # method can consume a fair amount of memory for bigger data sets. + # Returns a duplicate of +self+, in row mode + # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # dup_table = table.by_row + # dup_table.mode # => :row + # dup_table.equal?(table) # => false # It's a dup # - # This method returns the duplicate table for chaining. Don't chain - # destructive methods (like []=()) this way though, since you are working - # with a duplicate. + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_row[1] # + # Also note that changes to the duplicate table will not affect the original. def by_row self.class.new(@table.dup).by_row! end + # :call-seq: + # table.by_row! -> self # - # Switches the mode of this table to row mode. All calls to indexing and - # iteration methods will work with rows until the mode is changed again. - # - # This method returns the table and is safe to chain. - # + # Sets the mode for +self+ to row mode + # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # table1 = table.by_row! + # table.mode # => :row + # table1.equal?(table) # => true # Returned self def by_row! @mode = :row self end + # :call-seq: + # table.headers -> array_of_headers # - # Returns the headers for the first row of this table (assumed to match all - # other rows). The headers Array passed to CSV::Table.new is returned for - # empty tables. + # Returns a new \Array containing the \String headers for the table. # + # If the table is not empty, returns the headers from the first row: + # rows = [ + # CSV::Row.new(['Foo', 'Bar'], []), + # CSV::Row.new(['FOO', 'BAR'], []), + # CSV::Row.new(['foo', 'bar'], []), + # ] + # table = CSV::Table.new(rows) + # table.headers # => ["Foo", "Bar"] + # table.delete(0) + # table.headers # => ["FOO", "BAR"] + # table.delete(0) + # table.headers # => ["foo", "bar"] + # + # If the table is empty, returns a copy of the headers in the table itself: + # table.delete(0) + # table.headers # => ["Foo", "Bar"] def headers if @table.empty? @headers.dup @@ -145,17 +366,21 @@ class CSV end # :call-seq: - # table[n] -> row - # table[range] -> array_of_rows - # table[header] -> array_of_fields + # table[n] -> row or column_data + # table[range] -> array_of_rows or array_of_column_data + # table[header] -> array_of_column_data # # Returns data from the table; does not modify the table. # # --- # - # The expression <tt>table[n]</tt>, where +n+ is a non-negative \Integer, - # returns the +n+th row of the table, if that row exists, - # and if the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>: + # Fetch a \Row by Its \Integer Index:: + # - Form: <tt>table[n]</tt>, +n+ an integer. + # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>. + # - Return value: _nth_ row of the table, if that row exists; + # otherwise +nil+. + # + # Returns the _nth_ row of the table if that row exists: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => #<CSV::Table mode:row row_count:4> @@ -168,20 +393,45 @@ class CSV # # Returns +nil+ if +n+ is too large or too small: # table[4] # => nil - # table[-4] => nil + # table[-4] # => nil # # Raises an exception if the access mode is <tt>:row</tt> - # and +n+ is not an - # {Integer-convertible object}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. + # and +n+ is not an \Integer: # table.by_row! # => #<CSV::Table mode:row row_count:4> # # Raises TypeError (no implicit conversion of String into Integer): # table['Name'] # # --- # - # The expression <tt>table[range]</tt>, where +range+ is a Range object, - # returns rows from the table, beginning at row <tt>range.first</tt>, - # if those rows exist, and if the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>: + # Fetch a Column by Its \Integer Index:: + # - Form: <tt>table[n]</tt>, +n+ an \Integer. + # - Access mode: <tt>:col</tt>. + # - Return value: _nth_ column of the table, if that column exists; + # otherwise an \Array of +nil+ fields of length <tt>self.size</tt>. + # + # Returns the _nth_ column of the table if that column exists: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # table[1] # => ["0", "1", "2"] + # + # Counts backward from the last column if +n+ is negative: + # table[-2] # => ["foo", "bar", "baz"] + # + # Returns an \Array of +nil+ fields if +n+ is too large or too small: + # table[4] # => [nil, nil, nil] + # table[-4] # => [nil, nil, nil] + # + # --- + # + # Fetch Rows by \Range:: + # - Form: <tt>table[range]</tt>, +range+ a \Range object. + # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>. + # - Return value: rows from the table, beginning at row <tt>range.start</tt>, + # if those rows exists. + # + # Returns rows from the table, beginning at row <tt>range.first</tt>, + # if those rows exist: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => #<CSV::Table mode:row row_count:4> @@ -191,11 +441,11 @@ class CSV # rows = table[1..2] # => #<CSV::Row "Name":"bar" "Value":"1"> # rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">] # - # If there are too few rows, returns all from <tt>range.first</tt> to the end: + # If there are too few rows, returns all from <tt>range.start</tt> to the end: # rows = table[1..50] # => #<CSV::Row "Name":"bar" "Value":"1"> # rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">] # - # Special case: if <tt>range.start == table.size</tt>, returns an empty \Array: + # Special case: if <tt>range.start == table.size</tt>, returns an empty \Array: # table[table.size..50] # => [] # # If <tt>range.end</tt> is negative, calculates the ending index from the end: @@ -211,9 +461,41 @@ class CSV # # --- # - # The expression <tt>table[header]</tt>, where +header+ is a \String, - # returns column values (\Array of \Strings) if the column exists - # and if the access mode is <tt>:col</tt> or <tt>:col_or_row</tt>: + # Fetch Columns by \Range:: + # - Form: <tt>table[range]</tt>, +range+ a \Range object. + # - Access mode: <tt>:col</tt>. + # - Return value: column data from the table, beginning at column <tt>range.start</tt>, + # if those columns exist. + # + # Returns column values from the table, if the column exists; + # the values are arranged by row: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! + # table[0..1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # Special case: if <tt>range.start == headers.size</tt>, + # returns an \Array (size: <tt>table.size</tt>) of empty \Arrays: + # table[table.headers.size..50] # => [[], [], []] + # + # If <tt>range.end</tt> is negative, calculates the ending index from the end: + # table[0..-1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # If <tt>range.start</tt> is negative, calculates the starting index from the end: + # table[-2..2] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # If <tt>range.start</tt> is larger than <tt>table.size</tt>, + # returns an \Array of +nil+ values: + # table[4..4] # => [nil, nil, nil] + # + # --- + # + # Fetch a Column by Its \String Header:: + # - Form: <tt>table[header]</tt>, +header+ a \String header. + # - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt> + # - Return value: column data from the table, if that +header+ exists. + # + # Returns column values from the table, if the column exists: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # => #<CSV::Table mode:col row_count:4> @@ -238,22 +520,132 @@ class CSV end end + # :call-seq: + # table[n] = row -> row + # table[n] = field_or_array_of_fields -> field_or_array_of_fields + # table[header] = field_or_array_of_fields -> field_or_array_of_fields # - # In the default mixed mode, this method assigns rows for index access and - # columns for header access. You can force the index association by first - # calling by_col!() or by_row!(). + # Puts data onto the table. # - # Rows may be set to an Array of values (which will inherit the table's - # headers()) or a CSV::Row. + # --- + # + # Set a \Row by Its \Integer Index:: + # - Form: <tt>table[n] = row</tt>, +n+ an \Integer, + # +row+ a \CSV::Row instance or an \Array of fields. + # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>. + # - Return value: +row+. + # + # If the row exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_row = CSV::Row.new(['Name', 'Value'], ['bat', 3]) + # table.by_row! # => #<CSV::Table mode:row row_count:4> + # return_value = table[0] = new_row + # return_value.equal?(new_row) # => true # Returned the row + # table[0].to_h # => {"Name"=>"bat", "Value"=>3} + # + # With access mode <tt>:col_or_row</tt>: + # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4> + # table[0] = CSV::Row.new(['Name', 'Value'], ['bam', 4]) + # table[0].to_h # => {"Name"=>"bam", "Value"=>4} + # + # With an \Array instead of a \CSV::Row, inherits headers from the table: + # array = ['bad', 5] + # return_value = table[0] = array + # return_value.equal?(array) # => true # Returned the array + # table[0].to_h # => {"Name"=>"bad", "Value"=>5} # - # Columns may be set to a single value, which is copied to each row of the - # column, or an Array of values. Arrays of values are assigned to rows top - # to bottom in row major order. Excess values are ignored and if the Array - # does not have a value for each row the extra rows will receive a +nil+. + # If the row does not exist, extends the table by adding rows: + # assigns rows with +nil+ as needed: + # table.size # => 3 + # table[5] = ['bag', 6] + # table.size # => 6 + # table[3] # => nil + # table[4]# => nil + # table[5].to_h # => {"Name"=>"bag", "Value"=>6} + # + # Note that the +nil+ rows are actually +nil+, not a row of +nil+ fields. # - # Assigning to an existing column or row clobbers the data. Assigning to - # new columns creates them at the right end of the table. + # --- # + # Set a Column by Its \Integer Index:: + # - Form: <tt>table[n] = array_of_fields</tt>, +n+ an \Integer, + # +array_of_fields+ an \Array of \String fields. + # - Access mode: <tt>:col</tt>. + # - Return value: +array_of_fields+. + # + # If the column exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_col = [3, 4, 5] + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # return_value = table[1] = new_col + # return_value.equal?(new_col) # => true # Returned the column + # table[1] # => [3, 4, 5] + # # The rows, as revised: + # table.by_row! # => #<CSV::Table mode:row row_count:4> + # table[0].to_h # => {"Name"=>"foo", "Value"=>3} + # table[1].to_h # => {"Name"=>"bar", "Value"=>4} + # table[2].to_h # => {"Name"=>"baz", "Value"=>5} + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # + # If there are too few values, fills with +nil+ values: + # table[1] = [0] + # table[1] # => [0, nil, nil] + # + # If there are too many values, ignores the extra values: + # table[1] = [0, 1, 2, 3, 4] + # table[1] # => [0, 1, 2] + # + # If a single value is given, replaces all fields in the column with that value: + # table[1] = 'bat' + # table[1] # => ["bat", "bat", "bat"] + # + # --- + # + # Set a Column by Its \String Header:: + # - Form: <tt>table[header] = field_or_array_of_fields</tt>, + # +header+ a \String header, +field_or_array_of_fields+ a field value + # or an \Array of \String fields. + # - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt>. + # - Return value: +field_or_array_of_fields+. + # + # If the column exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_col = [3, 4, 5] + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # return_value = table['Value'] = new_col + # return_value.equal?(new_col) # => true # Returned the column + # table['Value'] # => [3, 4, 5] + # # The rows, as revised: + # table.by_row! # => #<CSV::Table mode:row row_count:4> + # table[0].to_h # => {"Name"=>"foo", "Value"=>3} + # table[1].to_h # => {"Name"=>"bar", "Value"=>4} + # table[2].to_h # => {"Name"=>"baz", "Value"=>5} + # table.by_col! # => #<CSV::Table mode:col row_count:4> + # + # If there are too few values, fills with +nil+ values: + # table['Value'] = [0] + # table['Value'] # => [0, nil, nil] + # + # If there are too many values, ignores the extra values: + # table['Value'] = [0, 1, 2, 3, 4] + # table['Value'] # => [0, 1, 2] + # + # If the column does not exist, extends the table by adding columns: + # table['Note'] = ['x', 'y', 'z'] + # table['Note'] # => ["x", "y", "z"] + # # The rows, as revised: + # table.by_row! + # table[0].to_h # => {"Name"=>"foo", "Value"=>0, "Note"=>"x"} + # table[1].to_h # => {"Name"=>"bar", "Value"=>1, "Note"=>"y"} + # table[2].to_h # => {"Name"=>"baz", "Value"=>2, "Note"=>"z"} + # table.by_col! + # + # If a single value is given, replaces all fields in the column with that value: + # table['Value'] = 'bat' + # table['Value'] # => ["bat", "bat", "bat"] def []=(index_or_header, value) if @mode == :row or # by index (@mode == :col_or_row and index_or_header.is_a? Integer) @@ -463,6 +855,9 @@ class CSV end end + # :call-seq: + # table.delete_if {|row_or_column| ... } -> self + # # Removes rows or columns for which the block returns a truthy value; # returns +self+. # @@ -495,9 +890,8 @@ class CSV if @mode == :row or @mode == :col_or_row # by index @table.delete_if(&block) else # by header - deleted = [] headers.each do |header| - deleted << delete(header) if yield([header, self[header]]) + delete(header) if yield([header, self[header]]) end end @@ -506,6 +900,9 @@ class CSV include Enumerable + # :call-seq: + # table.each {|row_or_column| ... ) -> self + # # Calls the block with each row or column; returns +self+. # # When the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>, @@ -534,7 +931,9 @@ class CSV return enum_for(__method__) { @mode == :col ? headers.size : size } unless block_given? if @mode == :col - headers.each { |header| yield([header, self[header]]) } + headers.each.with_index do |header, i| + yield([header, @table.map {|row| row[header, i]}]) + end else @table.each(&block) end @@ -542,6 +941,9 @@ class CSV self # for chaining end + # :call-seq: + # table == other_table -> true or false + # # Returns +true+ if all each row of +self+ <tt>==</tt> # the corresponding row of +other_table+, otherwise, +false+. # @@ -565,10 +967,14 @@ class CSV @table == other end + # :call-seq: + # table.to_a -> array_of_arrays # - # Returns the table as an Array of Arrays. Headers will be the first row, - # then all of the field rows will follow. - # + # Returns the table as an \Array of \Arrays; + # the headers are in the first row: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.to_a # => [["Name", "Value"], ["foo", "0"], ["bar", "1"], ["baz", "2"]] def to_a array = [headers] @table.each do |row| @@ -578,16 +984,29 @@ class CSV array end + # :call-seq: + # table.to_csv(**options) -> csv_string # - # Returns the table as a complete CSV String. Headers will be listed first, - # then all of the field rows. + # Returns the table as \CSV string. + # See {Options for Generating}[../CSV.html#class-CSV-label-Options+for+Generating]. # - # This method assumes you want the Table.headers(), unless you explicitly - # pass <tt>:write_headers => false</tt>. + # Defaults option +write_headers+ to +true+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.to_csv # => "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # - def to_csv(write_headers: true, **options) + # Omits the headers if option +write_headers+ is given as +false+ + # (see {Option +write_headers+}[../CSV.html#class-CSV-label-Option+write_headers]): + # table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n" + # + # Limit rows if option +limit+ is given like +2+: + # table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n" + def to_csv(write_headers: true, limit: nil, **options) array = write_headers ? [headers.to_csv(**options)] : [] - @table.each do |row| + limit ||= @table.size + limit = @table.size + 1 + limit if limit < 0 + limit = 0 if limit < 0 + @table.first(limit).each do |row| array.push(row.fields.to_csv(**options)) unless row.header_row? end @@ -613,9 +1032,24 @@ class CSV end end - # Shows the mode and size of this table in a US-ASCII String. + # :call-seq: + # table.inspect => string + # + # Returns a <tt>US-ASCII</tt>-encoded \String showing table: + # - Class: <tt>CSV::Table</tt>. + # - Access mode: <tt>:row</tt>, <tt>:col</tt>, or <tt>:col_or_row</tt>. + # - Size: Row count, including the header row. + # + # Example: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.inspect # => "#<CSV::Table mode:col_or_row row_count:4>\nName,Value\nfoo,0\nbar,1\nbaz,2\n" + # def inspect - "#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>".encode("US-ASCII") + inspected = +"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>" + summary = to_csv(limit: 5) + inspected << "\n" << summary if summary.encoding.ascii_compatible? + inspected end end end diff --git a/lib/csv/version.rb b/lib/csv/version.rb index d1d0dc0e02..e05d63d801 100644 --- a/lib/csv/version.rb +++ b/lib/csv/version.rb @@ -2,5 +2,5 @@ class CSV # The version of the installed library. - VERSION = "3.2.2" + VERSION = "3.2.6" end diff --git a/lib/csv/writer.rb b/lib/csv/writer.rb index 4a9a35c5af..030a295bc9 100644 --- a/lib/csv/writer.rb +++ b/lib/csv/writer.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true require_relative "input_record_separator" -require_relative "match_p" require_relative "row" -using CSV::MatchP if CSV.const_defined?(:MatchP) - class CSV # Note: Don't use this class directly. This is an internal class. class Writer @@ -42,7 +39,10 @@ class CSV @headers ||= row if @use_headers @lineno += 1 - row = @fields_converter.convert(row, nil, lineno) if @fields_converter + if @fields_converter + quoted_fields = [false] * row.size + row = @fields_converter.convert(row, nil, lineno, quoted_fields) + end i = -1 converted_row = row.collect do |field| @@ -97,7 +97,7 @@ class CSV return unless @headers converter = @options[:header_fields_converter] - @headers = converter.convert(@headers, nil, 0) + @headers = converter.convert(@headers, nil, 0, []) @headers.each do |header| header.freeze if header.is_a?(String) end diff --git a/lib/delegate.rb b/lib/delegate.rb index 70d4e4ad1d..387a5f063d 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -39,7 +39,7 @@ # Be advised, RDoc will not detect delegated methods. # class Delegator < BasicObject - VERSION = "0.2.0" + VERSION = "0.3.0" kernel = ::Kernel.dup kernel.class_eval do diff --git a/lib/did_you_mean/spell_checkers/method_name_checker.rb b/lib/did_you_mean/spell_checkers/method_name_checker.rb index d8ebaa4616..b5cbbb5da6 100644 --- a/lib/did_you_mean/spell_checkers/method_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/method_name_checker.rb @@ -59,6 +59,13 @@ module DidYouMean method_names = receiver.methods + receiver.singleton_methods method_names += receiver.private_methods if @private_call method_names.uniq! + # Assume that people trying to use a writer are not interested in a reader + # and vice versa + if method_name.match?(/=\Z/) + method_names.select! { |name| name.match?(/=\Z/) } + else + method_names.reject! { |name| name.match?(/=\Z/) } + end method_names else [] diff --git a/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb index b5fe50b5ed..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.1".freeze + VERSION = "1.6.3".freeze end diff --git a/lib/drb/version.rb b/lib/drb/version.rb index efaccf0319..10d33445b6 100644 --- a/lib/drb/version.rb +++ b/lib/drb/version.rb @@ -1,3 +1,3 @@ module DRb - VERSION = "2.1.0" + VERSION = "2.1.1" end diff --git a/lib/erb.gemspec b/lib/erb.gemspec index 43ffc89c69..d973cc10de 100644 --- a/lib/erb.gemspec +++ b/lib/erb.gemspec @@ -8,8 +8,8 @@ end Gem::Specification.new do |spec| spec.name = 'erb' spec.version = ERB.const_get(:VERSION, false) - spec.authors = ['Masatoshi SEKI'] - spec.email = ['seki@ruby-lang.org'] + spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun'] + 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.} @@ -27,5 +27,12 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] - spec.add_dependency 'cgi' + if RUBY_ENGINE == 'jruby' + spec.platform = 'java' + else + spec.required_ruby_version = '>= 2.7.0' + spec.extensions = ['ext/erb/escape/extconf.rb'] + end + + spec.add_dependency 'cgi', '>= 0.3.3' end diff --git a/lib/erb.rb b/lib/erb.rb index 0e42425a60..754419f819 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -14,6 +14,9 @@ require 'cgi/util' require 'erb/version' +require 'erb/compiler' +require 'erb/def_method' +require 'erb/util' # # = ERB -- Ruby Templating @@ -264,486 +267,7 @@ class ERB def self.version VERSION end -end -#-- -# ERB::Compiler -class ERB - # = ERB::Compiler - # - # Compiles ERB templates into Ruby code; the compiled code produces the - # template result when evaluated. ERB::Compiler provides hooks to define how - # generated output is handled. - # - # Internally ERB does something like this to generate the code returned by - # ERB#src: - # - # compiler = ERB::Compiler.new('<>') - # compiler.pre_cmd = ["_erbout=+''"] - # compiler.put_cmd = "_erbout.<<" - # compiler.insert_cmd = "_erbout.<<" - # compiler.post_cmd = ["_erbout"] - # - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code - # - # <i>Generates</i>: - # - # #coding:UTF-8 - # _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout - # - # By default the output is sent to the print method. For example: - # - # compiler = ERB::Compiler.new('<>') - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code - # - # <i>Generates</i>: - # - # #coding:UTF-8 - # print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze - # - # == Evaluation - # - # The compiled code can be used in any context where the names in the code - # correctly resolve. Using the last example, each of these print 'Got It!' - # - # Evaluate using a variable: - # - # obj = 'It' - # eval code - # - # Evaluate using an input: - # - # mod = Module.new - # mod.module_eval %{ - # def get(obj) - # #{code} - # end - # } - # extend mod - # get('It') - # - # Evaluate using an accessor: - # - # klass = Class.new Object - # klass.class_eval %{ - # attr_accessor :obj - # def initialize(obj) - # @obj = obj - # end - # def get_it - # #{code} - # end - # } - # klass.new('It').get_it - # - # Good! See also ERB#def_method, ERB#def_module, and ERB#def_class. - class Compiler # :nodoc: - class PercentLine # :nodoc: - def initialize(str) - @value = str - end - attr_reader :value - alias :to_s :value - end - - class Scanner # :nodoc: - @scanner_map = {} - class << self - def register_scanner(klass, trim_mode, percent) - @scanner_map[[trim_mode, percent]] = klass - end - alias :regist_scanner :register_scanner - end - - def self.default_scanner=(klass) - @default_scanner = klass - end - - def self.make_scanner(src, trim_mode, percent) - klass = @scanner_map.fetch([trim_mode, percent], @default_scanner) - klass.new(src, trim_mode, percent) - end - - DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze - DEFAULT_ETAGS = %w(%%> %>).freeze - def initialize(src, trim_mode, percent) - @src = src - @stag = nil - @stags = DEFAULT_STAGS - @etags = DEFAULT_ETAGS - end - attr_accessor :stag - attr_reader :stags, :etags - - def scan; end - end - - class TrimScanner < Scanner # :nodoc: - def initialize(src, trim_mode, percent) - super - @trim_mode = trim_mode - @percent = percent - if @trim_mode == '>' - @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:trim_line1) - elsif @trim_mode == '<>' - @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:trim_line2) - elsif @trim_mode == '-' - @scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m - @scan_line = self.method(:explicit_trim_line) - else - @scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:scan_line) - end - end - - def scan(&block) - @stag = nil - if @percent - @src.each_line do |line| - percent_line(line, &block) - end - else - @scan_line.call(@src, &block) - end - nil - end - - def percent_line(line, &block) - if @stag || line[0] != ?% - return @scan_line.call(line, &block) - end - - line[0] = '' - if line[0] == ?% - @scan_line.call(line, &block) - else - yield(PercentLine.new(line.chomp)) - end - end - - def scan_line(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - yield(token) - end - end - end - - def trim_line1(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - if token == "%>\n" || token == "%>\r\n" - yield('%>') - yield(:cr) - else - yield(token) - end - end - end - end - - def trim_line2(line) - head = nil - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - head = token unless head - if token == "%>\n" || token == "%>\r\n" - yield('%>') - if is_erb_stag?(head) - yield(:cr) - else - yield("\n") - end - head = nil - else - yield(token) - head = nil if token == "\n" - end - end - end - end - - def explicit_trim_line(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - if @stag.nil? && /[ \t]*<%-/ =~ token - yield('<%') - elsif @stag && (token == "-%>\n" || token == "-%>\r\n") - yield('%>') - yield(:cr) - elsif @stag && token == '-%>' - yield('%>') - else - yield(token) - end - end - end - end - - ERB_STAG = %w(<%= <%# <%) - def is_erb_stag?(s) - ERB_STAG.member?(s) - end - end - - Scanner.default_scanner = TrimScanner - - begin - require 'strscan' - rescue LoadError - else - class SimpleScanner < Scanner # :nodoc: - def scan - stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m - etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m - scanner = StringScanner.new(@src) - while ! scanner.eos? - scanner.scan(@stag ? etag_reg : stag_reg) - yield(scanner[1]) - yield(scanner[2]) - end - end - end - Scanner.register_scanner(SimpleScanner, nil, false) - - class ExplicitScanner < Scanner # :nodoc: - def scan - stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m - etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m - scanner = StringScanner.new(@src) - while ! scanner.eos? - scanner.scan(@stag ? etag_reg : stag_reg) - yield(scanner[1]) - - elem = scanner[2] - if /[ \t]*<%-/ =~ elem - yield('<%') - elsif elem == '-%>' - yield('%>') - yield(:cr) if scanner.scan(/(\r?\n|\z)/) - else - yield(elem) - end - end - end - end - Scanner.register_scanner(ExplicitScanner, '-', false) - end - - class Buffer # :nodoc: - def initialize(compiler, enc=nil, frozen=nil) - @compiler = compiler - @line = [] - @script = +'' - @script << "#coding:#{enc}\n" if enc - @script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil? - @compiler.pre_cmd.each do |x| - push(x) - end - end - attr_reader :script - - def push(cmd) - @line << cmd - end - - def cr - @script << (@line.join('; ')) - @line = [] - @script << "\n" - end - - def close - return unless @line - @compiler.post_cmd.each do |x| - push(x) - end - @script << (@line.join('; ')) - @line = nil - end - end - - def add_put_cmd(out, content) - out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}") - end - - def add_insert_cmd(out, content) - out.push("#{@insert_cmd}((#{content}).to_s)") - end - - # Compiles an ERB template into Ruby code. Returns an array of the code - # and encoding like ["code", Encoding]. - def compile(s) - enc = s.encoding - raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy? - s = s.b # see String#b - magic_comment = detect_magic_comment(s, enc) - out = Buffer.new(self, *magic_comment) - - self.content = +'' - scanner = make_scanner(s) - scanner.scan do |token| - next if token.nil? - next if token == '' - if scanner.stag.nil? - compile_stag(token, out, scanner) - else - compile_etag(token, out, scanner) - end - end - add_put_cmd(out, content) if content.size > 0 - out.close - return out.script, *magic_comment - end - - def compile_stag(stag, out, scanner) - case stag - when PercentLine - add_put_cmd(out, content) if content.size > 0 - self.content = +'' - out.push(stag.to_s) - out.cr - when :cr - out.cr - when '<%', '<%=', '<%#' - scanner.stag = stag - add_put_cmd(out, content) if content.size > 0 - self.content = +'' - when "\n" - content << "\n" - add_put_cmd(out, content) - self.content = +'' - when '<%%' - content << '<%' - else - content << stag - end - end - - def compile_etag(etag, out, scanner) - case etag - when '%>' - compile_content(scanner.stag, out) - scanner.stag = nil - self.content = +'' - when '%%>' - content << '%>' - else - content << etag - end - end - - def compile_content(stag, out) - case stag - when '<%' - if content[-1] == ?\n - content.chop! - out.push(content) - out.cr - else - out.push(content) - end - when '<%=' - add_insert_cmd(out, content) - when '<%#' - # commented out - end - end - - def prepare_trim_mode(mode) # :nodoc: - case mode - when 1 - return [false, '>'] - when 2 - return [false, '<>'] - when 0, nil - return [false, nil] - when String - unless mode.match?(/\A(%|-|>|<>){1,2}\z/) - warn_invalid_trim_mode(mode, uplevel: 5) - end - - perc = mode.include?('%') - if mode.include?('-') - return [perc, '-'] - elsif mode.include?('<>') - return [perc, '<>'] - elsif mode.include?('>') - return [perc, '>'] - else - [perc, nil] - end - else - warn_invalid_trim_mode(mode, uplevel: 5) - return [false, nil] - end - end - - def make_scanner(src) # :nodoc: - Scanner.make_scanner(src, @trim_mode, @percent) - end - - # Construct a new compiler using the trim_mode. See ERB::new for available - # trim modes. - def initialize(trim_mode) - @percent, @trim_mode = prepare_trim_mode(trim_mode) - @put_cmd = 'print' - @insert_cmd = @put_cmd - @pre_cmd = [] - @post_cmd = [] - end - attr_reader :percent, :trim_mode - - # The command to handle text that ends with a newline - attr_accessor :put_cmd - - # The command to handle text that is inserted prior to a newline - attr_accessor :insert_cmd - - # An array of commands prepended to compiled code - attr_accessor :pre_cmd - - # An array of commands appended to compiled code - attr_accessor :post_cmd - - private - - # A buffered text in #compile - attr_accessor :content - - def detect_magic_comment(s, enc = nil) - re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/ - frozen = nil - s.scan(re) do - comment = $+ - comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/] - case comment - when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)" - enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, '')) - when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)" - frozen = $1 - end - end - return enc, frozen - end - - def warn_invalid_trim_mode(mode, uplevel:) - warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + 1 - end - end -end - -#-- -# ERB -class ERB # # Constructs a new ERB object with the template specified in _str_. # @@ -980,100 +504,3 @@ class ERB cls end end - -#-- -# ERB::Util -class ERB - # A utility module for conversion routines, often handy in HTML generation. - module Util - public - # - # A utility method for escaping HTML tag characters in _s_. - # - # require "erb" - # include ERB::Util - # - # puts html_escape("is a > 0 & a < 10?") - # - # _Generates_ - # - # is a > 0 & a < 10? - # - def html_escape(s) - CGI.escapeHTML(s.to_s) - end - alias h html_escape - module_function :h - module_function :html_escape - - # - # A utility method for encoding the String _s_ as a URL. - # - # require "erb" - # include ERB::Util - # - # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") - # - # _Generates_ - # - # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide - # - def url_encode(s) - s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| - sprintf("%%%02X", m.unpack1("C")) - } - end - alias u url_encode - module_function :u - module_function :url_encode - end -end - -#-- -# ERB::DefMethod -class ERB - # Utility module to define eRuby script as instance method. - # - # === Example - # - # example.rhtml: - # <% for item in @items %> - # <b><%= item %></b> - # <% end %> - # - # example.rb: - # require 'erb' - # class MyClass - # extend ERB::DefMethod - # def_erb_method('render()', 'example.rhtml') - # def initialize(items) - # @items = items - # end - # end - # print MyClass.new([10,20,30]).render() - # - # result: - # - # <b>10</b> - # - # <b>20</b> - # - # <b>30</b> - # - module DefMethod - public - # define _methodname_ as instance method of current module, using ERB - # object or eRuby file - def def_erb_method(methodname, erb_or_fname) - if erb_or_fname.kind_of? String - fname = erb_or_fname - erb = ERB.new(File.read(fname)) - erb.def_method(self, methodname, fname) - else - erb = erb_or_fname - erb.def_method(self, methodname, erb.filename || '(ERB)') - end - end - module_function :def_erb_method - end -end diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb new file mode 100644 index 0000000000..547d2c4c44 --- /dev/null +++ b/lib/erb/compiler.rb @@ -0,0 +1,471 @@ +#-- +# ERB::Compiler +# +# Compiles ERB templates into Ruby code; the compiled code produces the +# template result when evaluated. ERB::Compiler provides hooks to define how +# generated output is handled. +# +# Internally ERB does something like this to generate the code returned by +# ERB#src: +# +# compiler = ERB::Compiler.new('<>') +# compiler.pre_cmd = ["_erbout=+''"] +# compiler.put_cmd = "_erbout.<<" +# compiler.insert_cmd = "_erbout.<<" +# compiler.post_cmd = ["_erbout"] +# +# code, enc = compiler.compile("Got <%= obj %>!\n") +# puts code +# +# <i>Generates</i>: +# +# #coding:UTF-8 +# _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout +# +# By default the output is sent to the print method. For example: +# +# compiler = ERB::Compiler.new('<>') +# code, enc = compiler.compile("Got <%= obj %>!\n") +# puts code +# +# <i>Generates</i>: +# +# #coding:UTF-8 +# print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze +# +# == Evaluation +# +# The compiled code can be used in any context where the names in the code +# correctly resolve. Using the last example, each of these print 'Got It!' +# +# Evaluate using a variable: +# +# obj = 'It' +# eval code +# +# Evaluate using an input: +# +# mod = Module.new +# mod.module_eval %{ +# def get(obj) +# #{code} +# end +# } +# extend mod +# get('It') +# +# Evaluate using an accessor: +# +# klass = Class.new Object +# klass.class_eval %{ +# attr_accessor :obj +# def initialize(obj) +# @obj = obj +# end +# def get_it +# #{code} +# end +# } +# klass.new('It').get_it +# +# Good! See also ERB#def_method, ERB#def_module, and ERB#def_class. +class ERB::Compiler # :nodoc: + class PercentLine # :nodoc: + def initialize(str) + @value = str + end + attr_reader :value + alias :to_s :value + end + + class Scanner # :nodoc: + @scanner_map = {} + class << self + def register_scanner(klass, trim_mode, percent) + @scanner_map[[trim_mode, percent]] = klass + end + alias :regist_scanner :register_scanner + end + + def self.default_scanner=(klass) + @default_scanner = klass + end + + def self.make_scanner(src, trim_mode, percent) + klass = @scanner_map.fetch([trim_mode, percent], @default_scanner) + klass.new(src, trim_mode, percent) + end + + DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze + DEFAULT_ETAGS = %w(%%> %>).freeze + def initialize(src, trim_mode, percent) + @src = src + @stag = nil + @stags = DEFAULT_STAGS + @etags = DEFAULT_ETAGS + end + attr_accessor :stag + attr_reader :stags, :etags + + def scan; end + end + + class TrimScanner < Scanner # :nodoc: + def initialize(src, trim_mode, percent) + super + @trim_mode = trim_mode + @percent = percent + if @trim_mode == '>' + @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:trim_line1) + elsif @trim_mode == '<>' + @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:trim_line2) + elsif @trim_mode == '-' + @scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m + @scan_line = self.method(:explicit_trim_line) + else + @scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:scan_line) + end + end + + def scan(&block) + @stag = nil + if @percent + @src.each_line do |line| + percent_line(line, &block) + end + else + @scan_line.call(@src, &block) + end + nil + end + + def percent_line(line, &block) + if @stag || line[0] != ?% + return @scan_line.call(line, &block) + end + + line[0] = '' + if line[0] == ?% + @scan_line.call(line, &block) + else + yield(PercentLine.new(line.chomp)) + end + end + + def scan_line(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + yield(token) + end + end + end + + def trim_line1(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + if token == "%>\n" || token == "%>\r\n" + yield('%>') + yield(:cr) + else + yield(token) + end + end + end + end + + def trim_line2(line) + head = nil + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + head = token unless head + if token == "%>\n" || token == "%>\r\n" + yield('%>') + if is_erb_stag?(head) + yield(:cr) + else + yield("\n") + end + head = nil + else + yield(token) + head = nil if token == "\n" + end + end + end + end + + def explicit_trim_line(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + if @stag.nil? && /[ \t]*<%-/ =~ token + yield('<%') + elsif @stag && (token == "-%>\n" || token == "-%>\r\n") + yield('%>') + yield(:cr) + elsif @stag && token == '-%>' + yield('%>') + else + yield(token) + end + end + end + end + + ERB_STAG = %w(<%= <%# <%) + def is_erb_stag?(s) + ERB_STAG.member?(s) + end + end + + Scanner.default_scanner = TrimScanner + + begin + require 'strscan' + rescue LoadError + else + class SimpleScanner < Scanner # :nodoc: + def scan + stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m + etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m + scanner = StringScanner.new(@src) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + yield(scanner[1]) + yield(scanner[2]) + end + end + end + Scanner.register_scanner(SimpleScanner, nil, false) + + class ExplicitScanner < Scanner # :nodoc: + def scan + stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m + etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m + scanner = StringScanner.new(@src) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + yield(scanner[1]) + + elem = scanner[2] + if /[ \t]*<%-/ =~ elem + yield('<%') + elsif elem == '-%>' + yield('%>') + yield(:cr) if scanner.scan(/(\r?\n|\z)/) + else + yield(elem) + end + end + end + end + Scanner.register_scanner(ExplicitScanner, '-', false) + end + + class Buffer # :nodoc: + def initialize(compiler, enc=nil, frozen=nil) + @compiler = compiler + @line = [] + @script = +'' + @script << "#coding:#{enc}\n" if enc + @script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil? + @compiler.pre_cmd.each do |x| + push(x) + end + end + attr_reader :script + + def push(cmd) + @line << cmd + end + + def cr + @script << (@line.join('; ')) + @line = [] + @script << "\n" + end + + def close + return unless @line + @compiler.post_cmd.each do |x| + push(x) + end + @script << (@line.join('; ')) + @line = nil + end + end + + def add_put_cmd(out, content) + out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}") + end + + def add_insert_cmd(out, content) + out.push("#{@insert_cmd}((#{content}).to_s)") + end + + # Compiles an ERB template into Ruby code. Returns an array of the code + # and encoding like ["code", Encoding]. + def compile(s) + enc = s.encoding + raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy? + s = s.b # see String#b + magic_comment = detect_magic_comment(s, enc) + out = Buffer.new(self, *magic_comment) + + self.content = +'' + scanner = make_scanner(s) + scanner.scan do |token| + next if token.nil? + next if token == '' + if scanner.stag.nil? + compile_stag(token, out, scanner) + else + compile_etag(token, out, scanner) + end + end + add_put_cmd(out, content) if content.size > 0 + out.close + return out.script, *magic_comment + end + + def compile_stag(stag, out, scanner) + case stag + when PercentLine + add_put_cmd(out, content) if content.size > 0 + self.content = +'' + out.push(stag.to_s) + out.cr + when :cr + out.cr + when '<%', '<%=', '<%#' + scanner.stag = stag + add_put_cmd(out, content) if content.size > 0 + self.content = +'' + when "\n" + content << "\n" + add_put_cmd(out, content) + self.content = +'' + when '<%%' + content << '<%' + else + content << stag + end + end + + def compile_etag(etag, out, scanner) + case etag + when '%>' + compile_content(scanner.stag, out) + scanner.stag = nil + self.content = +'' + when '%%>' + content << '%>' + else + content << etag + end + end + + def compile_content(stag, out) + case stag + when '<%' + if content[-1] == ?\n + content.chop! + out.push(content) + out.cr + else + out.push(content) + end + when '<%=' + add_insert_cmd(out, content) + when '<%#' + out.push("\n" * content.count("\n")) # only adjust lineno + end + end + + def prepare_trim_mode(mode) # :nodoc: + case mode + when 1 + return [false, '>'] + when 2 + return [false, '<>'] + when 0, nil + return [false, nil] + when String + unless mode.match?(/\A(%|-|>|<>){1,2}\z/) + warn_invalid_trim_mode(mode, uplevel: 5) + end + + perc = mode.include?('%') + if mode.include?('-') + return [perc, '-'] + elsif mode.include?('<>') + return [perc, '<>'] + elsif mode.include?('>') + return [perc, '>'] + else + [perc, nil] + end + else + warn_invalid_trim_mode(mode, uplevel: 5) + return [false, nil] + end + end + + def make_scanner(src) # :nodoc: + Scanner.make_scanner(src, @trim_mode, @percent) + end + + # Construct a new compiler using the trim_mode. See ERB::new for available + # trim modes. + def initialize(trim_mode) + @percent, @trim_mode = prepare_trim_mode(trim_mode) + @put_cmd = 'print' + @insert_cmd = @put_cmd + @pre_cmd = [] + @post_cmd = [] + end + attr_reader :percent, :trim_mode + + # The command to handle text that ends with a newline + attr_accessor :put_cmd + + # The command to handle text that is inserted prior to a newline + attr_accessor :insert_cmd + + # An array of commands prepended to compiled code + attr_accessor :pre_cmd + + # An array of commands appended to compiled code + attr_accessor :post_cmd + + private + + # A buffered text in #compile + attr_accessor :content + + def detect_magic_comment(s, enc = nil) + re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/ + frozen = nil + s.scan(re) do + comment = $+ + comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/] + case comment + when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)" + enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, '')) + when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)" + frozen = $1 + end + end + return enc, frozen + end + + def warn_invalid_trim_mode(mode, uplevel:) + warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + 1 + end +end diff --git a/lib/erb/def_method.rb b/lib/erb/def_method.rb new file mode 100644 index 0000000000..17f9c0f9fa --- /dev/null +++ b/lib/erb/def_method.rb @@ -0,0 +1,46 @@ +#-- +# ERB::DefMethod +# +# Utility module to define eRuby script as instance method. +# +# === Example +# +# example.rhtml: +# <% for item in @items %> +# <b><%= item %></b> +# <% end %> +# +# example.rb: +# require 'erb' +# class MyClass +# extend ERB::DefMethod +# def_erb_method('render()', 'example.rhtml') +# def initialize(items) +# @items = items +# end +# end +# print MyClass.new([10,20,30]).render() +# +# result: +# +# <b>10</b> +# +# <b>20</b> +# +# <b>30</b> +# +module ERB::DefMethod + # define _methodname_ as instance method of current module, using ERB + # object or eRuby file + def def_erb_method(methodname, erb_or_fname) + if erb_or_fname.kind_of? String + fname = erb_or_fname + erb = ERB.new(File.read(fname)) + erb.def_method(self, methodname, fname) + else + erb = erb_or_fname + erb.def_method(self, methodname, erb.filename || '(ERB)') + end + end + module_function :def_erb_method +end diff --git a/lib/erb/util.rb b/lib/erb/util.rb new file mode 100644 index 0000000000..0c1e7482a8 --- /dev/null +++ b/lib/erb/util.rb @@ -0,0 +1,62 @@ +#-- +# ERB::Escape +# +# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope +# Rails will not monkey-patch ERB::Escape#html_escape. +begin + # We don't build the C extension for JRuby, TruffleRuby, and WASM + if $LOAD_PATH.resolve_feature_path('erb/escape') + require 'erb/escape' + end +rescue LoadError # resolve_feature_path raises LoadError on TruffleRuby 22.3.0 +end +unless defined?(ERB::Escape) + module ERB::Escape + def html_escape(s) + CGI.escapeHTML(s.to_s) + end + module_function :html_escape + end +end + +#-- +# ERB::Util +# +# A utility module for conversion routines, often handy in HTML generation. +module ERB::Util + # + # A utility method for escaping HTML tag characters in _s_. + # + # require "erb" + # include ERB::Util + # + # puts html_escape("is a > 0 & a < 10?") + # + # _Generates_ + # + # is a > 0 & a < 10? + # + include ERB::Escape # html_escape + module_function :html_escape + alias h html_escape + module_function :h + + # + # A utility method for encoding the String _s_ as a URL. + # + # require "erb" + # include ERB::Util + # + # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") + # + # _Generates_ + # + # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide + # + def url_encode(s) + CGI.escapeURIComponent(s.to_s) + end + alias u url_encode + module_function :u + module_function :url_encode +end diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 0aaa38258f..38e1b76ff4 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB - VERSION = '2.2.3' + VERSION = '4.0.2' private_constant :VERSION end diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index 4c115cc828..062871ee16 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -59,8 +59,7 @@ module ErrorHighlight Spotter.new(node, **opts).spot when RubyVM::AbstractSyntaxTree::Node - # Just for compatibility - Spotter.new(node, **opts).spot + Spotter.new(obj, **opts).spot else raise TypeError, "Exception is expected" diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index 00d5671648..b69093f74e 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -37,6 +37,11 @@ module ErrorHighlight end NameError.prepend(CoreExt) - TypeError.prepend(CoreExt) - ArgumentError.prepend(CoreExt) + + if Exception.method_defined?(:detailed_message) + # ErrorHighlight is enabled for TypeError and ArgumentError only when Exception#detailed_message is available. + # This is because changing ArgumentError#message is highly incompatible. + TypeError.prepend(CoreExt) + ArgumentError.prepend(CoreExt) + end end diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb index 4279b6d05f..5afe5f06d6 100644 --- a/lib/error_highlight/version.rb +++ b/lib/error_highlight/version.rb @@ -1,3 +1,3 @@ module ErrorHighlight - VERSION = "0.4.0" + VERSION = "0.5.1" end diff --git a/lib/fileutils.rb b/lib/fileutils.rb index 74bb904e28..b495078f93 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -12,8 +12,8 @@ end # # First, what’s elsewhere. \Module \FileUtils: # -# - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html]. -# - Supplements {class File}[https://docs.ruby-lang.org/en/master/File.html] +# - Inherits from {class Object}[rdoc-ref:Object]. +# - Supplements {class File}[rdoc-ref:File] # (but is not included or extended there). # # Here, module \FileUtils provides methods that are useful for: @@ -36,6 +36,7 @@ end # - ::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 # # === Deleting # @@ -162,8 +163,8 @@ end # by applying a special pre-process: # # - If the target path points to a directory, this method uses methods -# {File#chown}[https://docs.ruby-lang.org/en/master/File.html#method-i-chown] -# and {File#chmod}[https://docs.ruby-lang.org/en/master/File.html#method-i-chmod] +# {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). @@ -179,7 +180,7 @@ end # - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452]. # module FileUtils - VERSION = "1.6.0" + VERSION = "1.7.0" def self.private_module_function(name) #:nodoc: module_function name @@ -291,7 +292,7 @@ module FileUtils # # With no keyword arguments, creates a directory at each +path+ in +list+ # by calling: <tt>Dir.mkdir(path, mode)</tt>; - # see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]: + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: # # FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"] # FileUtils.mkdir('tmp4') # => ["tmp4"] @@ -299,7 +300,7 @@ module FileUtils # Keyword arguments: # # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; - # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod]. + # see {File.chmod}[rdoc-ref:File.chmod]. # - <tt>noop: true</tt> - does not create directories. # - <tt>verbose: true</tt> - prints an equivalent command: # @@ -339,7 +340,7 @@ module FileUtils # 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}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]: + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: # # FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] # FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"] @@ -347,7 +348,7 @@ module FileUtils # Keyword arguments: # # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; - # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod]. + # see {File.chmod}[rdoc-ref:File.chmod]. # - <tt>noop: true</tt> - does not create directories. # - <tt>verbose: true</tt> - prints an equivalent command: # @@ -417,7 +418,7 @@ module FileUtils # # With no keyword arguments, removes the directory at each +path+ in +list+, # by calling: <tt>Dir.rmdir(path)</tt>; - # see {Dir.rmdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-rmdir]: + # see {Dir.rmdir}[rdoc-ref:Dir.rmdir]: # # FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] # FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"] @@ -690,6 +691,7 @@ module FileUtils # 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: # @@ -709,7 +711,10 @@ module FileUtils # # Related: FileUtils.ln_sf. # - def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + 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| @@ -729,6 +734,48 @@ module FileUtils end module_function :ln_sf + # Like 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+. # # Arguments +src+ and +dest+ @@ -1044,7 +1091,7 @@ module FileUtils module_function :copy_file # Copies \IO stream +src+ to \IO stream +dest+ via - # {IO.copy_stream}[https://docs.ruby-lang.org/en/master/IO.html#method-c-copy_stream]. + # {IO.copy_stream}[rdoc-ref:IO.copy_stream]. # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1165,7 +1212,7 @@ module FileUtils # # Keyword arguments: # - # - <tt>force: true</tt> - ignores raised exceptions of Errno::ENOENT + # - <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: @@ -1248,7 +1295,7 @@ module FileUtils # # Keyword arguments: # - # - <tt>force: true</tt> - ignores raised exceptions of Errno::ENOENT + # - <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; @@ -1315,7 +1362,7 @@ module FileUtils # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. # # Optional argument +force+ specifies whether to ignore - # raised exceptions of Errno::ENOENT and its descendants. + # raised exceptions of StandardError and its descendants. # # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # @@ -1384,12 +1431,10 @@ module FileUtils ent.remove rescue raise unless force - raise unless Errno::ENOENT === $! end end rescue raise unless force - raise unless Errno::ENOENT === $! end module_function :remove_entry_secure @@ -1415,7 +1460,7 @@ module FileUtils # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # # Optional argument +force+ specifies whether to ignore - # raised exceptions of Errno::ENOENT and its descendants. + # raised exceptions of StandardError and its descendants. # # Related: FileUtils.remove_entry_secure. # @@ -1425,12 +1470,10 @@ module FileUtils ent.remove rescue raise unless force - raise unless Errno::ENOENT === $! end end rescue raise unless force - raise unless Errno::ENOENT === $! end module_function :remove_entry @@ -1441,7 +1484,7 @@ module FileUtils # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # # Optional argument +force+ specifies whether to ignore - # raised exceptions of Errno::ENOENT and its descendants. + # raised exceptions of StandardError and its descendants. # # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # @@ -1449,7 +1492,6 @@ module FileUtils Entry_.new(path).remove_file rescue raise unless force - raise unless Errno::ENOENT === $! end module_function :remove_file @@ -1461,7 +1503,7 @@ module FileUtils # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # # Optional argument +force+ specifies whether to ignore - # raised exceptions of Errno::ENOENT and its descendants. + # raised exceptions of StandardError and its descendants. # # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # @@ -1560,14 +1602,14 @@ module FileUtils # Keyword arguments: # # - <tt>group: <i>group</i></tt> - changes the group if not +nil+, - # using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown]. + # using {File.chown}[rdoc-ref:File.chown]. # - <tt>mode: <i>permissions</i></tt> - changes the permissions. - # using {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod]. + # 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}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown]. + # using {File.chown}[rdoc-ref:File.chown]. # - <tt>preserve: true</tt> - preserve timestamps - # using {File.utime}[https://docs.ruby-lang.org/en/master/File.html#method-c-utime]. + # using {File.utime}[rdoc-ref:File.utime]. # - <tt>verbose: true</tt> - prints an equivalent command: # # FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true) @@ -1704,9 +1746,9 @@ module FileUtils # returns +list+ if it is an array, <tt>[list]</tt> otherwise: # # - Modifies each entry that is a regular file using - # {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod]. + # {File.chmod}[rdoc-ref:File.chmod]. # - Modifies each entry that is a symbolic link using - # {File.lchmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchmod]. + # {File.lchmod}[rdoc-ref:File.lchmod]. # # Argument +list+ or its elements # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. @@ -1806,9 +1848,9 @@ module FileUtils # returns +list+ if it is an array, <tt>[list]</tt> otherwise: # # - Modifies each entry that is a regular file using - # {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown]. + # {File.chown}[rdoc-ref:File.chown]. # - Modifies each entry that is a symbolic link using - # {File.lchown}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchown]. + # {File.lchown}[rdoc-ref:File.lchown]. # # Argument +list+ or its elements # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. @@ -2441,15 +2483,15 @@ module 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) @@ -2473,6 +2515,56 @@ module 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| diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 9318639f01..71b4e6adad 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -112,7 +112,7 @@ module Forwardable require 'forwardable/impl' # Version of +forwardable.rb+ - VERSION = "1.3.2" + VERSION = "1.3.3" VERSION.freeze FORWARDABLE_VERSION = VERSION FORWARDABLE_VERSION.freeze diff --git a/lib/getoptlong.rb b/lib/getoptlong.rb index 0810ed7a57..5ae0e1497c 100644 --- a/lib/getoptlong.rb +++ b/lib/getoptlong.rb @@ -368,7 +368,7 @@ # class GetoptLong # Version. - VERSION = "0.1.1" + VERSION = "0.2.0" # # Orderings. diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 7a99069faf..7a5cf94830 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,7 +40,7 @@ require 'socket' # p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr - VERSION = "1.2.4" + VERSION = "1.2.5" # 32 bit mask for IPv4 IN4MASK = 0xffffffff diff --git a/lib/irb.rb b/lib/irb.rb index 9a3a491641..2db99bcd43 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -53,6 +53,52 @@ require_relative "irb/easter-egg" # # :include: ./irb/lc/help-message # +# == Commands +# +# The following commands are available on IRB. +# +# * cwws +# * Show the current workspace. +# * cb, cws, chws +# * Change the current workspace to an object. +# * bindings, workspaces +# * Show workspaces. +# * pushb, pushws +# * Push an object to the workspace stack. +# * popb, popws +# * Pop a workspace from the workspace stack. +# * load +# * Load a Ruby file. +# * require +# * Require a Ruby file. +# * source +# * Loads a given file in the current session. +# * irb +# * Start a child IRB. +# * jobs +# * List of current sessions. +# * fg +# * Switches to the session of the given number. +# * kill +# * Kills the session with the given number. +# * help +# * Enter the mode to look up RI documents. +# * irb_info +# * Show information about IRB. +# * ls +# * Show methods, constants, and variables. +# -g [query] or -G [query] allows you to filter out the output. +# * measure +# * measure enables the mode to measure processing time. measure :off disables it. +# * $, show_source +# * Show the source code of a given method or constant. +# * @, whereami +# * Show the source code around binding.irb again. +# * debug +# * Start the debugger of debug.gem. +# * break, delete, next, step, continue, finish, backtrace, info, catch +# * Start the debugger of debug.gem and run the command on it. +# # == Configuration # # IRB reads a personal initialization file when it's invoked. @@ -93,9 +139,9 @@ require_relative "irb/easter-egg" # # === Autocompletion # -# To enable autocompletion for irb, add the following to your +.irbrc+: +# To disable autocompletion for irb, add the following to your +.irbrc+: # -# require 'irb/completion' +# IRB.conf[:USE_AUTOCOMPLETE] = false # # === History # @@ -389,11 +435,7 @@ module IRB # # Will raise an Abort exception, or the given +exception+. def IRB.irb_abort(irb, exception = Abort) - if defined? Thread - irb.context.thread.raise exception, "abort then interrupt!" - else - raise exception, "abort then interrupt!" - end + irb.context.thread.raise exception, "abort then interrupt!" end class Irb @@ -434,6 +476,17 @@ module IRB @scanner = RubyLex.new end + # A hook point for `debug` command's TracePoint after :IRB_EXIT as well as its clean-up + def debug_break + # it means the debug command is executed + if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) + # after leaving this initial breakpoint, revert the capture_frames patch + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb) + # and remove the redundant method + DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb) + end + end + def run(conf = IRB.conf) conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context @@ -510,13 +563,15 @@ module IRB @scanner.set_auto_indent(@context) if @context.auto_indent_mode - @scanner.each_top_level_statement do |line, line_no| + @scanner.each_top_level_statement(@context) do |line, line_no| signal_status(:IN_EVAL) do begin line.untaint if RUBY_VERSION < '2.7' if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? IRB.set_measure_callback end + # Assignment expression check should be done before @context.evaluate to handle code like `a /2#/ if false; a = 1` + is_assignment = assignment_expression?(line) if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? result = nil last_proc = proc{ result = @context.evaluate(line, line_no, exception: exc) } @@ -533,7 +588,7 @@ module IRB @context.evaluate(line, line_no, exception: exc) end if @context.echo? - if assignment_expression?(line) + if is_assignment if @context.echo_on_assignment? output_value(@context.echo_on_assignment? == :truncate) end @@ -596,11 +651,7 @@ module IRB if exc.backtrace order = nil - if '2.5.0' == RUBY_VERSION - # Exception#full_message doesn't have keyword arguments. - message = exc.full_message # the same of (highlight: true, order: bottom) - order = :bottom - elsif '2.5.1' <= RUBY_VERSION && RUBY_VERSION < '3.0.0' + if RUBY_VERSION < '3.0.0' if STDOUT.tty? message = exc.full_message(order: :bottom) order = :bottom @@ -831,9 +882,12 @@ module IRB # array of parsed expressions. The first element of each expression is the # expression's type. verbose, $VERBOSE = $VERBOSE, nil - result = ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0)) + code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{line}" + # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part. + node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) + ASSIGNMENT_NODE_TYPES.include?(node_type) + ensure $VERBOSE = verbose - result end ATTR_TTY = "\e[%sm" @@ -923,12 +977,13 @@ class Binding # # # See IRB@IRB+Usage for more information. - def irb + def irb(show_code: true) IRB.setup(source_location[0], argv: []) workspace = IRB::WorkSpace.new(self) - STDOUT.print(workspace.code_around_binding) + STDOUT.print(workspace.code_around_binding) if show_code binding_irb = IRB::Irb.new(workspace) binding_irb.context.irb_path = File.expand_path(source_location[0]) binding_irb.run(IRB.conf) + binding_irb.debug_break end end diff --git a/lib/irb/cmd/backtrace.rb b/lib/irb/cmd/backtrace.rb new file mode 100644 index 0000000000..f632894618 --- /dev/null +++ b/lib/irb/cmd/backtrace.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Backtrace < DebugCommand + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["backtrace", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/break.rb b/lib/irb/cmd/break.rb new file mode 100644 index 0000000000..df259a90ca --- /dev/null +++ b/lib/irb/cmd/break.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Break < DebugCommand + def self.transform_args(args) + args&.dump + end + + def execute(args = nil) + super(pre_cmds: "break #{args}") + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/catch.rb b/lib/irb/cmd/catch.rb new file mode 100644 index 0000000000..40b62c7533 --- /dev/null +++ b/lib/irb/cmd/catch.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Catch < DebugCommand + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["catch", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/chws.rb b/lib/irb/cmd/chws.rb index b28c090686..7c84ba0a4b 100644 --- a/lib/irb/cmd/chws.rb +++ b/lib/irb/cmd/chws.rb @@ -19,12 +19,18 @@ module IRB module ExtendCommand class CurrentWorkingWorkspace < Nop + category "IRB" + description "Show the current workspace." + def execute(*obj) irb_context.main end end class ChangeWorkspace < Nop + category "IRB" + description "Change the current workspace to an object." + def execute(*obj) irb_context.change_workspace(*obj) irb_context.main diff --git a/lib/irb/cmd/continue.rb b/lib/irb/cmd/continue.rb new file mode 100644 index 0000000000..9136177eef --- /dev/null +++ b/lib/irb/cmd/continue.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Continue < DebugCommand + def execute(*args) + super(do_cmds: ["continue", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb new file mode 100644 index 0000000000..7d39b9fa27 --- /dev/null +++ b/lib/irb/cmd/debug.rb @@ -0,0 +1,136 @@ +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class Debug < Nop + category "Debugging" + description "Start the debugger of debug.gem." + + BINDING_IRB_FRAME_REGEXPS = [ + '<internal:prelude>', + binding.method(:irb).source_location.first, + ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } + IRB_DIR = File.expand_path('..', __dir__) + + def execute(pre_cmds: nil, do_cmds: nil) + unless binding_irb? + puts "`debug` command is only available when IRB is started with binding.irb" + return + end + + unless setup_debugger + puts <<~MSG + You need to install the debug gem before using this command. + If you use `bundle exec`, please add `gem "debug"` into your Gemfile. + MSG + return + end + + options = { oneshot: true, hook_call: false } + if pre_cmds || do_cmds + options[:command] = ['irb', pre_cmds, do_cmds] + end + if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) + options[:skip_src] = true + end + + # To make debugger commands like `next` or `continue` work without asking + # the user to quit IRB after that, we need to exit IRB first and then hit + # a TracePoint on #debug_break. + file, lineno = IRB::Irb.instance_method(:debug_break).source_location + DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) + # exit current Irb#run call + throw :IRB_EXIT + end + + private + + def binding_irb? + caller.any? do |frame| + BINDING_IRB_FRAME_REGEXPS.any? do |regexp| + frame.match?(regexp) + end + end + end + + module SkipPathHelperForIRB + def skip_internal_path?(path) + # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved + super || path.match?(IRB_DIR) || path.match?('<internal:prelude>') + end + end + + def setup_debugger + unless defined?(DEBUGGER__::SESSION) + begin + require "debug/session" + rescue LoadError # debug.gem is not written in Gemfile + return false unless load_bundled_debug_gem + end + DEBUGGER__.start(nonstop: true) + end + + unless DEBUGGER__.respond_to?(:capture_frames_without_irb) + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) + + def DEBUGGER__.capture_frames(*args) + frames = capture_frames_without_irb(*args) + frames.reject! do |frame| + frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>" + end + frames + end + + DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) + end + + true + end + + # This is used when debug.gem is not written in Gemfile. Even if it's not + # installed by `bundle install`, debug.gem is installed by default because + # it's a bundled gem. This method tries to activate and load that. + def load_bundled_debug_gem + # Discover latest debug.gem under GEM_PATH + debug_gem = Gem.paths.path.flat_map { |path| Dir.glob("#{path}/gems/debug-*") }.select do |path| + File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+(\w+)?\z/) + end.sort_by do |path| + Gem::Version.new(File.basename(path).delete_prefix('debug-')) + end.last + return false unless debug_gem + + # Discover debug/debug.so under extensions for Ruby 3.2+ + 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 ext_path + $LOAD_PATH << ext_path.delete_suffix(ext_name) + end + $LOAD_PATH << "#{debug_gem}/lib" + begin + require "debug/session" + puts "Loaded #{File.basename(debug_gem)}" + true + rescue LoadError + false + end + end + end + + class DebugCommand < Debug + def self.category + "Debugging" + end + + def self.description + command_name = self.name.split("::").last.downcase + "Start the debugger of debug.gem and run its `#{command_name}` command." + end + end + end +end diff --git a/lib/irb/cmd/delete.rb b/lib/irb/cmd/delete.rb new file mode 100644 index 0000000000..aeb26d2572 --- /dev/null +++ b/lib/irb/cmd/delete.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Delete < DebugCommand + def execute(*args) + super(pre_cmds: ["delete", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb new file mode 100644 index 0000000000..0103891cf4 --- /dev/null +++ b/lib/irb/cmd/edit.rb @@ -0,0 +1,61 @@ +require 'shellwords' +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class Edit < Nop + category "Misc" + description 'Open a file with the editor command defined with `ENV["EDITOR"]`.' + + class << self + def transform_args(args) + # Return a string literal as is for backward compatibility + if args.nil? || args.empty? || string_literal?(args) + args + else # Otherwise, consider the input as a String for convenience + args.strip.dump + end + end + end + + def execute(*args) + path = args.first + + if path.nil? && (irb_path = @irb_context.irb_path) + path = irb_path + end + + if !File.exist?(path) + require_relative "show_source" + + source = + begin + ShowSource.find_source(path, @irb_context) + rescue NameError + # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well + # in this case, we should just ignore the error + end + + if source && File.exist?(source.file) + path = source.file + else + puts "Can not find file: #{path}" + return + end + end + + if editor = ENV['EDITOR'] + puts "command: '#{editor}'" + puts " path: #{path}" + system(*Shellwords.split(editor), path) + else + puts "Can not find editor setting: ENV['EDITOR']" + end + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/finish.rb b/lib/irb/cmd/finish.rb new file mode 100644 index 0000000000..29f100feb5 --- /dev/null +++ b/lib/irb/cmd/finish.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Finish < DebugCommand + def execute(*args) + super(do_cmds: ["finish", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb index 0497c57457..2a135cdb14 100644 --- a/lib/irb/cmd/help.rb +++ b/lib/irb/cmd/help.rb @@ -16,6 +16,20 @@ 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." + def execute(*names) require 'rdoc/ri/driver' opts = RDoc::RI::Driver.process_args([]) diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb index 37ecd762ff..2c0a32b34f 100644 --- a/lib/irb/cmd/info.rb +++ b/lib/irb/cmd/info.rb @@ -1,31 +1,18 @@ -# frozen_string_literal: false +# frozen_string_literal: true -require_relative "nop" +require_relative "debug" module IRB # :stopdoc: module ExtendCommand - class Info < Nop - def execute - Class.new { - def inspect - str = "Ruby version: #{RUBY_VERSION}\n" - str += "IRB version: #{IRB.version}\n" - str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" - str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file) - str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" - str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? - str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? - str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" - if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') - str += "Code page: #{codepage}\n" - end - str - end - alias_method :to_s, :inspect - }.new + class Info < DebugCommand + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["info", *args].join(" ")) end end end diff --git a/lib/irb/cmd/irb_info.rb b/lib/irb/cmd/irb_info.rb new file mode 100644 index 0000000000..da11e8d40b --- /dev/null +++ b/lib/irb/cmd/irb_info.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: false + +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class IrbInfo < Nop + category "IRB" + description "Show information about IRB." + + def execute + Class.new { + def inspect + str = "Ruby version: #{RUBY_VERSION}\n" + str += "IRB version: #{IRB.version}\n" + str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" + str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file) + str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" + str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? + str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? + str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" + if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') + str += "Code page: #{codepage}\n" + end + str + end + alias_method :to_s, :inspect + }.new + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/load.rb b/lib/irb/cmd/load.rb index 2c5c01e89c..2897bbd975 100644 --- a/lib/irb/cmd/load.rb +++ b/lib/irb/cmd/load.rb @@ -17,18 +17,29 @@ module IRB # :stopdoc: module ExtendCommand - class Load < Nop + class LoaderCommand < Nop include IrbLoader - def execute(file_name, priv = nil) - return irb_load(file_name, priv) + def raise_cmd_argument_error + raise CommandArgumentError.new("Please specify the file name.") end end - class Require < Nop - include IrbLoader + class Load < LoaderCommand + category "IRB" + description "Load a Ruby file." + + def execute(file_name = nil, priv = nil) + raise_cmd_argument_error unless file_name + irb_load(file_name, priv) + end + end - def execute(file_name) + class Require < LoaderCommand + category "IRB" + description "Require a Ruby file." + def execute(file_name = nil) + raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") return false if $".find{|f| f =~ rex} @@ -56,13 +67,16 @@ module IRB end end - class Source < Nop - include IrbLoader - def execute(file_name) + class Source < LoaderCommand + category "IRB" + description "Loads a given file in the current session." + + def execute(file_name = nil) + raise_cmd_argument_error unless file_name + source_file(file_name) end end end - # :startdoc: end diff --git a/lib/irb/cmd/ls.rb b/lib/irb/cmd/ls.rb index f4a7348bd1..b65fae2bf1 100644 --- a/lib/irb/cmd/ls.rb +++ b/lib/irb/cmd/ls.rb @@ -9,6 +9,18 @@ module IRB module ExtendCommand class Ls < Nop + category "Context" + description "Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output." + + def self.transform_args(args) + if match = args&.match(/\A(?<args>.+\s|)(-g|-G)\s+(?<grep>[^\s]+)\s*\n\z/) + args = match[:args] + "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + else + args + end + end + def execute(*arg, grep: nil) o = Output.new(grep: grep) @@ -21,6 +33,7 @@ module IRB o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) o.dump("locals", locals) + nil end def dump_methods(o, klass, obj) diff --git a/lib/irb/cmd/measure.rb b/lib/irb/cmd/measure.rb index a97baee9f1..9122e2dac9 100644 --- a/lib/irb/cmd/measure.rb +++ b/lib/irb/cmd/measure.rb @@ -5,6 +5,9 @@ module IRB module ExtendCommand class Measure < Nop + category "Misc" + description "`measure` enables the mode to measure processing time. `measure :off` disables it." + def initialize(*args) super(*args) end diff --git a/lib/irb/cmd/next.rb b/lib/irb/cmd/next.rb new file mode 100644 index 0000000000..d29c82e7fc --- /dev/null +++ b/lib/irb/cmd/next.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Next < DebugCommand + def execute(*args) + super(do_cmds: ["next", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 881a736722..c616c054a8 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -13,17 +13,41 @@ module IRB # :stopdoc: module ExtendCommand + class CommandArgumentError < StandardError; end + class Nop + class << self + def category(category = nil) + @category = category if category + @category + end + + def description(description = nil) + @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" def self.execute(conf, *opts, **kwargs, &block) command = new(conf) command.execute(*opts, **kwargs, &block) + rescue CommandArgumentError => e + puts e.message end else def self.execute(conf, *opts, &block) command = new(conf) command.execute(*opts, &block) + rescue CommandArgumentError => e + puts e.message end end diff --git a/lib/irb/cmd/pushws.rb b/lib/irb/cmd/pushws.rb index 791d8f8dbb..41d2e705f1 100644 --- a/lib/irb/cmd/pushws.rb +++ b/lib/irb/cmd/pushws.rb @@ -18,12 +18,18 @@ module IRB module ExtendCommand class Workspaces < Nop + category "IRB" + description "Show workspaces." + def execute(*obj) irb_context.workspaces.collect{|ws| ws.main} end end class PushWorkspace < Workspaces + category "IRB" + description "Push an object to the workspace stack." + def execute(*obj) irb_context.push_workspace(*obj) super @@ -31,6 +37,9 @@ module IRB end class PopWorkspace < Workspaces + category "IRB" + description "Pop a workspace from the workspace stack." + def execute(*obj) irb_context.pop_workspace(*obj) super diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/cmd/show_cmds.rb new file mode 100644 index 0000000000..acced27d48 --- /dev/null +++ b/lib/irb/cmd/show_cmds.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "stringio" +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class ShowCmds < Nop + category "IRB" + description "List all available commands and their description." + + def execute(*args) + commands_info = IRB::ExtendCommandBundle.all_commands_info + commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } + longest_cmd_name_length = commands_info.map { |c| c[:display_name] }.max { |a, b| a.length <=> b.length }.length + + output = StringIO.new + + commands_grouped_by_categories.each do |category, cmds| + output.puts Color.colorize(category, [:BOLD]) + + cmds.each do |cmd| + output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}" + end + + output.puts + end + + puts output.string + + nil + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index f8a17822df..ea700be4bf 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -9,12 +9,71 @@ module IRB module ExtendCommand class ShowSource < Nop + category "Context" + description "Show the source code of a given method or constant." + + 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 + + def find_source(str, irb_context) + case str + when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name + eval(str, irb_context.workspace.binding) # trigger autoload + base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } + file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+ + when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method + owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding) + method = Regexp.last_match[:method] + if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym) + file, line = owner.instance_method(method).source_location + end + when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method + receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding) + method = Regexp.last_match[:method] + file, line = receiver.method(method).source_location if receiver.respond_to?(method) + end + if file && line + Source.new(file: file, first_line: line, last_line: find_end(file, line)) + end + end + + private + + def find_end(file, first_line) + return first_line unless File.exist?(file) + lex = RubyLex.new + lines = File.read(file).lines[(first_line - 1)..-1] + tokens = RubyLex.ripper_lex_without_warning(lines.join) + prev_tokens = [] + + # chunk with line number + tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| + code = lines[0..lnum].join + prev_tokens.concat chunk + continue = lex.process_continue(prev_tokens) + code_block_open = lex.check_code_block(code, prev_tokens) + if !continue && !code_block_open + return first_line + lnum + end + end + first_line + end + end + def execute(str = nil) unless str.is_a?(String) puts "Error: Expected a string but got #{str.inspect}" return end - source = find_source(str) + + source = self.class.find_source(str, @irb_context) if source && File.exist?(source.file) show_source(source) else @@ -35,48 +94,6 @@ module IRB puts end - def find_source(str) - case str - when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name - eval(str, irb_context.workspace.binding) # trigger autoload - base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } - file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+ - when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method - owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding) - method = Regexp.last_match[:method] - if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym) - file, line = owner.instance_method(method).source_location - end - when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method - receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding) - method = Regexp.last_match[:method] - file, line = receiver.method(method).source_location if receiver.respond_to?(method) - end - if file && line - Source.new(file: file, first_line: line, last_line: find_end(file, line)) - end - end - - def find_end(file, first_line) - return first_line unless File.exist?(file) - lex = RubyLex.new - lines = File.read(file).lines[(first_line - 1)..-1] - tokens = RubyLex.ripper_lex_without_warning(lines.join) - prev_tokens = [] - - # chunk with line number - tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| - code = lines[0..lnum].join - prev_tokens.concat chunk - continue = lex.process_continue(prev_tokens) - code_block_open = lex.check_code_block(code, prev_tokens) - if !continue && !code_block_open - return first_line + lnum - end - end - first_line - end - def bold(str) Color.colorize(str, [:BOLD]) end diff --git a/lib/irb/cmd/step.rb b/lib/irb/cmd/step.rb new file mode 100644 index 0000000000..2bc74a9d79 --- /dev/null +++ b/lib/irb/cmd/step.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Step < DebugCommand + def execute(*args) + super(do_cmds: ["step", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/subirb.rb b/lib/irb/cmd/subirb.rb index b322aadc53..699b35fcb4 100644 --- a/lib/irb/cmd/subirb.rb +++ b/lib/irb/cmd/subirb.rb @@ -10,31 +10,57 @@ # require_relative "nop" -require_relative "../ext/multi-irb" module IRB # :stopdoc: module ExtendCommand - class IrbCommand < Nop + class MultiIRBCommand < Nop + def initialize(conf) + super + extend_irb_context + end + + private + + def extend_irb_context + # this extension patches IRB context like IRB.CurrentContext + require_relative "../ext/multi-irb" + end + end + + class IrbCommand < MultiIRBCommand + category "IRB" + description "Start a child IRB." + def execute(*obj) IRB.irb(nil, *obj) end end - class Jobs < Nop + class Jobs < MultiIRBCommand + category "IRB" + description "List of current sessions." + def execute IRB.JobManager end end - class Foreground < Nop - def execute(key) + class Foreground < MultiIRBCommand + category "IRB" + description "Switches to the session of the given number." + + def execute(key = nil) + raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key IRB.JobManager.switch(key) end end - class Kill < Nop + class Kill < MultiIRBCommand + category "IRB" + description "Kills the session with the given number." + def execute(*keys) IRB.JobManager.kill(*keys) end diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/cmd/whereami.rb index b8c7e047fa..8f56ba073d 100644 --- a/lib/irb/cmd/whereami.rb +++ b/lib/irb/cmd/whereami.rb @@ -7,6 +7,9 @@ module IRB module ExtendCommand class Whereami < Nop + category "Context" + description "Show the source code around binding.irb again." + def execute(*) code = irb_context.workspace.code_around_binding if code diff --git a/lib/irb/color.rb b/lib/irb/color.rb index 7071696cb2..6378e14856 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -123,13 +123,15 @@ module IRB # :nodoc: # If `complete` is false (code is incomplete), this does not warn compile_error. # This option is needed to avoid warning a user when the compile_error is happening # because the input is not wrong but just incomplete. - def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?) + def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: []) return code unless colorable symbol_state = SymbolState.new colored = +'' + lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) + code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code - scan(code, allow_last_error: !complete) do |token, str, expr| + scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr| # handle uncolorable code if token.nil? colored << Reline::Unicode.escape_for_print(str) @@ -152,6 +154,11 @@ module IRB # :nodoc: end end end + + if lvars_code + raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n") + colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors + end colored end diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index bfe6c7e7a4..34640e17f9 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -11,7 +11,29 @@ require_relative 'ruby-lex' module IRB module InputCompletor # :nodoc: + using Module.new { + refine ::Binding do + def eval_methods + ::Kernel.instance_method(:methods).bind(eval("self")).call + end + + def eval_private_methods + ::Kernel.instance_method(:private_methods).bind(eval("self")).call + end + def eval_instance_variables + ::Kernel.instance_method(:instance_variables).bind(eval("self")).call + end + + def eval_global_variables + ::Kernel.instance_method(:global_variables).bind(eval("self")).call + end + + def eval_class_constants + ::Module.instance_method(:constants).bind(eval("self.class")).call + end + end + } # Set of reserved words used by Ruby, you should not use these for # constants or variables @@ -42,25 +64,27 @@ module IRB if File.respond_to?(:absolute_path?) File.absolute_path?(p) else - if File.absolute_path(p) == p - true - else - false - end + File.absolute_path(p) == p end end + GEM_PATHS = + if defined?(Gem::Specification) + Gem::Specification.latest_specs(true).map { |s| + s.require_paths.map { |p| + if absolute_path?(p) + p + else + File.join(s.full_gem_path, p) + end + } + }.flatten + else + [] + end.freeze + def self.retrieve_gem_and_system_load_path - gem_paths = Gem::Specification.latest_specs(true).map { |s| - s.require_paths.map { |p| - if absolute_path?(p) - p - else - File.join(s.full_gem_path, p) - end - } - }.flatten if defined?(Gem::Specification) - candidates = (gem_paths.to_a | $LOAD_PATH) + candidates = (GEM_PATHS | $LOAD_PATH) candidates.map do |p| if p.respond_to?(:to_path) p.to_path @@ -149,10 +173,10 @@ module IRB receiver = $1 message = $3 - candidates = String.instance_methods.collect{|m| m.to_s} if doc_namespace "String.#{message}" else + candidates = String.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -161,10 +185,10 @@ module IRB receiver = $1 message = $2 - candidates = Regexp.instance_methods.collect{|m| m.to_s} if doc_namespace "Regexp.#{message}" else + candidates = Regexp.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -173,10 +197,10 @@ module IRB receiver = $1 message = $2 - candidates = Array.instance_methods.collect{|m| m.to_s} if doc_namespace "Array.#{message}" else + candidates = Array.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -185,29 +209,33 @@ module IRB receiver = $1 message = $2 - proc_candidates = Proc.instance_methods.collect{|m| m.to_s} - hash_candidates = Hash.instance_methods.collect{|m| m.to_s} if doc_namespace ["Proc.#{message}", "Hash.#{message}"] else + proc_candidates = Proc.instance_methods.collect{|m| m.to_s} + hash_candidates = Hash.instance_methods.collect{|m| m.to_s} select_message(receiver, message, proc_candidates | hash_candidates) end when /^(:[^:.]*)$/ # Symbol - return nil if doc_namespace - sym = $1 - candidates = Symbol.all_symbols.collect do |s| - ":" + s.id2name.encode(Encoding.default_external) - rescue EncodingError - # ignore + if doc_namespace + nil + else + sym = $1 + candidates = Symbol.all_symbols.collect do |s| + ":" + s.id2name.encode(Encoding.default_external) + rescue EncodingError + # ignore + end + candidates.grep(/^#{Regexp.quote(sym)}/) end - candidates.grep(/^#{Regexp.quote(sym)}/) - when /^::([A-Z][^:\.\(\)]*)$/ # Absolute Constant or class methods receiver = $1 + candidates = Object.constants.collect{|m| m.to_s} + if doc_namespace candidates.find { |i| i == receiver } else @@ -218,16 +246,18 @@ module IRB # Constant or class methods receiver = $1 message = $2 - begin - candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) - candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) - rescue Exception - candidates = [] - end + if doc_namespace "#{receiver}::#{message}" else - select_message(receiver, message, candidates, "::") + begin + candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) + candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) + rescue Exception + candidates = [] + end + + select_message(receiver, message, candidates.sort, "::") end when /^(:[^:.]+)(\.|::)([^.]*)$/ @@ -236,10 +266,10 @@ module IRB sep = $2 message = $3 - candidates = Symbol.instance_methods.collect{|m| m.to_s} if doc_namespace "Symbol.#{message}" else + candidates = Symbol.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates, sep) end @@ -251,6 +281,7 @@ module IRB begin instance = eval(receiver, bind) + if doc_namespace "#{instance.class.name}.#{message}" else @@ -261,7 +292,7 @@ module IRB if doc_namespace nil else - candidates = [] + [] end end @@ -283,7 +314,7 @@ module IRB if doc_namespace nil else - candidates = [] + [] end end @@ -291,6 +322,7 @@ module IRB # global var gvar = $1 all_gvars = global_variables.collect{|m| m.to_s} + if doc_namespace all_gvars.find{ |i| i == gvar } else @@ -303,10 +335,10 @@ module IRB sep = $2 message = $3 - gv = eval("global_variables", bind).collect{|m| m.to_s}.push("true", "false", "nil") - lv = eval("local_variables", bind).collect{|m| m.to_s} - iv = eval("instance_variables", bind).collect{|m| m.to_s} - cv = eval("self.class.constants", bind).collect{|m| m.to_s} + gv = bind.eval_global_variables.collect{|m| m.to_s}.push("true", "false", "nil") + lv = bind.local_variables.collect{|m| m.to_s} + iv = bind.eval_instance_variables.collect{|m| m.to_s} + cv = bind.eval_class_constants.collect{|m| m.to_s} if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver # foo.func and foo is var. OR @@ -329,11 +361,13 @@ module IRB to_ignore = ignored_modules ObjectSpace.each_object(Module){|m| next if (to_ignore.include?(m) rescue true) + next unless m.respond_to?(:instance_methods) # JRuby has modules that represent java packages. They don't include many common ruby methods candidates.concat m.instance_methods(false).collect{|x| x.to_s} } candidates.sort! candidates.uniq! end + if doc_namespace rec_class = rec.is_a?(Module) ? rec : rec.class "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" @@ -348,27 +382,28 @@ module IRB message = $1 candidates = String.instance_methods(true).collect{|m| m.to_s} + if doc_namespace "String.#{candidates.find{ |i| i == message }}" else - select_message(receiver, message, candidates) + select_message(receiver, message, candidates.sort) end else if doc_namespace - vars = eval("local_variables | instance_variables", bind).collect{|m| m.to_s} + vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} perfect_match_var = vars.find{|m| m.to_s == input} if perfect_match_var eval("#{perfect_match_var}.class.name", bind) else - candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s} + candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} candidates |= ReservedWords candidates.find{ |i| i == input } end else - candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s} + candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} candidates |= ReservedWords - candidates.grep(/^#{Regexp.quote(input)}/) + candidates.grep(/^#{Regexp.quote(input)}/).sort end end end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index b74cae1223..91fbb2fcf1 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -22,7 +22,7 @@ module IRB # # The optional +input_method+ argument: # - # +nil+:: uses stdin or Reidline or Readline + # +nil+:: uses stdin or Reline or Readline # +String+:: uses a File # +other+:: uses this as InputMethod def initialize(irb, workspace = nil, input_method = nil) @@ -32,7 +32,7 @@ module IRB else @workspace = WorkSpace.new end - @thread = Thread.current if defined? Thread + @thread = Thread.current # copy of default configuration @ap_name = IRB.conf[:AP_NAME] @@ -48,7 +48,15 @@ module IRB end if IRB.conf.has_key?(:USE_MULTILINE) @use_multiline = IRB.conf[:USE_MULTILINE] - elsif IRB.conf.has_key?(:USE_REIDLINE) # backward compatibility + elsif IRB.conf.has_key?(:USE_RELINE) # backward compatibility + warn <<~MSG.strip + USE_RELINE is deprecated, please use USE_MULTILINE instead. + MSG + @use_multiline = IRB.conf[:USE_RELINE] + elsif IRB.conf.has_key?(:USE_REIDLINE) + warn <<~MSG.strip + USE_REIDLINE is deprecated, please use USE_MULTILINE instead. + MSG @use_multiline = IRB.conf[:USE_REIDLINE] else @use_multiline = nil @@ -83,14 +91,14 @@ module IRB when nil if STDIN.tty? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline? # Both of multiline mode and singleline mode aren't specified. - @io = ReidlineInputMethod.new + @io = RelineInputMethod.new else @io = nil end when false @io = nil when true - @io = ReidlineInputMethod.new + @io = RelineInputMethod.new end unless @io case use_singleline? @@ -144,6 +152,8 @@ module IRB if @newline_before_multiline_output.nil? @newline_before_multiline_output = true end + + @command_aliases = IRB.conf[:COMMAND_ALIASES] end # The top-level workspace, see WorkSpace#main @@ -160,7 +170,7 @@ module IRB # The current input method. # # Can be either StdioInputMethod, ReadlineInputMethod, - # ReidlineInputMethod, FileInputMethod or other specified when the + # RelineInputMethod, FileInputMethod or other specified when the # context is created. See ::new for more # information on +input_method+. attr_accessor :io @@ -321,14 +331,17 @@ module IRB # See IRB@Command+line+options for more command line options. attr_accessor :back_trace_limit + # User-defined IRB command aliases + attr_accessor :command_aliases + # Alias for #use_multiline alias use_multiline? use_multiline # Alias for #use_singleline alias use_singleline? use_singleline # backward compatibility - alias use_reidline use_multiline + alias use_reline use_multiline # backward compatibility - alias use_reidline? use_multiline + alias use_reline? use_multiline # backward compatibility alias use_readline use_singleline # backward compatibility @@ -346,7 +359,7 @@ module IRB # Returns whether messages are displayed or not. def verbose? if @verbose.nil? - if @io.kind_of?(ReidlineInputMethod) + if @io.kind_of?(RelineInputMethod) false elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) false @@ -361,11 +374,11 @@ module IRB end # Whether #verbose? is +true+, and +input_method+ is either - # StdioInputMethod or ReidlineInputMethod or ReadlineInputMethod, see #io + # StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io # for more information. def prompting? verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) || - @io.kind_of?(ReidlineInputMethod) || + @io.kind_of?(RelineInputMethod) || (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod))) end @@ -472,6 +485,20 @@ module IRB line = "begin ::Kernel.raise _; rescue _.class\n#{line}\n""end" @workspace.local_variable_set(:_, exception) end + + # Transform a non-identifier alias (@, $) or keywords (next, break) + command, args = line.split(/\s/, 2) + if original = command_aliases[command.to_sym] + line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s) + command = original + end + + # Hook command-specific transformation + command_class = ExtendCommandBundle.load_command(command) + if command_class&.respond_to?(:transform_args) + line = "#{command} #{command_class.transform_args(args)}" + end + set_last_value(@workspace.evaluate(self, line, irb_path, line_no)) end @@ -513,5 +540,21 @@ module IRB end alias __to_s__ to_s alias to_s inspect + + def local_variables # :nodoc: + workspace.binding.local_variables + end + + # Return true if it's aliased from the argument and it's not an identifier. + def symbol_alias?(command) + return nil if command.match?(/\A\w+\z/) + command_aliases.key?(command.to_sym) + end + + # Return true if the command supports transforming args + def transform_args?(command) + command = command_aliases.fetch(command.to_sym, command) + ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) + end end end diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb index 74de1ecde5..e57d43a569 100644 --- a/lib/irb/ext/multi-irb.rb +++ b/lib/irb/ext/multi-irb.rb @@ -9,7 +9,6 @@ # # # -fail CantShiftToMultiIrbMode unless defined?(Thread) module IRB class JobManager diff --git a/lib/irb/ext/save-history.rb b/lib/irb/ext/save-history.rb index 2135184dba..9e7620545a 100644 --- a/lib/irb/ext/save-history.rb +++ b/lib/irb/ext/save-history.rb @@ -70,10 +70,10 @@ module IRB end history_file = IRB.rc_file("_history") unless history_file if File.exist?(history_file) - open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| + File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| f.each { |l| l = l.chomp - if self.class == ReidlineInputMethod and history.last&.end_with?("\\") + if self.class == RelineInputMethod and history.last&.end_with?("\\") history.last.delete_suffix!("\\") history.last << "\n" << l else diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 7778a0d0ce..d0829a06c4 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -45,14 +45,15 @@ module IRB # :nodoc: [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY], ] + @EXTEND_COMMANDS = [ [ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], [:irb_print_working_workspace, OVERRIDE_ALL], [:irb_cwws, OVERRIDE_ALL], [:irb_pwws, OVERRIDE_ALL], - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], [:irb_current_working_binding, OVERRIDE_ALL], [:irb_print_working_binding, OVERRIDE_ALL], [:irb_cwb, OVERRIDE_ALL], @@ -60,10 +61,10 @@ module IRB # :nodoc: ], [ :irb_change_workspace, :ChangeWorkspace, "cmd/chws", - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], [:chws, NO_OVERRIDE], [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], [:irb_change_binding, OVERRIDE_ALL], [:irb_cb, OVERRIDE_ALL], [:cb, NO_OVERRIDE], @@ -77,16 +78,16 @@ module IRB # :nodoc: ], [ :irb_push_workspace, :PushWorkspace, "cmd/pushws", - [:irb_pushws, OVERRIDE_ALL], [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], [:irb_push_binding, OVERRIDE_ALL], [:irb_pushb, OVERRIDE_ALL], [:pushb, NO_OVERRIDE], ], [ :irb_pop_workspace, :PopWorkspace, "cmd/pushws", - [:irb_popws, OVERRIDE_ALL], [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], [:irb_pop_binding, OVERRIDE_ALL], [:irb_popb, OVERRIDE_ALL], [:popb, NO_OVERRIDE], @@ -117,12 +118,56 @@ module IRB # :nodoc: ], [ + :irb_debug, :Debug, "cmd/debug", + [:debug, NO_OVERRIDE], + ], + [ + :irb_edit, :Edit, "cmd/edit", + [:edit, NO_OVERRIDE], + ], + [ + :irb_break, :Break, "cmd/break", + ], + [ + :irb_catch, :Catch, "cmd/catch", + ], + [ + :irb_next, :Next, "cmd/next" + ], + [ + :irb_delete, :Delete, "cmd/delete", + [:delete, NO_OVERRIDE], + ], + [ + :irb_step, :Step, "cmd/step", + [:step, NO_OVERRIDE], + ], + [ + :irb_continue, :Continue, "cmd/continue", + [:continue, NO_OVERRIDE], + ], + [ + :irb_finish, :Finish, "cmd/finish", + [:finish, NO_OVERRIDE], + ], + [ + :irb_backtrace, :Backtrace, "cmd/backtrace", + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE], + ], + [ + :irb_debug_info, :Info, "cmd/info", + [:info, NO_OVERRIDE], + ], + + [ :irb_help, :Help, "cmd/help", + [:show_doc, NO_OVERRIDE], [:help, NO_OVERRIDE], ], [ - :irb_info, :Info, "cmd/info" + :irb_info, :IrbInfo, "cmd/irb_info" ], [ @@ -144,24 +189,56 @@ module IRB # :nodoc: :irb_whereami, :Whereami, "cmd/whereami", [:whereami, NO_OVERRIDE], ], - + [ + :irb_show_cmds, :ShowCmds, "cmd/show_cmds", + [:show_cmds, NO_OVERRIDE], + ] ] - # Installs the default irb commands: - # - # +irb_current_working_workspace+:: Context#main - # +irb_change_workspace+:: Context#change_workspace - # +irb_workspaces+:: Context#workspaces - # +irb_push_workspace+:: Context#push_workspace - # +irb_pop_workspace+:: Context#pop_workspace - # +irb_load+:: #irb_load - # +irb_require+:: #irb_require - # +irb_source+:: IrbLoader#source_file - # +irb+:: IRB.irb - # +irb_jobs+:: JobManager - # +irb_fg+:: JobManager#switch - # +irb_kill+:: JobManager#kill - # +irb_help+:: IRB@Command+line+options + + @@commands = [] + + def self.all_commands_info + return @@commands unless @@commands.empty? + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| + if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false) + require_relative load_file + end + + klass = ExtendCommand.const_get(cmd_class, false) + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[cmd_name] + aliases += additional_aliases + end + + display_name = aliases.shift || cmd_name + @@commands << { display_name: display_name, description: klass.description, category: klass.category } + end + + @@commands + end + + # Convert a command name to its implementation class if such command exists + def self.load_command(command) + command = command.to_sym + @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| + next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } + + if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false) + require_relative load_file + end + return ExtendCommand.const_get(cmd_class, false) + end + nil + end + + # Installs the default irb commands. def self.install_extend_commands for args in @EXTEND_COMMANDS def_extend_command(*args) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index d9c4353f39..55453cc8f7 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -44,8 +44,8 @@ module IRB # :nodoc: @CONF[:IRB_RC] = nil @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) - @CONF[:USE_COLORIZE] = !ENV['NO_COLOR'] - @CONF[:USE_AUTOCOMPLETE] = true + @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? + @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false" @CONF[:INSPECT_MODE] = true @CONF[:USE_TRACER] = false @CONF[:USE_LOADER] = false @@ -158,6 +158,16 @@ module IRB # :nodoc: @CONF[:LC_MESSAGES] = Locale.new @CONF[:AT_EXIT] = [] + + @CONF[:COMMAND_ALIASES] = { + # Symbol aliases + :'$' => :show_source, + :'@' => :whereami, + # Keyword aliases + :break => :irb_break, + :catch => :irb_catch, + :next => :irb_next, + } end def IRB.set_measure_callback(type = nil, arg = nil, &block) @@ -255,8 +265,20 @@ module IRB # :nodoc: when "--nosingleline", "--noreadline" @CONF[:USE_SINGLELINE] = false when "--multiline", "--reidline" + if opt == "--reidline" + warn <<~MSG.strip + --reidline is deprecated, please use --multiline instead. + MSG + end + @CONF[:USE_MULTILINE] = true when "--nomultiline", "--noreidline" + if opt == "--noreidline" + warn <<~MSG.strip + --noreidline is deprecated, please use --nomultiline instead. + MSG + end + @CONF[:USE_MULTILINE] = false when /^--extra-doc-dir(?:=(.+))?/ opt = $1 || argv.shift @@ -379,11 +401,9 @@ module IRB # :nodoc: end if xdg_config_home = ENV["XDG_CONFIG_HOME"] irb_home = File.join(xdg_config_home, "irb") - unless File.exist? irb_home - require 'fileutils' - FileUtils.mkdir_p irb_home + if File.directory?(irb_home) + yield proc{|rc| irb_home + "/irb#{rc}"} end - yield proc{|rc| irb_home + "/irb#{rc}"} end if home = ENV["HOME"] yield proc{|rc| home+"/.irb#{rc}"} diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index eec2daa549..9480573195 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -261,7 +261,7 @@ module IRB end end - class ReidlineInputMethod < InputMethod + class RelineInputMethod < InputMethod include Reline # Creates a new input method object using Reline @@ -286,7 +286,8 @@ module IRB if IRB.conf[:USE_COLORIZE] proc do |output, complete: | next unless IRB::Color.colorable? - IRB::Color.colorize_code(output, complete: complete) + lvars = IRB.CurrentContext&.local_variables || [] + IRB::Color.colorize_code(output, complete: complete, local_variables: lvars) end else proc do |output| @@ -295,8 +296,13 @@ module IRB end Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] + if IRB.conf[:USE_AUTOCOMPLETE] - Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT) + begin + require 'rdoc' + Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT) + rescue LoadError + end end end @@ -320,11 +326,6 @@ module IRB [195, 164], # The "ä" that appears when Alt+d is pressed on xterm. [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2. ] - begin - require 'rdoc' - rescue LoadError - return nil - end if just_cursor_moving and completion_journey_data.nil? return nil @@ -460,7 +461,7 @@ module IRB # For debug message def inspect config = Reline::Config.new - str = "ReidlineInputMethod with Reline #{Reline::VERSION}" + str = "RelineInputMethod with Reline #{Reline::VERSION}" if config.respond_to?(:inputrc_path) inputrc_path = File.expand_path(config.inputrc_path) else @@ -470,4 +471,13 @@ module IRB str end end + + class ReidlineInputMethod < RelineInputMethod + def initialize + warn <<~MSG.strip + IRB::ReidlineInputMethod is deprecated, please use IRB::RelineInputMethod instead. + MSG + super + end + end end diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec index 26d0fb018f..c3e8a4dc58 100644 --- a/lib/irb/irb.gemspec +++ b/lib/irb/irb.gemspec @@ -8,8 +8,8 @@ end Gem::Specification.new do |spec| spec.name = "irb" spec.version = IRB::VERSION - spec.authors = ["Keiju ISHITSUKA"] - spec.email = ["keiju@ruby-lang.org"] + spec.authors = ["aycabta", "Keiju ISHITSUKA"] + spec.email = ["aycabta@gmail.com", "keiju@ruby-lang.org"] spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.5") + spec.required_ruby_version = Gem::Requirement.new(">= 2.6") spec.add_dependency "reline", ">= 0.3.0" end diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb index b8a7fe5a0e..cb5c21cdb4 100644 --- a/lib/irb/lc/error.rb +++ b/lib/irb/lc/error.rb @@ -48,11 +48,6 @@ module IRB super("No such job(#{val}).") end end - class CantShiftToMultiIrbMode < StandardError - def initialize - super("Can't shift to multi irb mode.") - end - end class CantChangeBinding < StandardError def initialize(val) super("Can't change binding to (#{val}).") diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb index d7c181c02e..5e3622cbae 100644 --- a/lib/irb/lc/ja/error.rb +++ b/lib/irb/lc/ja/error.rb @@ -48,11 +48,6 @@ module IRB super("そのようなジョブ(#{val})はありません.") end end - class CantShiftToMultiIrbMode < StandardError - def initialize - super("multi-irb modeに移れません.") - end - end class CantChangeBinding < StandardError def initialize(val) super("バインディング(#{val})に変更できません.") diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 77e7842469..85b336fbe1 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -48,7 +48,7 @@ class RubyLex end # io functions - def set_input(io, p = nil, context: nil, &block) + def set_input(io, p = nil, context:, &block) @io = io if @io.respond_to?(:check_termination) @io.check_termination do |code| @@ -65,6 +65,12 @@ class RubyLex false end else + # Accept any single-line input for symbol aliases or commands that transform args + command = code.split(/\s/, 2).first + if context.symbol_alias?(command) || context.transform_args?(command) + next true + end + code.gsub!(/\s*\z/, '').concat("\n") ltype, indent, continue, code_block_open = check_state(code, context: context) if ltype or indent > 0 or continue or code_block_open @@ -136,16 +142,18 @@ class RubyLex :on_param_error ] + def self.generate_local_variables_assign_code(local_variables) + "#{local_variables.join('=')}=nil;" unless local_variables.empty? + end + def self.ripper_lex_without_warning(code, context: nil) verbose, $VERBOSE = $VERBOSE, nil - if context - lvars = context.workspace&.binding&.local_variables - if lvars && !lvars.empty? - code = "#{lvars.join('=')}=nil\n#{code}" - line_no = 0 - else - line_no = 1 - end + lvars_code = generate_local_variables_assign_code(context&.local_variables || []) + if lvars_code + code = "#{lvars_code}\n#{code}" + line_no = 0 + else + line_no = 1 end compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| @@ -162,7 +170,7 @@ class RubyLex end end else - lexer.parse.reject { |it| it.pos.first == 0 } + lexer.parse.reject { |it| it.pos.first == 0 }.sort_by(&:pos) end end ensure @@ -203,12 +211,7 @@ class RubyLex last_line = lines[line_index]&.byteslice(0, byte_pointer) code += last_line if last_line @tokens = self.class.ripper_lex_without_warning(code, context: context) - corresponding_token_depth = check_corresponding_token_depth(lines, line_index) - if corresponding_token_depth - corresponding_token_depth - else - nil - end + check_corresponding_token_depth(lines, line_index) end end end @@ -219,6 +222,8 @@ class RubyLex ltype = process_literal_type(tokens) indent = process_nesting_level(tokens) continue = process_continue(tokens) + lvars_code = self.class.generate_local_variables_assign_code(context.local_variables) + code = "#{lvars_code}\n#{code}" if lvars_code code_block_open = check_code_block(code, tokens) [ltype, indent, continue, code_block_open] end @@ -238,13 +243,13 @@ class RubyLex @code_block_open = false end - def each_top_level_statement + def each_top_level_statement(context) initialize_input catch(:TERM_INPUT) do loop do begin prompt - unless l = lex + unless l = lex(context) throw :TERM_INPUT if @line == '' else @line_no += l.count("\n") @@ -274,18 +279,15 @@ class RubyLex end end - def lex + def lex(context) line = @input.call if @io.respond_to?(:check_termination) return line # multiline end code = @line + (line.nil? ? '' : line) code.gsub!(/\s*\z/, '').concat("\n") - @tokens = self.class.ripper_lex_without_warning(code) - @continue = process_continue - @code_block_open = check_code_block(code) - @indent = process_nesting_level - @ltype = process_literal_type + @tokens = self.class.ripper_lex_without_warning(code, context: context) + @ltype, @indent, @continue, @code_block_open = check_state(code, @tokens, context: context) line end @@ -302,7 +304,7 @@ class RubyLex return true elsif tokens.size >= 1 and tokens[-1].event == :on_heredoc_end # "EOH\n" return false - elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2].state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2].tok !~ /\A\.\.\.?\z/ + elsif tokens.size >= 2 and tokens[-2].state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2].tok !~ /\A\.\.\.?\z/ # end of literal except for regexp # endless range at end of line is not a continue return true @@ -383,21 +385,20 @@ class RubyLex $VERBOSE = verbose end - if defined?(Ripper::EXPR_BEG) - last_lex_state = tokens.last.state - if last_lex_state.allbits?(Ripper::EXPR_BEG) - return false - elsif last_lex_state.allbits?(Ripper::EXPR_DOT) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_CLASS) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_FNAME) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_VALUE) - return true - elsif last_lex_state.allbits?(Ripper::EXPR_ARG) - return false - end + last_lex_state = tokens.last.state + + if last_lex_state.allbits?(Ripper::EXPR_BEG) + return false + elsif last_lex_state.allbits?(Ripper::EXPR_DOT) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_CLASS) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_FNAME) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_VALUE) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_ARG) + return false end false @@ -712,6 +713,7 @@ class RubyLex i = 0 start_token = [] end_type = [] + pending_heredocs = [] while i < tokens.size t = tokens[i] case t.event @@ -735,18 +737,27 @@ class RubyLex end end when :on_backtick - start_token << t - end_type << :on_tstring_end + if t.state.allbits?(Ripper::EXPR_BEG) + start_token << t + end_type << :on_tstring_end + end when :on_qwords_beg, :on_words_beg, :on_qsymbols_beg, :on_symbols_beg start_token << t end_type << :on_tstring_end when :on_heredoc_beg - start_token << t - end_type << :on_heredoc_end + pending_heredocs << t + end + + if pending_heredocs.any? && t.tok.include?("\n") + pending_heredocs.reverse_each do |t| + start_token << t + end_type << :on_heredoc_end + end + pending_heredocs = [] end i += 1 end - start_token.last.nil? ? nil : start_token.last + pending_heredocs.first || start_token.last end def process_literal_type(tokens = @tokens) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 481d14ffd2..d1c0e54fdc 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -11,7 +11,7 @@ # module IRB # :nodoc: - VERSION = "1.4.1" + VERSION = "1.6.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2021-12-25" + @LAST_UPDATE_DATE = "2022-12-13" end diff --git a/lib/logger.rb b/lib/logger.rb index cbb0da676b..7e4dacc911 100644 --- a/lib/logger.rb +++ b/lib/logger.rb @@ -107,7 +107,7 @@ require_relative 'logger/errors' # # - \Severity (one letter). # - Timestamp. -# - Timezone. +# - Process id. # - \Severity (word). # - Program name. # - Message. @@ -147,7 +147,7 @@ require_relative 'logger/errors' # when the entry is created. # # The logged timestamp is formatted by method -# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime] +# {Time#strftime}[rdoc-ref:Time#strftime] # using this format string: # # '%Y-%m-%dT%H:%M:%S.%6N' @@ -365,7 +365,7 @@ require_relative 'logger/errors' # You can set a different format using create-time option # +shift_period_suffix+; # see details and suggestions at -# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime]. +# {Time#strftime}[rdoc-ref:Time#strftime]. # class Logger _, name, rev = %w$Id$ @@ -425,7 +425,7 @@ class Logger # Argument +datetime_format+ should be either of these: # # - A string suitable for use as a format for method - # {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime]. + # {Time#strftime}[rdoc-ref:Time#strftime]. # - +nil+: the logger uses <tt>'%Y-%m-%dT%H:%M:%S.%6N'</tt>. # def datetime_format=(datetime_format) @@ -453,7 +453,7 @@ class Logger # The proc should return a string containing the formatted entry. # # This custom formatter uses - # {String#dump}[https://docs.ruby-lang.org/en/master/String.html#method-i-dump] + # {String#dump}[rdoc-ref:String#dump] # to escape the message string: # # logger = Logger.new($stdout, progname: 'mung') diff --git a/lib/logger/formatter.rb b/lib/logger/formatter.rb index 34e07bc57f..c634dbf34d 100644 --- a/lib/logger/formatter.rb +++ b/lib/logger/formatter.rb @@ -3,7 +3,7 @@ class Logger # Default formatter for log messages. class Formatter - Format = "%s, [%s #%d] %5s -- %s: %s\n" + Format = "%.1s, [%s #%d] %5s -- %s: %s\n" DatetimeFormat = "%Y-%m-%dT%H:%M:%S.%6N" attr_accessor :datetime_format @@ -13,8 +13,7 @@ class Logger end def call(severity, time, progname, msg) - Format % [severity[0, 1], format_datetime(time), Process.pid, severity, progname, - msg2str(msg)] + sprintf(Format, severity, format_datetime(time), Process.pid, severity, progname, msg2str(msg)) end private diff --git a/lib/logger/log_device.rb b/lib/logger/log_device.rb index 8683328a5e..84277a2656 100644 --- a/lib/logger/log_device.rb +++ b/lib/logger/log_device.rb @@ -79,8 +79,10 @@ class Logger def set_dev(log) if log.respond_to?(:write) and log.respond_to?(:close) @dev = log - if log.respond_to?(:path) - @filename = log.path + if log.respond_to?(:path) and path = log.path + if File.exist?(path) + @filename = path + end end else @dev = open_logfile(log) diff --git a/lib/logger/logger.gemspec b/lib/logger/logger.gemspec index ccd4e70db7..d12db625d9 100644 --- a/lib/logger/logger.gemspec +++ b/lib/logger/logger.gemspec @@ -23,5 +23,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", ">= 0" spec.add_development_dependency "rake", ">= 12.3.3" spec.add_development_dependency "test-unit" - spec.add_development_dependency "rdoc" end diff --git a/lib/logger/version.rb b/lib/logger/version.rb index ba845a9304..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.1" + VERSION = "1.5.3" end diff --git a/lib/mjit/compiler.rb b/lib/mjit/compiler.rb deleted file mode 100644 index 49f28ab690..0000000000 --- a/lib/mjit/compiler.rb +++ /dev/null @@ -1,1019 +0,0 @@ -module RubyVM::MJIT - USE_RVARGC = C.USE_RVARGC - ROBJECT_EMBED_LEN_MAX = C.ROBJECT_EMBED_LEN_MAX - - UNSUPPORTED_INSNS = [ - :defineclass, # low priority - ] - - # Available variables and macros in JIT-ed function: - # ec: the first argument of _mjitXXX - # reg_cfp: the second argument of _mjitXXX - # GET_CFP(): refers to `reg_cfp` - # GET_EP(): refers to `reg_cfp->ep` - # GET_SP(): refers to `reg_cfp->sp`, or `(stack + stack_size)` if local_stack_p - # GET_SELF(): refers to `cfp_self` - # GET_LEP(): refers to `VM_EP_LEP(reg_cfp->ep)` - # EXEC_EC_CFP(): refers to `val = vm_exec(ec, true)` with frame setup - # CALL_METHOD(): using `GET_CFP()` and `EXEC_EC_CFP()` - # TOPN(): refers to `reg_cfp->sp`, or `*(stack + (stack_size - num - 1))` if local_stack_p - # STACK_ADDR_FROM_TOP(): refers to `reg_cfp->sp`, or `stack + (stack_size - num)` if local_stack_p - # DISPATCH_ORIGINAL_INSN(): expanded in _mjit_compile_insn.erb - # THROW_EXCEPTION(): specially defined for JIT - # RESTORE_REGS(): specially defined for `leave` - class << Compiler = Module.new - # mjit_compile - # @param funcname [String] - def compile(f, iseq, funcname, id) - status = C.compile_status.new # not freed for now - status.compiled_iseq = iseq.body - status.compiled_id = id - init_compile_status(status, iseq.body, true) # not freed for now - if iseq.body.ci_size > 0 && status.cc_entries_index == -1 - return false - end - - init_ivar_compile_status(iseq.body, status) - - if !status.compile_info.disable_send_cache && !status.compile_info.disable_inlining - unless precompile_inlinable_iseqs(f, iseq, status) - return false - end - end - - C.fprintf(f, "VALUE\n#{funcname}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp)\n{\n") - success = compile_body(f, iseq, status) - C.fprintf(f, "\n} // end of #{funcname}\n") - - return success - rescue => e # Should rb_rescue be called in C? - if C.mjit_opts.warnings || C.mjit_opts.verbose > 0 - $stderr.puts e.full_message - end - return false - end - - # mjit_compile_body - def compile_body(f, iseq, status) - status.success = true - status.local_stack_p = !iseq.body.catch_except_p - - if status.local_stack_p - src = +" VALUE stack[#{iseq.body.stack_max}];\n" - else - src = +" VALUE *stack = reg_cfp->sp;\n" - end - - unless status.inlined_iseqs.nil? # i.e. compile root - src << " static const rb_iseq_t *original_iseq = (const rb_iseq_t *)#{iseq};\n" - end - src << " static const VALUE *const original_body_iseq = (VALUE *)#{iseq.body.iseq_encoded};\n" - - src << " VALUE cfp_self = reg_cfp->self;\n" # cache self across the method - 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 && status.merge_ivar_guards_p - src << " if (UNLIKELY(!(RB_TYPE_P(GET_SELF(), T_OBJECT) && (rb_serial_t)#{status.ivar_serial} == RCLASS_SERIAL(RBASIC(GET_SELF())->klass) &&" - if USE_RVARGC - src << "#{status.max_ivar_index} < ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj) - else - if status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX - src << "#{status.max_ivar_index} < ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj) && !RB_FL_ANY_RAW(obj, ROBJECT_EMBED) - else - src << "ROBJECT_EMBED_LEN_MAX == ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj) && RB_FL_ANY_RAW(obj, ROBJECT_EMBED) - end - end - src << "))) {\n" - 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 - src << "\n" - src << " switch (reg_cfp->pc - ISEQ_BODY(reg_cfp->iseq)->iseq_encoded) {\n" - (0..iseq.body.param.opt_num).each do |i| - pc_offset = iseq.body.param.opt_table[i] - src << " case #{pc_offset}:\n" - src << " goto label_#{pc_offset};\n" - end - src << " }\n" - end - - C.fprintf(f, src) - compile_insns(0, 0, status, iseq.body, f) - compile_cancel_handler(f, iseq.body, status) - C.fprintf(f, "#undef GET_SELF\n") - return status.success - end - - # Compile one conditional branch. If it has branchXXX insn, this should be - # called multiple times for each branch. - def compile_insns(stack_size, pos, status, body, f) - branch = C.compile_branch.new # not freed for now - branch.stack_size = stack_size - branch.finish_p = false - - while pos < body.iseq_size && !already_compiled?(status, pos) && !branch.finish_p - insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos])) - status.stack_size_for_pos[pos] = branch.stack_size - - C.fprintf(f, "\nlabel_#{pos}: /* #{insn.name} */\n") - pos = compile_insn(insn, pos, status, body.iseq_encoded + (pos+1), body, branch, f) - if status.success && branch.stack_size > body.stack_max - if mjit_opts.warnings || mjit_opts.verbose > 0 - $stderr.puts "MJIT warning: JIT stack size (#{branch.stack_size}) exceeded its max size (#{body.stack_max})" - end - status.success = false - end - break unless status.success - end - end - - # Main function of JIT compilation, vm_exec_core counterpart for JIT. Compile one insn to `f`, may modify - # b->stack_size and return next position. - # - # When you add a new instruction to insns.def, it would be nice to have JIT compilation support here but - # it's optional. This JIT compiler just ignores ISeq which includes unknown instruction, and ISeq which - # does not have it can be compiled as usual. - def compile_insn(insn, pos, status, operands, body, b, f) - sp_inc = C.mjit_call_attribute_sp_inc(insn.bin, operands) - next_pos = pos + insn.len - - result = compile_insn_entry(f, insn, b.stack_size, sp_inc, status.local_stack_p, pos, next_pos, insn.len, - status.inlined_iseqs.nil?, status, operands, body) - if result.nil? - if C.mjit_opts.warnings || C.mjit_opts.verbose > 0 - $stderr.puts "MJIT warning: Skipped to compile unsupported instruction: #{insn.name}" - end - status.success = false - else - src, next_pos, finish_p, compile_insns_p = result - - C.fprintf(f, src) - b.stack_size += sp_inc - - if finish_p - b.finish_p = true - end - if compile_insns_p - if already_compiled?(status, pos + insn.len) - C.fprintf(f, "goto label_#{pos + insn.len};\n") - else - compile_insns(b.stack_size, pos + insn.len, status, body, f) - end - end - end - - # If next_pos is already compiled and this branch is not finished yet, - # next instruction won't be compiled in C code next and will need `goto`. - if !b.finish_p && next_pos < body.iseq_size && already_compiled?(status, next_pos) - C.fprintf(f, "goto label_#{next_pos};\n") - - # Verify stack size assumption is the same among multiple branches - if status.stack_size_for_pos[next_pos] != b.stack_size - if mjit_opts.warnings || mjit_opts.verbose > 0 - $stderr.puts "MJIT warning: JIT stack assumption is not the same between branches (#{status.stack_size_for_pos[next_pos]} != #{b.stack_size})\n" - end - status.success = false - end - end - - return next_pos - end - - # mjit_comiple.inc.erb - def compile_insn_entry(f, insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, status, operands, body) - finish_p = false - compile_insns = false - - # TODO: define this outside this method, or at least cache it - opt_send_without_block = INSNS.values.find { |i| i.name == :opt_send_without_block } - if opt_send_without_block.nil? - raise 'opt_send_without_block not found' - end - send_compatible_opt_insns = INSNS.values.select do |insn| - insn.name.start_with?('opt_') && opt_send_without_block.opes == insn.opes && - insn.expr.lines.any? { |l| l.match(/\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/) } - end.map(&:name) - - case insn.name - when *UNSUPPORTED_INSNS - return nil - when :opt_send_without_block, :send - if src = compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body) - return src, next_pos, finish_p, compile_insns - end - when *send_compatible_opt_insns - if C.has_cache_for_send(captured_cc_entries(status)[call_data_index(C.CALL_DATA.new(operands[0]), body)], insn.bin) && - src = compile_send(opt_send_without_block, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body) - return src, next_pos, finish_p, compile_insns - end - when :getinstancevariable, :setinstancevariable - if src = compile_ivar(insn.name, stack_size, pos, status, operands, body) - return src, next_pos, finish_p, compile_insns - end - when :invokebuiltin, :opt_invokebuiltin_delegate - if compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands) - return '', next_pos, finish_p, compile_insns - end - when :opt_getconstant_path - if src = compile_getconstant_path(stack_size, pos, insn_len, operands, status) - return src, next_pos, finish_p, compile_insns - end - when :leave, :opt_invokebuiltin_delegate_leave - src = +'' - - # opt_invokebuiltin_delegate_leave also implements leave insn. We need to handle it here for inlining. - if insn.name == :opt_invokebuiltin_delegate_leave - compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands) - else - if stack_size != 1 - $stderr.puts "MJIT warning: Unexpected JIT stack_size on leave: #{stack_size}" # TODO: check mjit_opts? - return nil - end - end - - # Skip vm_pop_frame for inlined call - unless inlined_iseq_p - # Cancel on interrupts to make leave insn leaf - src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n" - src << " }\n" - src << " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(reg_cfp);\n" # vm_pop_frame - end - src << " return stack[0];\n" - finish_p = true - return src, next_pos, finish_p, compile_insns - end - - return compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands) - rescue => e - puts e.full_message - nil - end - - private - - # Optimized case of send / opt_send_without_block instructions. - # _mjit_compile_send.erb - def compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body) - # compiler: Use captured cc to avoid race condition - cd = C.CALL_DATA.new(operands[0]) - cd_index = call_data_index(cd, body) - captured_cc = captured_cc_entries(status)[cd_index] - - # compiler: Inline send insn where some supported fastpath is used. - ci = cd.ci - kw_splat = (C.vm_ci_flag(ci) & C.VM_CALL_KW_SPLAT) > 0 - if !status.compile_info.disable_send_cache && has_valid_method_type?(captured_cc) && ( - # `CC_SET_FASTPATH(cd->cc, vm_call_cfunc_with_frame, ...)` in `vm_call_cfunc` - (vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC && !C.rb_splat_or_kwargs_p(ci) && !kw_splat) || - # `CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(...), vm_call_iseq_optimizable_p(...))` in `vm_callee_setup_arg`, - # and support only non-VM_CALL_TAILCALL path inside it - (vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_ISEQ && - C.fastpath_applied_iseq_p(ci, captured_cc, iseq = def_iseq_ptr(vm_cc_cme(captured_cc).def)) && - (C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL) == 0) - ) - src = +"{\n" - - # JIT: Invalidate call cache if it requires vm_search_method. This allows to inline some of following things. - src << " const struct rb_callcache *cc = (struct rb_callcache *)#{captured_cc};\n" - src << " const rb_callable_method_entry_t *cc_cme = (rb_callable_method_entry_t *)#{vm_cc_cme(captured_cc)};\n" - src << " const VALUE recv = stack[#{stack_size + sp_inc - 1}];\n" - # If opt_class_of is true, use RBASIC_CLASS instead of CLASS_OF to reduce code size - opt_class_of = !maybe_special_const?(captured_cc.klass) - src << " if (UNLIKELY(#{opt_class_of ? 'RB_SPECIAL_CONST_P(recv)' : 'false'} || !vm_cc_valid_p(cc, cc_cme, #{opt_class_of ? 'RBASIC_CLASS' : 'CLASS_OF'}(recv)))) {\n" - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " goto send_cancel;\n" - src << " }\n" - - # JIT: move sp and pc if necessary - pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos) - - # JIT: If ISeq is inlinable, call the inlined method without pushing a frame. - if iseq && status.inlined_iseqs && iseq.body.to_i == status.inlined_iseqs[pos]&.to_i - src << " {\n" - src << " VALUE orig_self = reg_cfp->self;\n" - src << " reg_cfp->self = stack[#{stack_size + sp_inc - 1}];\n" - src << " stack[#{stack_size + sp_inc - 1}] = _mjit#{status.compiled_id}_inlined_#{pos}(ec, reg_cfp, orig_self, original_iseq);\n" - src << " reg_cfp->self = orig_self;\n" - src << " }\n" - else - # JIT: Forked `vm_sendish` (except method_explorer = vm_search_method_wrap) to inline various things - src << " {\n" - src << " VALUE val;\n" - src << " struct rb_calling_info calling;\n" - if insn.name == :send - src << " calling.block_handler = vm_caller_setup_arg_block(ec, reg_cfp, (const struct rb_callinfo *)#{ci}, (rb_iseq_t *)0x#{operands[1].to_s(16)}, FALSE);\n" - else - src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n" - end - src << " calling.kw_splat = #{kw_splat ? 1 : 0};\n" - src << " calling.recv = stack[#{stack_size + sp_inc - 1}];\n" - src << " calling.argc = #{C.vm_ci_argc(ci)};\n" - - if vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC - # TODO: optimize this more - src << " calling.ci = (const struct rb_callinfo *)#{ci};\n" # creating local cd here because operand's cd->cc may not be the same as inlined cc. - src << " calling.cc = cc;" - src << " val = vm_call_cfunc_with_frame(ec, reg_cfp, &calling);\n" - else # :iseq - # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE - src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, cc_cme, 0, #{iseq.body.param.size}, #{iseq.body.local_table_size});\n" - if iseq.body.catch_except_p - src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n" - src << " val = vm_exec(ec, true);\n" - else - src << " if ((val = jit_exec(ec)) == Qundef) {\n" - src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n" # This is vm_call0_body's code after vm_call_iseq_setup - src << " val = vm_exec(ec, false);\n" - src << " }\n" - end - end - src << " stack[#{stack_size + sp_inc - 1}] = val;\n" - src << " }\n" - - # JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow. - src << " if (UNLIKELY(!mjit_call_p)) {\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n" - if !pc_moved_p - src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" - end - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n" - src << " goto cancel;\n" - src << " }\n" - end - - src << "}\n" - return src - else - return nil - end - end - - # _mjit_compile_ivar.erb - def compile_ivar(insn_name, stack_size, pos, status, operands, body) - ic_copy = (status.is_entries + (C.iseq_inline_storage_entry.new(operands[1]) - body.is_entries)).iv_cache - - src = +'' - if !status.compile_info.disable_ivar_cache && ic_copy.entry - # JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`. - # compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos) - - # JIT: prepare vm_getivar/vm_setivar arguments and variables - src << "{\n" - src << " VALUE obj = GET_SELF();\n" - src << " const uint32_t index = #{ic_copy.entry.index};\n" - if status.merge_ivar_guards_p - # JIT: Access ivar without checking these VM_ASSERTed prerequisites as we checked them in the beginning of `mjit_compile_body` - src << " VM_ASSERT(RB_TYPE_P(obj, T_OBJECT));\n" - src << " VM_ASSERT((rb_serial_t)#{ic_copy.entry.class_serial} == RCLASS_SERIAL(RBASIC(obj)->klass));\n" - src << " VM_ASSERT(index < ROBJECT_NUMIV(obj));\n" - if insn_name == :setinstancevariable - if USE_RVARGC - src << " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && index < ROBJECT_NUMIV(obj))) {\n" - src << " RB_OBJ_WRITE(obj, &ROBJECT_IVPTR(obj)[index], stack[#{stack_size - 1}]);\n" - else - heap_ivar_p = status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX - src << " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && #{heap_ivar_p ? 'true' : 'RB_FL_ANY_RAW(obj, ROBJECT_EMBED)'})) {\n" - src << " RB_OBJ_WRITE(obj, &ROBJECT(obj)->as.#{heap_ivar_p ? 'heap.ivptr[index]' : 'ary[index]'}, stack[#{stack_size - 1}]);\n" - end - src << " }\n" - else - src << " VALUE val;\n" - if USE_RVARGC - src << " if (LIKELY(index < ROBJECT_NUMIV(obj) && (val = ROBJECT_IVPTR(obj)[index]) != Qundef)) {\n" - else - heap_ivar_p = status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX - src << " if (LIKELY(#{heap_ivar_p ? 'true' : 'RB_FL_ANY_RAW(obj, ROBJECT_EMBED)'} && (val = ROBJECT(obj)->as.#{heap_ivar_p ? 'heap.ivptr[index]' : 'ary[index]'}) != Qundef)) {\n" - end - src << " stack[#{stack_size}] = val;\n" - src << " }\n" - end - else - src << " const rb_serial_t ic_serial = (rb_serial_t)#{ic_copy.entry.class_serial};\n" - # JIT: cache hit path of vm_getivar/vm_setivar, or cancel JIT (recompile it with exivar) - if insn_name == :setinstancevariable - src << " if (LIKELY(RB_TYPE_P(obj, T_OBJECT) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && index < ROBJECT_NUMIV(obj) && !RB_OBJ_FROZEN_RAW(obj))) {\n" - src << " VALUE *ptr = ROBJECT_IVPTR(obj);\n" - src << " RB_OBJ_WRITE(obj, &ptr[index], stack[#{stack_size - 1}]);\n" - src << " }\n" - else - src << " VALUE val;\n" - src << " if (LIKELY(RB_TYPE_P(obj, T_OBJECT) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && index < ROBJECT_NUMIV(obj) && (val = ROBJECT_IVPTR(obj)[index]) != Qundef)) {\n" - src << " stack[#{stack_size}] = val;\n" - src << " }\n" - end - end - src << " else {\n" - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " goto ivar_cancel;\n" - src << " }\n" - src << "}\n" - return src - elsif insn_name == :getinstancevariable && !status.compile_info.disable_exivar_cache && ic_copy.entry - # JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`. - # compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos) - - # JIT: prepare vm_getivar's arguments and variables - src << "{\n" - src << " VALUE obj = GET_SELF();\n" - src << " const rb_serial_t ic_serial = (rb_serial_t)#{ic_copy.entry.class_serial};\n" - src << " const uint32_t index = #{ic_copy.entry.index};\n" - # JIT: cache hit path of vm_getivar, or cancel JIT (recompile it without any ivar optimization) - src << " struct gen_ivtbl *ivtbl;\n" - src << " VALUE val;\n" - src << " if (LIKELY(FL_TEST_RAW(obj, FL_EXIVAR) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && rb_ivar_generic_ivtbl_lookup(obj, &ivtbl) && index < ivtbl->numiv && (val = ivtbl->ivptr[index]) != Qundef)) {\n" - src << " stack[#{stack_size}] = val;\n" - src << " }\n" - src << " else {\n" - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " goto exivar_cancel;\n" - src << " }\n" - src << "}\n" - return src - else - return nil - end - end - - # _mjit_compile_invokebulitin.erb - def compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands) - bf = C.RB_BUILTIN.new(operands[0]) - if bf.compiler > 0 - C.fprintf(f, "{\n") - C.fprintf(f, " VALUE val;\n") - C.builtin_compiler(f, bf, operands[1], stack_size, body.builtin_inline_p) - C.fprintf(f, " stack[#{stack_size + sp_inc - 1}] = val;\n") - C.fprintf(f, "}\n") - return true - else - return false - end - end - - # _mjit_compile_getconstant_path.erb - def compile_getconstant_path(stack_size, pos, insn_len, operands, status) - ice = C.IC.new(operands[0]).entry - if !status.compile_info.disable_const_cache && ice - # JIT: Inline everything in IC, and cancel the slow path - src = +" if (vm_inlined_ic_hit_p(#{ice.flags}, #{ice.value}, (const rb_cref_t *)#{to_addr(ice.ic_cref)}, reg_cfp->ep)) {\n" - src << " stack[#{stack_size}] = #{ice.value};\n" - src << " }\n" - src << " else {\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " goto const_cancel;\n" - src << " }\n" - return src - else - return nil - end - end - - # _mjit_compile_insn.erb - def compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands) - src = +'' - finish_p = false - compile_insns = false - - # JIT: Declare stack_size to be used in some macro of _mjit_compile_insn_body.erb - src << "{\n" - if local_stack_p - src << " MAYBE_UNUSED(unsigned int) stack_size = #{stack_size};\n" - end - - # JIT: Declare variables for operands, popped values and return values - insn.declarations.each do |decl| - src << " #{decl};\n" - end - - # JIT: Set const expressions for `RubyVM::OperandsUnifications` insn - insn.preamble.each do |amble| - src << "#{amble.sub(/const \S+\s+/, '')}\n" - end - - # JIT: Initialize operands - insn.opes.each_with_index do |ope, i| - src << " #{ope.fetch(:name)} = (#{ope.fetch(:type)})#{operands[i]};\n" - # TODO: resurrect comment_id - end - - # JIT: Initialize popped values - insn.pops.reverse_each.with_index.reverse_each do |pop, i| - src << " #{pop.fetch(:name)} = stack[#{stack_size - (i + 1)}];\n" - end - - # JIT: move sp and pc if necessary - pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos) - - # JIT: Print insn body in insns.def - next_pos = compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands) - - # JIT: Set return values - insn.rets.reverse_each.with_index do |ret, i| - # TOPN(n) = ... - src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n" - end - - # JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow. - # leaf insn may not cancel JIT. leaf_without_check_ints is covered in RUBY_VM_CHECK_INTS of _mjit_compile_insn_body.erb. - unless insn.always_leaf? || insn.leaf_without_check_ints? - src << " if (UNLIKELY(!mjit_call_p)) {\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n" - if !pc_moved_p - src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" - end - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n" - src << " goto cancel;\n" - src << " }\n" - end - - src << "}\n" - - # compiler: If insn has conditional JUMP, the code should go to the branch not targeted by JUMP next. - if insn.expr.match?(/if\s+\([^{}]+\)\s+\{[^{}]+JUMP\([^)]+\);[^{}]+\}/) - compile_insns = true - end - - # compiler: If insn returns (leave) or does longjmp (throw), the branch should no longer be compiled. TODO: create attr for it? - if insn.expr.match?(/\sTHROW_EXCEPTION\([^)]+\);/) || insn.expr.match?(/\bvm_pop_frame\(/) - finish_p = true - end - - return src, next_pos, finish_p, compile_insns - end - - # _mjit_compile_insn_body.erb - def compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands) - # Print a body of insn, but with macro expansion. - expand_simple_macros(insn.expr).each_line do |line| - # Expand dynamic macro here - # TODO: support combination of following macros in the same line - case line - when /\A\s+RUBY_VM_CHECK_INTS\(ec\);\s+\z/ - if insn.leaf_without_check_ints? # lazily move PC and optionalize mjit_call_p here - src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n" - src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width)); - src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n" - src << " if (UNLIKELY(!mjit_call_p)) {\n" - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n" - src << " goto cancel;\n" - src << " }\n" - src << " }\n" - else - src << to_cstr(line) - end - 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 - else_offset = cast_offset(operands[1]) - cdhash = C.cdhash_to_hash(operands[0]) - base_pos = pos + insn_len - - src << " switch (#{dest}) {\n" - cdhash.each do |_key, offset| - src << " case #{offset}:\n" - src << " goto label_#{base_pos + offset};\n" - end - src << " case #{else_offset}:\n" - src << " goto label_#{base_pos + else_offset};\n" - src << " }\n" - else - # Before we `goto` next insn, we need to set return values, especially for getinlinecache - insn.rets.reverse_each.with_index do |ret, i| - # TOPN(n) = ... - src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n" - end - - next_pos = pos + insn_len + cast_offset(operands[0]) # workaround: assuming dest == operands[0]. TODO: avoid relying on it - src << " goto label_#{next_pos};\n" - end - when /\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/ - # For `opt_xxx`'s fallbacks. - if local_stack_p - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - end - src << " reg_cfp->pc = original_body_iseq + #{pos};\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_opt_insn);\n" - src << " goto cancel;\n" - when /\A(?<prefix>.+\b)INSN_LABEL\((?<name>[^)]+)\)(?<suffix>.+)\z/m - prefix, name, suffix = Regexp.last_match[:prefix], Regexp.last_match[:name], Regexp.last_match[:suffix] - src << "#{prefix}INSN_LABEL(#{name}_#{pos})#{suffix}" - else - if insn.handles_sp? - # If insn.handles_sp? is true, cfp->sp might be changed inside insns (like vm_caller_setup_arg_block) - # and thus we need to use cfp->sp, even when local_stack_p is TRUE. When insn.handles_sp? is true, - # cfp->sp should be available too because _mjit_compile_pc_and_sp.erb sets it. - src << to_cstr(line) - else - # If local_stack_p is TRUE and insn.handles_sp? is false, stack values are only available in local variables - # for stack. So we need to replace those macros if local_stack_p is TRUE here. - case line - when /\bGET_SP\(\)/ - # reg_cfp->sp - src << to_cstr(line.sub(/\bGET_SP\(\)/, local_stack_p ? '(stack + stack_size)' : 'GET_SP()')) - when /\bSTACK_ADDR_FROM_TOP\((?<num>[^)]+)\)/ - # #define STACK_ADDR_FROM_TOP(n) (GET_SP()-(n)) - num = Regexp.last_match[:num] - src << to_cstr(line.sub(/\bSTACK_ADDR_FROM_TOP\(([^)]+)\)/, local_stack_p ? "(stack + (stack_size - (#{num})))" : "STACK_ADDR_FROM_TOP(#{num})")) - when /\bTOPN\((?<num>[^)]+)\)/ - # #define TOPN(n) (*(GET_SP()-(n)-1)) - num = Regexp.last_match[:num] - src << to_cstr(line.sub(/\bTOPN\(([^)]+)\)/, local_stack_p ? "*(stack + (stack_size - (#{num}) - 1))" : "TOPN(#{num})")) - else - src << to_cstr(line) - end - end - end - end - return next_pos - end - - # _mjit_compile_pc_and_sp.erb - def compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos) - # JIT: When an insn is leaf, we don't need to Move pc for a catch table on catch_except_p, #caller_locations, - # and rb_profile_frames. For check_ints, we lazily move PC when we have interruptions. - pc_moved_p = false - unless insn.always_leaf? || insn.leaf_without_check_ints? - src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width)); - pc_moved_p = true - end - - # JIT: move sp to use or preserve stack variables - if local_stack_p - # sp motion is optimized away for `handles_sp? #=> false` case. - # Thus sp should be set properly before `goto cancel`. - if insn.handles_sp? - # JIT-only behavior (pushing JIT's local variables to VM's stack): - push_size = -sp_inc + insn.rets.size - insn.pops.size - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{push_size};\n" - push_size.times do |i| - src << " *(reg_cfp->sp + #{i - push_size}) = stack[#{stack_size - push_size + i}];\n" - end - end - else - if insn.handles_sp? - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size - insn.pops.size};\n" # POPN(INSN_ATTR(popn)); - else - src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n" - end - end - return pc_moved_p - end - - # Print the block to cancel inlined method call. It's supporting only `opt_send_without_block` for now. - def compile_inlined_cancel_handler(f, body, inline_context) - src = +"\ncancel:\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel);\n" - src << " rb_mjit_recompile_inlining(original_iseq);\n" - - # Swap pc/sp set on cancel with original pc/sp. - src << " const VALUE *current_pc = reg_cfp->pc;\n" - src << " VALUE *current_sp = reg_cfp->sp;\n" - src << " reg_cfp->pc = orig_pc;\n" - src << " reg_cfp->sp = orig_sp;\n\n" - - # Lazily push the current call frame. - src << " struct rb_calling_info calling;\n" - src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n" # assumes `opt_send_without_block` - src << " calling.argc = #{inline_context.orig_argc};\n" - src << " calling.recv = reg_cfp->self;\n" - src << " reg_cfp->self = orig_self;\n" - # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE - src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)#{inline_context.me}, 0, #{inline_context.param_size}, #{inline_context.local_size});\n\n" - - # Start usual cancel from here. - src << " reg_cfp = ec->cfp;\n" # work on the new frame - src << " reg_cfp->pc = current_pc;\n" - src << " reg_cfp->sp = current_sp;\n" - (0...body.stack_max).each do |i| # should be always `status->local_stack_p` - src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n" - end - # We're not just returning Qundef here so that caller's normal cancel handler can - # push back `stack` to `cfp->sp`. - src << " return vm_exec(ec, false);\n" - C.fprintf(f, src) - end - - # Print the block to cancel JIT execution. - def compile_cancel_handler(f, body, status) - if status.inlined_iseqs.nil? # the current ISeq is being inlined - compile_inlined_cancel_handler(f, body, status.inline_context) - return - end - - src = +"\nsend_cancel:\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_send_inline);\n" - src << " rb_mjit_recompile_send(original_iseq);\n" - src << " goto cancel;\n" - - src << "\nivar_cancel:\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_ivar_inline);\n" - src << " rb_mjit_recompile_ivar(original_iseq);\n" - src << " goto cancel;\n" - - src << "\nexivar_cancel:\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel_exivar_inline);\n" - src << " rb_mjit_recompile_exivar(original_iseq);\n" - src << " goto cancel;\n" - - src << "\nconst_cancel:\n" - src << " rb_mjit_recompile_const(original_iseq);\n" - src << " goto cancel;\n" - - src << "\ncancel:\n" - src << " RB_DEBUG_COUNTER_INC(mjit_cancel);\n" - if status.local_stack_p - (0...body.stack_max).each do |i| - src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n" - end - end - src << " return Qundef;\n" - C.fprintf(f, src) - end - - def precompile_inlinable_child_iseq(f, child_iseq, status, ci, cc, pos) - child_status = C.compile_status.new # not freed for now - child_status.compiled_iseq = status.compiled_iseq - child_status.compiled_id = status.compiled_id - init_compile_status(child_status, child_iseq.body, false) # not freed for now - child_status.inline_context.orig_argc = C.vm_ci_argc(ci) - child_status.inline_context.me = vm_cc_cme(cc).to_i - child_status.inline_context.param_size = child_iseq.body.param.size - child_status.inline_context.local_size = child_iseq.body.local_table_size - if child_iseq.body.ci_size > 0 && child_status.cc_entries_index == -1 - return false - end - init_ivar_compile_status(child_iseq.body, child_status) - - src = +"ALWAYS_INLINE(static VALUE _mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq));\n" - src << "static inline VALUE\n_mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq)\n{\n" - src << " const VALUE *orig_pc = reg_cfp->pc;\n" - src << " VALUE *orig_sp = reg_cfp->sp;\n" - C.fprintf(f, src) - - success = compile_body(f, child_iseq, child_status) - - C.fprintf(f, "\n} /* end of _mjit#{status.compiled_id}_inlined_#{pos} */\n\n") - - return success; - end - - def precompile_inlinable_iseqs(f, iseq, status) - body = iseq.body - pos = 0 - while pos < body.iseq_size - insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos])) - if insn.name == :opt_send_without_block || insn.name == :opt_size # `compile_inlined_cancel_handler` supports only `opt_send_without_block` - cd = C.CALL_DATA.new(body.iseq_encoded[pos + 1]) - ci = cd.ci - cc = captured_cc_entries(status)[call_data_index(cd, body)] # use copy to avoid race condition - - if (child_iseq = rb_mjit_inlinable_iseq(ci, cc)) != nil - status.inlined_iseqs[pos] = child_iseq.body - - if C.mjit_opts.verbose >= 1 # print beforehand because ISeq may be GCed during copy job. - child_location = child_iseq.body.location - $stderr.puts "JIT inline: #{child_location.label}@#{C.rb_iseq_path(child_iseq)}:#{C.rb_iseq_first_lineno(child_iseq)} " \ - "=> #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{C.rb_iseq_first_lineno(iseq)}" - end - if !precompile_inlinable_child_iseq(f, child_iseq, status, ci, cc, pos) - return false - end - end - end - pos += insn.len - end - return true - end - - def init_compile_status(status, body, compile_root_p) - status.stack_size_for_pos = Fiddle.malloc(Fiddle::SIZEOF_INT * body.iseq_size) - body.iseq_size.times do |i| - status.stack_size_for_pos[i] = C.NOT_COMPILED_STACK_SIZE - end - if compile_root_p - status.inlined_iseqs = Fiddle.malloc(Fiddle::SIZEOF_VOIDP * body.iseq_size) - body.iseq_size.times do |i| - status.inlined_iseqs[i] = nil - end - end - if ISEQ_IS_SIZE(body) > 0 - status.is_entries = Fiddle.malloc(C.iseq_inline_storage_entry.sizeof * ISEQ_IS_SIZE(body)) - end - if body.ci_size > 0 - status.cc_entries_index = C.mjit_capture_cc_entries(status.compiled_iseq, body) - else - status.cc_entries_index = -1 - end - if compile_root_p - status.compile_info = rb_mjit_iseq_compile_info(body) - else - status.compile_info = Fiddle.malloc(C.rb_mjit_compile_info.sizeof) - status.compile_info.disable_ivar_cache = false - status.compile_info.disable_exivar_cache = false - status.compile_info.disable_send_cache = false - status.compile_info.disable_inlining = false - status.compile_info.disable_const_cache = false - end - end - - def init_ivar_compile_status(body, status) - C.mjit_capture_is_entries(body, status.is_entries) - - num_ivars = 0 - pos = 0 - status.max_ivar_index = 0 - status.ivar_serial = 0 - - while pos < body.iseq_size - insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos])) - if insn.name == :getinstancevariable || insn.name == :setinstancevariable - ic = body.iseq_encoded[pos+2] - ic_copy = (status.is_entries + (C.iseq_inline_storage_entry.new(ic) - body.is_entries)).iv_cache - if ic_copy.entry # Only initialized (ic_serial > 0) IVCs are optimized - num_ivars += 1 - - if status.max_ivar_index < ic_copy.entry.index - status.max_ivar_index = ic_copy.entry.index - end - - if status.ivar_serial == 0 - status.ivar_serial = ic_copy.entry.class_serial - elsif status.ivar_serial != ic_copy.entry.class_serial - # Multiple classes have used this ISeq. Give up assuming one serial. - status.merge_ivar_guards_p = false - return - end - end - end - pos += insn.len - end - status.merge_ivar_guards_p = status.ivar_serial > 0 && num_ivars >= 2 - end - - # Expand simple macro that doesn't require dynamic C code. - def expand_simple_macros(arg_expr) - arg_expr.dup.tap do |expr| - # For `leave`. We can't proceed next ISeq in the same JIT function. - expr.gsub!(/^(?<indent>\s*)RESTORE_REGS\(\);\n/) do - indent = Regexp.last_match[:indent] - <<-end.gsub(/^ {12}/, '') - #if OPT_CALL_THREADED_CODE - #{indent}rb_ec_thread_ptr(ec)->retval = val; - #{indent}return 0; - #else - #{indent}return val; - #endif - end - end - expr.gsub!(/^(?<indent>\s*)NEXT_INSN\(\);\n/) do - indent = Regexp.last_match[:indent] - <<-end.gsub(/^ {12}/, '') - #{indent}UNREACHABLE_RETURN(Qundef); - end - end - end - end - - def to_cstr(expr) - expr.gsub(/^(?!#)/, ' ') # indent everything but preprocessor lines - end - - # 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 - end - offset - end - - def captured_cc_entries(status) - status.compiled_iseq.jit_unit.cc_entries + status.cc_entries_index - end - - def call_data_index(cd, body) - cd - body.call_data - end - - def vm_cc_cme(cc) - # TODO: add VM_ASSERT like actual vm_cc_cme - cc.cme_ - end - - def def_iseq_ptr(method_def) - C.rb_iseq_check(method_def.body.iseq.iseqptr) - end - - def rb_mjit_iseq_compile_info(body) - body.jit_unit.compile_info - end - - def ISEQ_IS_SIZE(body) - body.ic_size + body.ivc_size + body.ise_size + body.icvarc_size - end - - # Return true if an object of the class may be a special const (immediate). - # It's "maybe" because Integer and Float are not guaranteed to be an immediate. - # If this returns false, rb_class_of could be optimzied to RBASIC_CLASS. - def maybe_special_const?(klass) - [ - C.rb_cFalseClass, - C.rb_cNilClass, - C.rb_cTrueClass, - C.rb_cInteger, - C.rb_cSymbol, - C.rb_cFloat, - ].include?(klass) - end - - def has_valid_method_type?(cc) - vm_cc_cme(cc) != nil - end - - def already_compiled?(status, pos) - status.stack_size_for_pos[pos] != C.NOT_COMPILED_STACK_SIZE - end - - # Return an iseq pointer if cc has inlinable iseq. - def rb_mjit_inlinable_iseq(ci, cc) - if has_valid_method_type?(cc) && - C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL == 0 && # inlining only non-tailcall path - vm_cc_cme(cc).def.type == C.VM_METHOD_TYPE_ISEQ && - C.fastpath_applied_iseq_p(ci, cc, iseq = def_iseq_ptr(vm_cc_cme(cc).def)) && - inlinable_iseq_p(iseq.body) # CC_SET_FASTPATH in vm_callee_setup_arg - return iseq - end - return nil - end - - # Return true if the ISeq can be inlined without pushing a new control frame. - def inlinable_iseq_p(body) - # 1) If catch_except_p, caller frame should be preserved when callee catches an exception. - # Then we need to wrap `vm_exec()` but then we can't inline the call inside it. - # - # 2) If `body->catch_except_p` is false and `handles_sp?` of an insn is false, - # sp is not moved as we assume `status->local_stack_p = !body->catch_except_p`. - # - # 3) If `body->catch_except_p` is false and `always_leaf?` of an insn is true, - # pc is not moved. - if body.catch_except_p - return false - end - - pos = 0 - while pos < body.iseq_size - insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos])) - # All insns in the ISeq except `leave` (to be overridden in the inlined code) - # should meet following strong assumptions: - # * Do not require `cfp->sp` motion - # * Do not move `cfp->pc` - # * Do not read any `cfp->pc` - if insn.name == :invokebuiltin || insn.name == :opt_invokebuiltin_delegate || insn.name == :opt_invokebuiltin_delegate_leave - # builtin insn's inlinability is handled by `Primitive.attr! 'inline'` per iseq - if !body.builtin_inline_p - return false; - end - elsif insn.name != :leave && C.insn_may_depend_on_sp_or_pc(insn.bin, body.iseq_encoded + (pos + 1)) - return false - end - # At this moment, `cfp->ep` in an inlined method is not working. - case insn.name - when :getlocal, - :getlocal_WC_0, - :getlocal_WC_1, - :setlocal, - :setlocal_WC_0, - :setlocal_WC_1, - :getblockparam, - :getblockparamproxy, - :setblockparam - return false - end - pos += insn.len - end - return true - end - - # CPointer::Struct could be nil on field reference, and this is a helper to - # handle that case while using CPointer::Struct#to_s in most cases. - # @param struct [RubyVM::MJIT::CPointer::Struct] - def to_addr(struct) - struct&.to_s || 'NULL' - end - end - - private_constant(*constants) -end diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 86ba70ed19..0fbc1cc2e5 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -1762,7 +1762,7 @@ SRC hdr << "#endif\n" hdr = hdr.join("") log_src(hdr, "#{header} is") - unless (IO.read(header) == hdr rescue false) + unless (File.read(header) == hdr rescue false) File.open(header, "wb") do |hfile| hfile.write(hdr) end @@ -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 @@ -2378,11 +2378,19 @@ TIMESTAMP_DIR = #{$extout && $extmk ? '$(extout)/.timestamp' : '.'} install_dirs.each {|d| conf << ("%-14s= %s\n" % d) if /^[[:upper:]]/ =~ d[0]} sodir = $extout ? '$(TARGET_SO_DIR)' : '$(RUBYARCHDIR)' n = '$(TARGET_SO_DIR)$(TARGET)' + cleanobjs = ["$(OBJS)"] + if $extmk + %w[bc i s].each {|ex| cleanobjs << "$(OBJS:.#{$OBJEXT}=.#{ex})"} + end + if target + config_string('cleanobjs') {|t| cleanobjs << t.gsub(/\$\*/, "$(TARGET)#{deffile ? '-$(arch)': ''}")} + end conf << "\ TARGET_SO_DIR =#{$extout ? " $(RUBYARCHDIR)/" : ''} TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) CLEANLIBS = #{'$(TARGET_SO) ' if target}#{config_string('cleanlibs') {|t| t.gsub(/\$\*/) {n}}} -CLEANOBJS = *.#{$OBJEXT} #{config_string('cleanobjs') {|t| t.gsub(/\$\*/, "$(TARGET)#{deffile ? '-$(arch)': ''}")} if target} *.bak +CLEANOBJS = #{cleanobjs.join(' ')} *.bak +TARGET_SO_DIR_TIMESTAMP = #{timestamp_file(sodir, target_prefix)} " #" conf = yield(conf) if block_given? @@ -2416,7 +2424,7 @@ static: #{$extmk && !$static ? "all" : "$(STATIC_LIB)#{$extout ? " install-rb" : if target f = "$(DLLIB)" dest = "$(TARGET_SO)" - stamp = timestamp_file(dir, target_prefix) + stamp = '$(TARGET_SO_DIR_TIMESTAMP)' if $extout mfile.puts dest mfile.print "clean-so::\n" @@ -2485,7 +2493,9 @@ static: #{$extmk && !$static ? "all" : "$(STATIC_LIB)#{$extout ? " install-rb" : end end end - dirs.unshift(sodir) if target and !dirs.include?(sodir) + if target and !dirs.include?(sodir) + mfile.print "$(TARGET_SO_DIR_TIMESTAMP):\n\t$(Q) $(MAKEDIRS) $(@D) #{sodir}\n\t$(Q) $(TOUCH) $@\n" + end dirs.each do |d| t = timestamp_file(d, target_prefix) mfile.print "#{t}:\n\t$(Q) $(MAKEDIRS) $(@D) #{d}\n\t$(Q) $(TOUCH) $@\n" @@ -2529,7 +2539,7 @@ site-install-rb: install-rb mfile.print "$(TARGET_SO): " mfile.print "$(DEFFILE) " if makedef mfile.print "$(OBJS) Makefile" - mfile.print " #{timestamp_file(sodir, target_prefix)}" if $extout + mfile.print " $(TARGET_SO_DIR_TIMESTAMP)" if $extout mfile.print "\n" mfile.print "\t$(ECHO) linking shared-object #{target_prefix.sub(/\A\/(.*)/, '\1/')}$(DLLIB)\n" mfile.print "\t-$(Q)$(RM) $(@#{sep})\n" diff --git a/lib/mutex_m.rb b/lib/mutex_m.rb index abd0fc6add..17ec9924e4 100644 --- a/lib/mutex_m.rb +++ b/lib/mutex_m.rb @@ -40,7 +40,7 @@ # module Mutex_m - VERSION = "0.1.1" + VERSION = "0.1.2" Ractor.make_shareable(VERSION) if defined?(Ractor) def Mutex_m.define_aliases(cl) # :nodoc: diff --git a/lib/net/http.rb b/lib/net/http.rb index 7e89409c1b..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 # @@ -32,373 +32,697 @@ module Net #:nodoc: class HTTPHeaderSyntaxError < StandardError; end # :startdoc: - # == An HTTP client API for Ruby. + # \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: + # + # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol]. + # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview]. + # + # == 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 # - # Net::HTTP provides a rich library which can be used to build HTTP - # user-agents. For more details about HTTP see - # [RFC2616](http://www.ietf.org/rfc/rfc2616.txt). + # 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]. # - # Net::HTTP is designed to work closely with URI. URI::HTTP#host, - # URI::HTTP#port and URI::HTTP#request_uri are designed to work with - # Net::HTTP. + # == URIs # - # If you are only performing a few GET requests you should try OpenURI. + # On the internet, a URI + # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) + # is a string that identifies a particular resource. + # It consists of some or all of: scheme, hostname, path, query, and fragment; + # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # - # == Simple Examples + # A Ruby {URI::Generic}[rdoc-ref:URI::Generic] object + # represents an internet URI. + # It provides, among others, methods + # +scheme+, +hostname+, +path+, +query+, and +fragment+. # - # All examples assume you have loaded Net::HTTP with: + # === Schemes # - # require 'net/http' + # An internet \URI has + # a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes]. # - # This will also require 'uri' so you don't need to require it separately. + # The two schemes supported in \Net::HTTP are <tt>'https'</tt> and <tt>'http'</tt>: # - # The Net::HTTP methods in the following section do not persist - # connections. They are not recommended if you are performing many HTTP - # requests. + # uri.scheme # => "https" + # URI('http://example.com').scheme # => "http" # - # === GET + # === Hostnames # - # Net::HTTP.get('example.com', '/index.html') # => String + # A hostname identifies a server (host) to which requests may be sent: # - # === GET by URI + # hostname = uri.hostname # => "jsonplaceholder.typicode.com" + # Net::HTTP.start(hostname) do |http| + # # Some HTTP stuff. + # end # - # uri = URI('http://example.com/index.html?count=10') - # Net::HTTP.get(uri) # => String + # === Paths # - # === GET with Dynamic Parameters + # A host-specific path identifies a resource on the host: # - # uri = URI('http://example.com/index.html') - # params = { :limit => 10, :page => 3 } - # uri.query = URI.encode_www_form(params) + # _uri = uri.dup + # _uri.path = '/todos/1' + # hostname = _uri.hostname + # path = _uri.path + # Net::HTTP.get(hostname, path) # - # res = Net::HTTP.get_response(uri) - # puts res.body if res.is_a?(Net::HTTPSuccess) + # === Queries # - # === POST + # A host-specific query adds name/value pairs to the URI: # - # uri = URI('http://www.example.com/search.cgi') - # res = Net::HTTP.post_form(uri, 'q' => 'ruby', 'max' => '50') - # puts res.body + # _uri = uri.dup + # params = {userId: 1, completed: false} + # _uri.query = URI.encode_www_form(params) + # _uri # => #<URI::HTTPS https://jsonplaceholder.typicode.com?userId=1&completed=false> + # Net::HTTP.get(_uri) # - # === POST with Multiple Values + # === Fragments # - # uri = URI('http://www.example.com/search.cgi') - # res = Net::HTTP.post_form(uri, 'q' => ['ruby', 'perl'], 'max' => '50') - # puts res.body + # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect + # in \Net::HTTP; + # the same data is returned, regardless of whether a fragment is included. # - # == How to use Net::HTTP + # == Request Headers # - # The following example code can be used as the basis of an HTTP user-agent - # which can perform a variety of request types using persistent - # connections. + # Request headers may be used to pass additional information to the host, + # similar to arguments passed in a method call; + # each header is a name/value pair. # - # uri = URI('http://example.com/some_path?query=string') + # Each of the \Net::HTTP methods that sends a request to the host + # has optional argument +headers+, + # where the headers are expressed as a hash of field-name/value pairs: # - # Net::HTTP.start(uri.host, uri.port) do |http| - # request = Net::HTTP::Get.new uri + # headers = {Accept: 'application/json', Connection: 'Keep-Alive'} + # Net::HTTP.get(uri, headers) # - # response = http.request request # Net::HTTPResponse object - # end + # See lists of both standard request fields and common request fields at + # {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. + # A host may also accept other custom fields. # - # Net::HTTP::start immediately creates a connection to an HTTP server which - # is kept open for the duration of the block. The connection will remain - # open for multiple requests in the block if the server indicates it - # supports persistent connections. + # == \HTTP Sessions # - # If you wish to re-use a connection across multiple HTTP requests without - # automatically closing it you can use ::new and then call #start and - # #finish manually. + # A _session_ is a connection between a server (host) and a client that: # - # The request types Net::HTTP supports are listed below in the section "HTTP - # Request Classes". + # - Is begun by instance method Net::HTTP#start. + # - May contain any number of requests. + # - Is ended by instance method Net::HTTP#finish. # - # For all the Net::HTTP request objects and shortcut request methods you may - # supply either a String for the request path or a URI from which Net::HTTP - # will extract the request path. + # See example sessions at {Strategies}[rdoc-ref:Net::HTTP@Strategies]. # - # === Response Data + # === Session Using \Net::HTTP.start # - # uri = URI('http://example.com/index.html') - # res = Net::HTTP.get_response(uri) + # If you have many requests to make to a single host (and port), + # consider using singleton method Net::HTTP.start with a block; + # the method handles the session automatically by: # - # # Headers - # res['Set-Cookie'] # => String - # res.get_fields('set-cookie') # => Array - # res.to_hash['set-cookie'] # => Array - # puts "Headers: #{res.to_hash.inspect}" + # - Calling #start before block execution. + # - Executing the block. + # - Calling #finish after block execution. # - # # Status - # puts res.code # => '200' - # puts res.message # => 'OK' - # puts res.class.name # => 'HTTPOK' + # In the block, you can use these instance methods, + # each of which that sends a single request: # - # # Body - # puts res.body + # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]: # - # === Following Redirection + # - #get, #request_get: GET. + # - #head, #request_head: HEAD. + # - #post, #request_post: POST. + # - #delete: DELETE. + # - #options: OPTIONS. + # - #trace: TRACE. + # - #patch: PATCH. # - # Each Net::HTTPResponse object belongs to a class for its response code. + # - {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # - # 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. + # - #copy: COPY. + # - #lock: LOCK. + # - #mkcol: MKCOL. + # - #move: MOVE. + # - #propfind: PROPFIND. + # - #proppatch: PROPPATCH. + # - #unlock: UNLOCK. # - # Using a case statement you can handle various types of responses properly: + # === Session Using \Net::HTTP.start and \Net::HTTP.finish # - # def fetch(uri_str, limit = 10) - # # You should choose a better exception. - # raise ArgumentError, 'too many HTTP redirects' if limit == 0 + # You can manage a session manually using methods #start and #finish: # - # response = Net::HTTP.get_response(URI(uri_str)) + # http = Net::HTTP.new(hostname) + # http.start + # http.get('/todos/1') + # http.get('/todos/2') + # http.delete('/posts/1') + # http.finish # Needed to free resources. # - # case response - # when Net::HTTPSuccess then - # response - # when Net::HTTPRedirection then - # location = response['location'] - # warn "redirected to #{location}" - # fetch(location, limit - 1) - # else - # response.value - # end - # end + # === Single-Request Session # - # print fetch('http://www.ruby-lang.org') + # Certain convenience methods automatically handle a session by: # - # === POST + # - Creating an \HTTP object + # - Starting a session. + # - Sending a single request. + # - Finishing the session. + # - Destroying the object. # - # A POST can be made using the Net::HTTP::Post request class. This example - # creates a URL encoded POST body: + # Such methods that send GET requests: # - # uri = URI('http://www.example.com/todo.cgi') - # req = Net::HTTP::Post.new(uri) - # req.set_form_data('from' => '2005-01-01', 'to' => '2005-03-31') + # - ::get: Returns the string response body. + # - ::get_print: Writes the string response body to $stdout. + # - ::get_response: Returns a Net::HTTPResponse object. # - # res = Net::HTTP.start(uri.hostname, uri.port) do |http| - # http.request(req) - # end + # Such methods that send POST requests: # - # case res - # when Net::HTTPSuccess, Net::HTTPRedirection - # # OK - # else - # res.value - # end + # - ::post: Posts data to the host. + # - ::post_form: Posts form data to the host. # - # To send multipart/form-data use Net::HTTPHeader#set_form: + # == \HTTP Requests and Responses # - # req = Net::HTTP::Post.new(uri) - # req.set_form([['upload', File.open('foo.bar')]], 'multipart/form-data') + # Many of the methods above are convenience methods, + # each of which sends a request and returns a string + # without directly using \Net::HTTPRequest and \Net::HTTPResponse objects. # - # Other requests that can contain a body such as PUT can be created in the - # same way using the corresponding request class (Net::HTTP::Put). + # You can, however, directly create a request object, send the request, + # and retrieve the response object; see: # - # === Setting Headers + # - Net::HTTPRequest. + # - Net::HTTPResponse. # - # The following example performs a conditional GET using the - # If-Modified-Since header. If the files has not been modified since the - # time in the header a Not Modified response will be returned. See RFC 2616 - # section 9.3 for further details. + # == Following Redirection # - # uri = URI('http://example.com/cached_response') - # file = File.stat 'cached_response' + # Each returned response is an instance of a subclass of Net::HTTPResponse. + # See the {response class hierarchy}[rdoc-ref:Net::HTTPResponse@Response+Subclasses]. # - # req = Net::HTTP::Get.new(uri) - # req['If-Modified-Since'] = file.mtime.rfc2822 + # In particular, class Net::HTTPRedirection is the parent + # of all redirection classes. + # This allows you to craft a case statement to handle redirections properly: # - # res = Net::HTTP.start(uri.hostname, uri.port) {|http| - # http.request(req) - # } + # def fetch(uri, limit = 10) + # # You should choose a better exception. + # 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 # Any other class. + # res.value + # end + # end # - # open 'cached_response', 'w' do |io| - # io.write res.body - # end if res.is_a?(Net::HTTPSuccess) + # fetch(uri) # - # === Basic Authentication + # == 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 + # == 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 # end # - # === HTTPS - # - # HTTPS is enabled for an HTTP connection by Net::HTTP#use_ssl=. + # == HTTPS # - # uri = URI('https://secure.example.com/some_path?query=string') + # HTTPS is enabled for an \HTTP connection by Net::HTTP#use_ssl=: # - # 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 + # + # When argument +p_addr+ is a string hostname, + # the returned +http+ has the given host as its proxy: # - # uri = URI('https://example.com/') - # Net::HTTP.get(uri) # => String + # 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 # - # In previous versions of Ruby you would need to require 'net/https' to use - # HTTPS. This is no longer true. + # The port, username, and password for the proxy may also be given: + # + # 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" + # + # === Proxy Using '<tt>ENV['http_proxy']</tt>' + # + # 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>: + # + # 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" + # + # === Filtering Proxies + # + # With method Net::HTTP.new (but not Net::HTTP.start), + # you can use argument +p_no_proxy+ to filter proxies: + # + # - Reject a certain address: + # + # 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 # - # 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. - # - # You may also create a custom proxy: - # - # proxy_addr = 'your.proxy.host' - # proxy_port = 8080 - # - # Net::HTTP.new('example.com', nil, proxy_addr, proxy_port).start { |http| - # # always proxy via your.proxy.addr:8080 - # } - # - # See Net::HTTP.new for further details and examples such as proxies that - # require a username and password. - # - # === Compression - # - # Net::HTTP automatically adds Accept-Encoding for compression of response - # bodies and automatically decompresses gzip and deflate responses unless a - # Range header was sent. - # - # Compression can be disabled through the Accept-Encoding: identity header. - # - # == HTTP Request Classes - # - # Here is the HTTP request class hierarchy. - # - # * Net::HTTPRequest - # * Net::HTTP::Get - # * Net::HTTP::Head - # * Net::HTTP::Post - # * Net::HTTP::Patch - # * Net::HTTP::Put - # * Net::HTTP::Proppatch - # * Net::HTTP::Lock - # * Net::HTTP::Unlock - # * Net::HTTP::Options - # * Net::HTTP::Propfind - # * Net::HTTP::Delete - # * Net::HTTP::Move - # * Net::HTTP::Copy - # * Net::HTTP::Mkcol - # * Net::HTTP::Trace - # - # == HTTP Response Classes - # - # Here is HTTP response class hierarchy. All classes are defined in Net - # module and are subclasses of Net::HTTPResponse. - # - # HTTPUnknownResponse:: For unhandled HTTP extensions - # HTTPInformation:: 1xx - # HTTPContinue:: 100 - # HTTPSwitchProtocol:: 101 - # HTTPProcessing:: 102 - # HTTPEarlyHints:: 103 - # HTTPSuccess:: 2xx - # HTTPOK:: 200 - # HTTPCreated:: 201 - # HTTPAccepted:: 202 - # HTTPNonAuthoritativeInformation:: 203 - # HTTPNoContent:: 204 - # HTTPResetContent:: 205 - # HTTPPartialContent:: 206 - # HTTPMultiStatus:: 207 - # HTTPAlreadyReported:: 208 - # HTTPIMUsed:: 226 - # HTTPRedirection:: 3xx - # HTTPMultipleChoices:: 300 - # HTTPMovedPermanently:: 301 - # HTTPFound:: 302 - # HTTPSeeOther:: 303 - # HTTPNotModified:: 304 - # HTTPUseProxy:: 305 - # HTTPTemporaryRedirect:: 307 - # HTTPPermanentRedirect:: 308 - # HTTPClientError:: 4xx - # HTTPBadRequest:: 400 - # HTTPUnauthorized:: 401 - # HTTPPaymentRequired:: 402 - # HTTPForbidden:: 403 - # HTTPNotFound:: 404 - # HTTPMethodNotAllowed:: 405 - # HTTPNotAcceptable:: 406 - # HTTPProxyAuthenticationRequired:: 407 - # HTTPRequestTimeOut:: 408 - # HTTPConflict:: 409 - # HTTPGone:: 410 - # HTTPLengthRequired:: 411 - # HTTPPreconditionFailed:: 412 - # HTTPRequestEntityTooLarge:: 413 - # HTTPRequestURITooLong:: 414 - # HTTPUnsupportedMediaType:: 415 - # HTTPRequestedRangeNotSatisfiable:: 416 - # HTTPExpectationFailed:: 417 - # HTTPMisdirectedRequest:: 421 - # HTTPUnprocessableEntity:: 422 - # HTTPLocked:: 423 - # HTTPFailedDependency:: 424 - # HTTPUpgradeRequired:: 426 - # HTTPPreconditionRequired:: 428 - # HTTPTooManyRequests:: 429 - # HTTPRequestHeaderFieldsTooLarge:: 431 - # HTTPUnavailableForLegalReasons:: 451 - # HTTPServerError:: 5xx - # HTTPInternalServerError:: 500 - # HTTPNotImplemented:: 501 - # HTTPBadGateway:: 502 - # HTTPServiceUnavailable:: 503 - # HTTPGatewayTimeOut:: 504 - # HTTPVersionNotSupported:: 505 - # HTTPVariantAlsoNegotiates:: 506 - # HTTPInsufficientStorage:: 507 - # HTTPLoopDetected:: 508 - # HTTPNotExtended:: 510 - # HTTPNetworkAuthenticationRequired:: 511 - # - # There is also the Net::HTTPBadResponse exception which is raised when - # there is a protocol error. + # - {: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.2.2" - Revision = %q$Revision$.split[1] + VERSION = "0.4.1" HTTPVersion = '1.1' begin require 'zlib' @@ -408,18 +732,17 @@ module Net #:nodoc: end # :startdoc: - # Turns on net/http 1.2 (Ruby 1.8) features. - # Defaults to ON in Ruby 1.8 or later. + # Returns +true+; retained for compatibility. def HTTP.version_1_2 true end - # Returns true if net/http is in version 1.2 mode. - # Defaults to true. + # Returns +true+; retained for compatibility. def HTTP.version_1_2? true end + # Returns +false+; retained for compatibility. def HTTP.version_1_1? #:nodoc: false end @@ -429,25 +752,12 @@ module Net #:nodoc: alias is_version_1_2? version_1_2? #:nodoc: end + # :call-seq: + # Net::HTTP.get_print(hostname, path, port = 80) -> nil + # Net::HTTP:get_print(uri, headers = {}, port = uri.port) -> nil # - # short cut methods - # - - # - # Gets the body text from the target and outputs it to $stdout. The - # target can either be specified as - # (+uri+, +headers+), or as (+host+, +path+, +port+ = 80); so: - # - # Net::HTTP.get_print URI('http://www.example.com/index.html') - # - # or: - # - # Net::HTTP.get_print 'www.example.com', '/index.html' - # - # you can also specify request headers: - # - # Net::HTTP.get_print URI('http://www.example.com/index.html'), { 'Accept' => 'text/html' } - # + # Like Net::HTTP.get, but writes the returned body to $stdout; + # returns +nil+. def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port) {|res| res.read_body do |chunk| @@ -457,40 +767,48 @@ module Net #:nodoc: nil end - # Sends a GET request to the target and returns the HTTP response - # as a string. The target can either be specified as - # (+uri+, +headers+), or as (+host+, +path+, +port+ = 80); so: - # - # print Net::HTTP.get(URI('http://www.example.com/index.html')) + # :call-seq: + # Net::HTTP.get(hostname, path, port = 80) -> body + # Net::HTTP:get(uri, headers = {}, port = uri.port) -> body # - # or: + # Sends a GET request and returns the \HTTP response body as a string. # - # print Net::HTTP.get('www.example.com', '/index.html') + # With string arguments +hostname+ and +path+: # - # you can also specify request headers: + # hostname = 'jsonplaceholder.typicode.com' + # path = '/todos/1' + # puts Net::HTTP.get(hostname, path) # - # Net::HTTP.get(URI('http://www.example.com/index.html'), { 'Accept' => 'text/html' }) + # Output: # - def HTTP.get(uri_or_host, path_or_headers = nil, port = nil) - get_response(uri_or_host, path_or_headers, port).body - end - - # Sends a GET request to the target and returns the HTTP response - # as a Net::HTTPResponse object. The target can either be specified as - # (+uri+, +headers+), or as (+host+, +path+, +port+ = 80); so: + # { + # "userId": 1, + # "id": 1, + # "title": "delectus aut autem", + # "completed": false + # } # - # res = Net::HTTP.get_response(URI('http://www.example.com/index.html')) - # print res.body + # With URI object +uri+ and optional hash argument +headers+: # - # or: + # uri = URI('https://jsonplaceholder.typicode.com/todos/1') + # headers = {'Content-type' => 'application/json; charset=UTF-8'} + # Net::HTTP.get(uri, headers) # - # res = Net::HTTP.get_response('www.example.com', '/index.html') - # print res.body + # Related: # - # you can also specify request headers: + # - Net::HTTP::Get: request class for \HTTP method +GET+. + # - Net::HTTP#get: convenience method for \HTTP method +GET+. # - # Net::HTTP.get_response(URI('http://www.example.com/index.html'), { 'Accept' => 'text/html' }) + def HTTP.get(uri_or_host, path_or_headers = nil, port = nil) + get_response(uri_or_host, path_or_headers, port).body + end + + # :call-seq: + # 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 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) host = uri_or_host @@ -508,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: # - # require 'net/http' - # require 'uri' + # { + # "title": "foo", + # "body": "bar", + # "userId": 1, + # "id": 101 + # } # - # Net::HTTP.post URI('http://www.example.com/api/search'), - # { "q" => "ruby", "max" => "50" }.to_json, - # "Content-Type" => "application/json" + # Related: + # + # - 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, @@ -526,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. - # - # Example: + # _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 # - # 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) @@ -554,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 @@ -577,35 +922,91 @@ module Net #:nodoc: end # :call-seq: - # HTTP.start(address, port, p_addr, p_port, p_user, p_pass, &block) - # HTTP.start(address, port=nil, p_addr=:ENV, p_port=nil, p_user=nil, p_pass=nil, opt, &block) - # - # Creates a new Net::HTTP object, then additionally opens the TCP - # connection and HTTP session. - # - # Arguments are the following: - # _address_ :: hostname or IP address of the server - # _port_ :: port of the server - # _p_addr_ :: address of proxy - # _p_port_ :: port of proxy - # _p_user_ :: user of proxy - # _p_pass_ :: pass of proxy - # _opt_ :: optional hash - # - # _opt_ sets following values by its accessor. - # The keys are ipaddr, ca_file, ca_path, cert, cert_store, ciphers, keep_alive_timeout, - # close_on_empty_response, key, open_timeout, read_timeout, write_timeout, ssl_timeout, - # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode. - # If you set :use_ssl as true, you can use https and default value of - # verify_mode is set as OpenSSL::SSL::VERIFY_PEER. - # - # If the optional block is given, the newly - # created Net::HTTP object is passed to it and closed when the - # block finishes. In this case, the return value of this method - # is the return value of the block. If no block is given, the - # return value of this method is the newly created Net::HTTP object - # itself, and the caller is responsible for closing it upon completion - # using the finish() method. + # 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, +http+, via \Net::HTTP.new: + # + # - 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: + # + # - 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: + # + # hostname = 'jsonplaceholder.typicode.com' + # Net::HTTP.start(hostname) do |http| + # puts http.get('/todos/1').body + # puts http.get('/todos/2').body + # end + # + # Output: + # + # { + # "userId": 1, + # "id": 1, + # "title": "delectus aut autem", + # "completed": false + # } + # { + # "userId": 1, + # "id": 2, + # "title": "quis ut nam facilis et officia qui", + # "completed": false + # } + # + # 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. + # + # The keys may include: + # + # - #ca_file + # - #ca_path + # - #cert + # - #cert_store + # - #ciphers + # - #close_on_empty_response + # - +ipaddr+ (calls #ipaddr=) + # - #keep_alive_timeout + # - #key + # - #open_timeout + # - #read_timeout + # - #ssl_timeout + # - #ssl_version + # - +use_ssl+ (calls #use_ssl=) + # - #verify_callback + # - #verify_depth + # - #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 @@ -632,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). # - # 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 only string argument +address+ given + # (and <tt>ENV['http_proxy']</tt> undefined or +nil+), + # the returned +http+: # - # 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. + # - Has the given address. + # - Has the default port number, Net::HTTP.default_port (80). + # - Has no proxy. # - # 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. + # 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 + # + # With integer argument +port+ also given, + # the returned +http+ has the given port: + # + # http = Net::HTTP.new(hostname, 8000) + # # => #<Net::HTTP jsonplaceholder.typicode.com:8000 open=false> + # http.port # => 8000 + # + # 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 @@ -664,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 @@ -677,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 @@ -717,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 @@ -724,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 @@ -809,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 @@ -886,7 +1456,7 @@ module Net #:nodoc: :@verify_depth, :@verify_mode, :@verify_hostname, - ] + ] # :nodoc: SSL_ATTRIBUTES = [ :ca_file, :ca_path, @@ -903,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 @@ -968,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 @@ -1013,13 +1598,14 @@ module Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - begin - s = Socket.tcp conn_addr, conn_port, @local_host, @local_port, connect_timeout: @open_timeout - rescue => e - e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) #for compatibility with previous versions - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" - end + s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { + begin + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + rescue => e + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end + } s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1028,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" @@ -1110,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 @@ -1138,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) { @@ -1165,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 @@ -1198,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 @@ -1212,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 @@ -1221,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 @@ -1231,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 @@ -1279,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| ... } # - # +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. + # Sends a GET request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # This method returns a Net::HTTPResponse object. + # The request is based on the Net::HTTP::Get object + # created from string +path+ and initial headers hash +initheader+. # - # 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. + # With a block given, calls the block with the response body: # - # +dest+ argument is obsolete. - # It still works but you must not use it. + # 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}" + # + # 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 @@ -1325,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}" + # + # With no block given, simply returns the response object: # - # response = http.post('/cgi-bin/search.rb', 'query=foo') + # http.post('/todos', data) # => #<Net::HTTPCreated 201 Created readbody=true> # - # # using block - # File.open('result.txt', 'w') {|f| - # http.post('/cgi-bin/search.rb', 'query=foo') do |str| - # f.write str - # end - # } + # Related: # - # 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. + # - 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. # - # 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. + # The request is based on the Net::HTTP::Get object + # created from string +path+ and initial headers hash +initheader+. # - # Returns the response. + # With no block given, returns the response object: + # + # http = Net::HTTP.new(hostname) + # http.request_get('/todos') # => #<Net::HTTPOK 200 OK readbody=true> # - # This method never raises Net::* exceptions. + # With a block given, calls the block with the response object + # and returns the response object: # - # response = http.request_get('/index.html') - # # The entity body is already read in this case. - # p response['content-type'] - # puts response.body + # http.request_get('/todos') do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> # - # # Using a block - # http.request_get('/index.html') {|response| - # p response['content-type'] - # response.read_body do |str| # read body now - # print str - # end - # } + # Output: + # + # #<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. - # - # Returns the response. + # Sends a HEAD request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # This method never raises Net::* exceptions. + # The request is based on the Net::HTTP::Head object + # created from string +path+ and initial headers hash +initheader+. # - # 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 @@ -1526,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' @@ -1543,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. + # + # 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. + # + # With no block given, returns the response object: + # + # 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> # - # 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. + # With a block given, calls the block with the response and returns the response: # - # Returns an HTTPResponse object. + # req = Net::HTTP::Get.new('/todos/1') + # http.request(req) do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> # - # 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. + # 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 313de6ac92..44e329a0c8 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -1,24 +1,29 @@ -# 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 if URI === uri_or_path then raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path - raise ArgumentError, "no host component for URI" unless uri_or_path.hostname + hostname = uri_or_path.hostname + 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 @@ -52,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 @@ -75,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 @@ -97,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 @@ -135,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 @@ -239,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 = @@ -324,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 b0ec4b0625..6660c8075a 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -1,16 +1,188 @@ -# frozen_string_literal: false -# The HTTPHeader module defines methods for reading and writing -# HTTP headers. +# frozen_string_literal: true # -# It is used as a mixin by other classes, to provide hash-like -# access to HTTP header values. Unlike raw hash access, HTTPHeader -# provides access via case-insensitive keys. It also provides -# methods for accessing commonly-used HTTP header values in more -# convenient formats. +# The \HTTPHeader module provides access to \HTTP headers. +# +# The module is included in: +# +# - Net::HTTPGenericRequest (and therefore Net::HTTPRequest). +# - Net::HTTPResponse. +# +# The headers are a hash-like collection of key/value pairs called _fields_. +# +# == Request and Response Fields +# +# Headers may be included in: +# +# - A Net::HTTPRequest object: +# the object's headers will be sent with the request. +# Any fields may be defined in the request; +# see {Setters}[rdoc-ref:Net::HTTPHeader@Setters]. +# - A Net::HTTPResponse object: +# the objects headers are usually those returned from the host. +# Fields may be retrieved from the object; +# see {Getters}[rdoc-ref:Net::HTTPHeader@Getters] +# and {Iterators}[rdoc-ref:Net::HTTPHeader@Iterators]. +# +# Exactly which fields should be sent or expected depends on the host; +# see: +# +# - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. +# - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields]. +# +# == About the Examples +# +# :include: doc/net-http/examples.rdoc +# +# == Fields +# +# A header field is a key/value pair. +# +# === Field Keys +# +# A field key may be: +# +# - A string: Key <tt>'Accept'</tt> is treated as if it were +# <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>. +# - A symbol: Key <tt>:Accept</tt> is treated as if it were +# <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>. +# +# Examples: +# +# req = Net::HTTP::Get.new(uri) +# req[:accept] # => "*/*" +# req['Accept'] # => "*/*" +# req['ACCEPT'] # => "*/*" +# +# req['accept'] = 'text/html' +# req[:accept] = 'text/html' +# req['ACCEPT'] = 'text/html' +# +# === Field Values +# +# A field value may be returned as an array of strings or as a string: +# +# - These methods return field values as arrays: +# +# - #get_fields: Returns the array value for the given key, +# or +nil+ if it does not exist. +# - #to_hash: Returns a hash of all header fields: +# each key is a field name; its value is the array value for the field. +# +# - These methods return field values as string; +# the string value for a field is equivalent to +# <tt>self[key.downcase.to_s].join(', '))</tt>: +# +# - #[]: Returns the string value for the given key, +# or +nil+ if it does not exist. +# - #fetch: Like #[], but accepts a default value +# to be returned if the key does not exist. +# +# The field value may be set: +# +# - #[]=: Sets the value for the given key; +# the given value may be a string, a symbol, an array, or a hash. +# - #add_field: Adds a given value to a value for the given key +# (not overwriting the existing value). +# - #delete: Deletes the field for the given key. +# +# Example field values: +# +# - \String: +# +# req['Accept'] = 'text/html' # => "text/html" +# req['Accept'] # => "text/html" +# req.get_fields('Accept') # => ["text/html"] +# +# - \Symbol: +# +# req['Accept'] = :text # => :text +# req['Accept'] # => "text" +# req.get_fields('Accept') # => ["text"] +# +# - Simple array: +# +# req[:foo] = %w[bar baz bat] +# req[:foo] # => "bar, baz, bat" +# req.get_fields(:foo) # => ["bar", "baz", "bat"] +# +# - Simple hash: +# +# req[:foo] = {bar: 0, baz: 1, bat: 2} +# req[:foo] # => "bar, 0, baz, 1, bat, 2" +# req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"] +# +# - Nested: +# +# req[:foo] = [%w[bar baz], {bat: 0, bam: 1}] +# req[:foo] # => "bar, baz, bat, 0, bam, 1" +# req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"] +# +# req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}} +# req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1" +# req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"] +# +# == Convenience Methods +# +# Various convenience methods retrieve values, set values, query values, +# set form values, or iterate over fields. +# +# === Setters +# +# \Method #[]= can set any field, but does little to validate the new value; +# some of the other setter methods provide some validation: +# +# - #[]=: Sets the string or array value for the given key. +# - #add_field: Creates or adds to the array value for the given key. +# - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>. +# - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>. +# - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>. +# - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>. +# - #set_range: Sets the value for field <tt>'Range'</tt>. +# +# === Form Setters +# +# - #set_form: Sets an HTML form data set. +# - #set_form_data: Sets header fields and a body from HTML form data. +# +# === Getters +# +# \Method #[] can retrieve the value of any field that exists, +# but always as a string; +# some of the other getter methods return something different +# from the simple string value: +# +# - #[]: Returns the string field value for the given key. +# - #content_length: Returns the integer value of field <tt>'Content-Length'</tt>. +# - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>. +# - #content_type: Returns the string value of field <tt>'Content-Type'</tt>. +# - #fetch: Returns the string field value for the given key. +# - #get_fields: Returns the array field value for the given +key+. +# - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>. +# - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>. +# - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+. +# - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>. +# - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>. +# +# === Queries +# +# - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>. +# - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>. +# - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>. +# - #key?: Returns whether a given key exists. +# +# === Iterators +# +# - #each_capitalized: Passes each field capitalized-name/value pair to the block. +# - #each_capitalized_name: Passes each capitalized field name to the block. +# - #each_header: Passes each field name/value pair to the block. +# - #each_name: Passes each field name to the block. +# - #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) + def initialize_http_header(initheader) #:nodoc: @header = {} return unless initheader initheader.each do |key, value| @@ -19,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 @@ -33,14 +211,32 @@ module Net::HTTPHeader alias length size #:nodoc: obsolete - # Returns the header field corresponding to the case-insensitive key. - # For example, a key of "Content-Type" might return "text/html" + # Returns the string field value for the case-insensitive field +key+, + # or +nil+ if there is no such key; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['Connection'] # => "keep-alive" + # res['Nosuch'] # => nil + # + # Note that some field values may be retrieved via convenience methods; + # see {Getters}[rdoc-ref:Net::HTTPHeader@Getters]. def [](key) a = @header[key.downcase.to_s] or return nil a.join(', ') end - # Sets the header field corresponding to the case-insensitive key. + # Sets the value for the case-insensitive +key+ to +val+, + # overwriting the previous value if the field exists; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # req = Net::HTTP::Get.new(uri) + # req['Accept'] # => "*/*" + # req['Accept'] = 'text/html' + # req['Accept'] # => "text/html" + # + # Note that some field values may be set via convenience methods; + # see {Setters}[rdoc-ref:Net::HTTPHeader@Setters]. def []=(key, val) unless val @header.delete key.downcase.to_s @@ -49,20 +245,18 @@ module Net::HTTPHeader set_field(key, val) end - # [Ruby 1.8.3] - # Adds a value to a named header field, instead of replacing its value. - # Second argument +val+ must be a String. - # See also #[]=, #[] and #get_fields. + # Adds value +val+ to the value array for field +key+ if the field exists; + # creates the field with the given +key+ and +val+ if it does not exist. + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # request.add_field 'X-My-Header', 'a' - # p request['X-My-Header'] #=> "a" - # p request.get_fields('X-My-Header') #=> ["a"] - # request.add_field 'X-My-Header', 'b' - # p request['X-My-Header'] #=> "a, b" - # p request.get_fields('X-My-Header') #=> ["a", "b"] - # request.add_field 'X-My-Header', 'c' - # p request['X-My-Header'] #=> "a, b, c" - # p request.get_fields('X-My-Header') #=> ["a", "b", "c"] + # req = Net::HTTP::Get.new(uri) + # req.add_field('Foo', 'bar') + # req['Foo'] # => "bar" + # req.add_field('Foo', 'baz') + # req['Foo'] # => "bar, baz" + # req.add_field('Foo', %w[baz bam]) + # req['Foo'] # => "bar, baz, baz, bam" + # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"] # def add_field(key, val) stringified_downcased_key = key.downcase.to_s @@ -101,16 +295,13 @@ module Net::HTTPHeader end end - # [Ruby 1.8.3] - # Returns an array of header field strings corresponding to the - # case-insensitive +key+. This method allows you to get duplicated - # header fields without any processing. See also #[]. + # Returns the array field value for the given +key+, + # or +nil+ if there is no such field; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # p response.get_fields('Set-Cookie') - # #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23", - # "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"] - # p response['Set-Cookie'] - # #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23" + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res.get_fields('Connection') # => ["keep-alive"] + # res.get_fields('Nosuch') # => nil # def get_fields(key) stringified_downcased_key = key.downcase.to_s @@ -118,24 +309,58 @@ module Net::HTTPHeader @header[stringified_downcased_key].dup end - # Returns the header field corresponding to the case-insensitive key. - # Returns the default value +args+, or the result of the block, or - # raises an IndexError if there's no header field named +key+ - # See Hash#fetch + # call-seq: + # fetch(key, default_val = nil) {|key| ... } -> object + # fetch(key, default_val = nil) -> value or default_val + # + # With a block, returns the string value for +key+ if it exists; + # otherwise returns the value of the block; + # ignores the +default_val+; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # + # # Field exists; block not called. + # res.fetch('Connection') do |value| + # fail 'Cannot happen' + # end # => "keep-alive" + # + # # Field does not exist; block called. + # res.fetch('Nosuch') do |value| + # value.downcase + # end # => "nosuch" + # + # With no block, returns the string value for +key+ if it exists; + # otherwise, returns +default_val+ if it was given; + # otherwise raises an exception: + # + # res.fetch('Connection', 'Foo') # => "keep-alive" + # res.fetch('Nosuch', 'Foo') # => "Foo" + # res.fetch('Nosuch') # Raises KeyError. + # def fetch(key, *args, &block) #:yield: +key+ a = @header.fetch(key.downcase.to_s, *args, &block) a.kind_of?(Array) ? a.join(', ') : a end - # Iterates through the header names and values, passing in the name - # and value to the code block supplied. + # Calls the block with each key/value pair: # - # Returns an enumerator if no block is given. + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res.each_header do |key, value| + # p [key, value] if key.start_with?('c') + # end # - # Example: + # Output: # - # response.header.each_header {|key,value| puts "#{key} = #{value}" } + # ["content-type", "application/json; charset=utf-8"] + # ["connection", "keep-alive"] + # ["cache-control", "max-age=43200"] + # ["cf-cache-status", "HIT"] + # ["cf-ray", "771d17e9bc542cf5-ORD"] # + # Returns an enumerator if no block is given. + # + # Net::HTTPHeader#each is an alias for Net::HTTPHeader#each_header. def each_header #:yield: +key+, +value+ block_given? or return enum_for(__method__) { @header.size } @header.each do |k,va| @@ -145,10 +370,24 @@ module Net::HTTPHeader alias each each_header - # Iterates through the header names in the header, passing - # each header name to the code block. + # Calls the block with each field key: + # + # 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" # # Returns an enumerator if no block is given. + # + # Net::HTTPHeader#each_name is an alias for Net::HTTPHeader#each_key. def each_name(&block) #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key(&block) @@ -156,12 +395,23 @@ module Net::HTTPHeader alias each_key each_name - # Iterates through the header names in the header, passing - # capitalized header names to the code block. + # Calls the block with each capitalized field name: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res.each_capitalized_name do |key| + # p key if key.start_with?('C') + # end + # + # Output: # - # Note that header names are capitalized systematically; - # capitalization may not match that used by the remote HTTP - # server in its response. + # "Content-Type" + # "Connection" + # "Cache-Control" + # "Cf-Cache-Status" + # "Cf-Ray" + # + # The capitalization is system-dependent; + # see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. # # Returns an enumerator if no block is given. def each_capitalized_name #:yield: +key+ @@ -171,8 +421,18 @@ module Net::HTTPHeader end end - # Iterates through header values, passing each value to the - # code block. + # Calls the block with each string field value: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res.each_value do |value| + # p value if value.start_with?('c') + # end + # + # Output: + # + # "chunked" + # "cf-q-config;dur=6.0000002122251e-06" + # "cloudflare" # # Returns an enumerator if no block is given. def each_value #:yield: +value+ @@ -182,32 +442,45 @@ module Net::HTTPHeader end end - # Removes a header field, specified by case-insensitive key. + # Removes the header for the given case-insensitive +key+ + # (see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]); + # returns the deleted value, or +nil+ if no such field exists: + # + # req = Net::HTTP::Get.new(uri) + # req.delete('Accept') # => ["*/*"] + # req.delete('Nosuch') # => nil + # def delete(key) @header.delete(key.downcase.to_s) end - # true if +key+ header exists. + # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise: + # + # req = Net::HTTP::Get.new(uri) + # req.key?('Accept') # => true + # req.key?('Nosuch') # => false + # def key?(key) @header.key?(key.downcase.to_s) end - # Returns a Hash consisting of header names and array of values. - # e.g. - # {"cache-control" => ["private"], - # "content-type" => ["text/html"], - # "date" => ["Wed, 22 Jun 2005 22:11:50 GMT"]} + # Returns a hash of the key/value pairs: + # + # req = Net::HTTP::Get.new(uri) + # 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"]} + # def to_hash @header.dup end - # As for #each_header, except the keys are provided in capitalized form. + # Like #each_header, but the keys are returned in capitalized form. # - # Note that header names are capitalized systematically; - # capitalization may not match that used by the remote HTTP - # server in its response. - # - # Returns an enumerator if no block is given. + # Net::HTTPHeader#canonical_each is an alias for Net::HTTPHeader#each_capitalized. def each_capitalized block_given? or return enum_for(__method__) { @header.size } @header.each do |k,v| @@ -222,8 +495,17 @@ module Net::HTTPHeader end private :capitalize - # Returns an Array of Range objects which represent the Range: - # HTTP header field, or +nil+ if there is no such header. + # Returns an array of Range objects that represent + # the value of field <tt>'Range'</tt>, + # or +nil+ if there is no such field; + # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: + # + # req = Net::HTTP::Get.new(uri) + # req['Range'] = 'bytes=0-99,200-299,400-499' + # req.range # => [0..99, 200..299, 400..499] + # req.delete('Range') + # req.range # # => nil + # def range return nil unless @header['range'] @@ -266,14 +548,31 @@ module Net::HTTPHeader result end - # Sets the HTTP Range: header. - # Accepts either a Range object as a single argument, - # or a beginning index and a length from that index. - # Example: + # call-seq: + # set_range(length) -> length + # set_range(offset, length) -> range + # set_range(begin..length) -> range + # + # Sets the value for field <tt>'Range'</tt>; + # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: + # + # With argument +length+: + # + # req = Net::HTTP::Get.new(uri) + # req.set_range(100) # => 100 + # req['Range'] # => "bytes=0-99" # - # req.range = (0..1023) - # req.set_range 0, 1023 + # With arguments +offset+ and +length+: # + # req.set_range(100, 100) # => 100...200 + # req['Range'] # => "bytes=100-199" + # + # With argument +range+: + # + # req.set_range(100..199) # => 100..199 + # req['Range'] # => "bytes=100-199" + # + # Net::HTTPHeader#range= is an alias for Net::HTTPHeader#set_range. def set_range(r, e = nil) unless r @header.delete 'range' @@ -305,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 @@ -314,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' @@ -322,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 @@ -344,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('/') @@ -377,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(';') @@ -391,29 +761,54 @@ 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 = {}) @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] end 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>: + # + # req = Net::HTTP::Post.new('example.com') + # + # req.set_form_data(q: 'ruby', lang: 'en') + # req.body # => "q=ruby&lang=en" + # req['Content-Type'] # => "application/x-www-form-urlencoded" # - # Values are URL encoded as necessary and the content-type is set to - # application/x-www-form-urlencoded + # req.set_form_data([['q', 'ruby'], ['lang', 'en']]) + # req.body # => "q=ruby&lang=en" # - # 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', '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 = '&') query = URI.encode_www_form(params) query.gsub!(/&/, sep) if sep != '&' @@ -423,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 @@ -485,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 @@ -500,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} @@ -507,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 1e86f3e4b4..4a138572e9 100644 --- a/lib/net/http/request.rb +++ b/lib/net/http/request.rb @@ -1,8 +1,76 @@ -# frozen_string_literal: false -# HTTP request class. -# This class wraps together the request header and the request path. -# You cannot use this class directly. Instead, you should use one of its -# subclasses: Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Head. +# frozen_string_literal: true + +# This class is the base class for \Net::HTTP request classes. +# The class should not be used directly; +# 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: +# +# - Net::HTTP::Get +# - Net::HTTP::Head +# - Net::HTTP::Post +# - Net::HTTP::Put +# - Net::HTTP::Delete +# - Net::HTTP::Options +# - Net::HTTP::Trace +# - Net::HTTP::Patch +# +# Subclasses for WebDAV requests: +# +# - Net::HTTP::Propfind +# - Net::HTTP::Proppatch +# - Net::HTTP::Mkcol +# - Net::HTTP::Copy +# - Net::HTTP::Move +# - Net::HTTP::Lock +# - Net::HTTP::Unlock # class Net::HTTPRequest < Net::HTTPGenericRequest # Creates an HTTP request object for +path+. @@ -18,4 +86,3 @@ class Net::HTTPRequest < Net::HTTPGenericRequest path, initheader end end - diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index d4c80a3812..5724164205 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -1,67 +1,257 @@ -# frozen_string_literal: false -# +# frozen_string_literal: true + # HTTP/1.1 methods --- RFC2616 -# -# See Net::HTTPGenericRequest for attributes and methods. -# See Net::HTTP for usage examples. +# \Class for representing +# {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: optional. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# +# Related: +# +# - Net::HTTP.get: sends +GET+ request, returns response body. +# - Net::HTTP#get: sends +GET+ request, returns response object. +# class Net::HTTP::Get < Net::HTTPRequest METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. -# See Net::HTTP for usage examples. +# \Class for representing +# {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Head.new(uri) # => #<Net::HTTP::Head HEAD> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: optional. +# - Response body: no. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# +# Related: +# +# - Net::HTTP#head: sends +HEAD+ request, returns response object. +# class Net::HTTP::Head < Net::HTTPRequest METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false end -# See Net::HTTPGenericRequest for attributes and methods. -# See Net::HTTP for usage examples. +# \Class for representing +# {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# uri.path = '/posts' +# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> +# req.body = '{"title": "foo","body": "bar","userId": 1}' +# req.content_type = 'application/json' +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: yes. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# +# Related: +# +# - Net::HTTP.post: sends +POST+ request, returns response object. +# - Net::HTTP#post: sends +POST+ request, returns response object. +# class Net::HTTP::Post < Net::HTTPRequest METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. -# See Net::HTTP for usage examples. +# \Class for representing +# {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# uri.path = '/posts' +# req = Net::HTTP::Put.new(uri) # => #<Net::HTTP::Put PUT> +# req.body = '{"title": "foo","body": "bar","userId": 1}' +# req.content_type = 'application/json' +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: yes. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# class Net::HTTP::Put < Net::HTTPRequest METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. -# See Net::HTTP for usage examples. +# \Class for representing +# {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# uri.path = '/posts/1' +# req = Net::HTTP::Delete.new(uri) # => #<Net::HTTP::Delete DELETE> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: optional. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# +# Related: +# +# - Net::HTTP#delete: sends +DELETE+ request, returns response object. +# class Net::HTTP::Delete < Net::HTTPRequest METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Options.new(uri) # => #<Net::HTTP::Options OPTIONS> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: optional. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# +# Related: +# +# - Net::HTTP#options: sends +OPTIONS+ request, returns response object. +# class Net::HTTP::Options < Net::HTTPRequest METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Trace.new(uri) # => #<Net::HTTP::Trace TRACE> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: no. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# +# Related: +# +# - Net::HTTP#trace: sends +TRACE+ request, returns response object. +# class Net::HTTP::Trace < Net::HTTPRequest METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end +# \Class for representing +# {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]: # -# PATCH method --- RFC5789 +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# uri.path = '/posts' +# req = Net::HTTP::Patch.new(uri) # => #<Net::HTTP::Patch PATCH> +# req.body = '{"title": "foo","body": "bar","userId": 1}' +# req.content_type = 'application/json' +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Properties: +# +# - Request body: yes. +# - Response body: yes. +# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. +# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# +# Related: +# +# - Net::HTTP#patch: sends +PATCH+ request, returns response object. # - -# See Net::HTTPGenericRequest for attributes and methods. class Net::HTTP::Patch < Net::HTTPRequest METHOD = 'PATCH' REQUEST_HAS_BODY = true @@ -72,49 +262,161 @@ end # WebDAV methods --- RFC2518 # -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Propfind.new(uri) # => #<Net::HTTP::Propfind PROPFIND> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. +# class Net::HTTP::Propfind < Net::HTTPRequest METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Proppatch.new(uri) # => #<Net::HTTP::Proppatch PROPPATCH> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. +# class Net::HTTP::Proppatch < Net::HTTPRequest METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Mkcol.new(uri) # => #<Net::HTTP::Mkcol MKCOL> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. +# class Net::HTTP::Mkcol < Net::HTTPRequest METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Copy.new(uri) # => #<Net::HTTP::Copy COPY> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#copy: sends +COPY+ request, returns response object. +# class Net::HTTP::Copy < Net::HTTPRequest METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Move.new(uri) # => #<Net::HTTP::Move MOVE> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#move: sends +MOVE+ request, returns response object. +# class Net::HTTP::Move < Net::HTTPRequest METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Lock.new(uri) # => #<Net::HTTP::Lock LOCK> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#lock: sends +LOCK+ request, returns response object. +# class Net::HTTP::Lock < Net::HTTPRequest METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end -# See Net::HTTPGenericRequest for attributes and methods. +# \Class for representing +# {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]: +# +# require 'net/http' +# uri = URI('http://example.com') +# hostname = uri.hostname # => "example.com" +# req = Net::HTTP::Unlock.new(uri) # => #<Net::HTTP::Unlock UNLOCK> +# res = Net::HTTP.start(hostname) do |http| +# http.request(req) +# end +# +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# +# Related: +# +# - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. +# class Net::HTTP::Unlock < Net::HTTPRequest METHOD = 'UNLOCK' REQUEST_HAS_BODY = true diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index f8b522f1ff..40de963868 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -1,20 +1,136 @@ -# frozen_string_literal: false -# HTTP response class. +# frozen_string_literal: true + +# This class is the base class for \Net::HTTP response classes. +# +# == About the Examples +# +# :include: doc/net-http/examples.rdoc +# +# == Returned Responses +# +# \Method Net::HTTP.get_response returns +# an instance of one of the subclasses of \Net::HTTPResponse: +# +# Net::HTTP.get_response(uri) +# # => #<Net::HTTPOK 200 OK readbody=true> +# Net::HTTP.get_response(hostname, '/nosuch') +# # => #<Net::HTTPNotFound 404 Not Found readbody=true> +# +# As does method Net::HTTP#request: +# +# req = Net::HTTP::Get.new(uri) +# Net::HTTP.start(hostname) do |http| +# http.request(req) +# end # => #<Net::HTTPOK 200 OK readbody=true> +# +# \Class \Net::HTTPResponse includes module Net::HTTPHeader, +# which provides access to response header values via (among others): +# +# - \Hash-like method <tt>[]</tt>. +# - Specific reader methods, such as +content_type+. +# +# Examples: +# +# res = Net::HTTP.get_response(uri) # => #<Net::HTTPOK 200 OK readbody=true> +# res['Content-Type'] # => "text/html; charset=UTF-8" +# res.content_type # => "text/html" +# +# == Response Subclasses +# +# \Class \Net::HTTPResponse has a subclass for each +# {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes]. +# You can look up the response class for a given code: +# +# Net::HTTPResponse::CODE_TO_OBJ['200'] # => Net::HTTPOK +# Net::HTTPResponse::CODE_TO_OBJ['400'] # => Net::HTTPBadRequest +# Net::HTTPResponse::CODE_TO_OBJ['404'] # => Net::HTTPNotFound +# +# And you can retrieve the status code for a response object: +# +# Net::HTTP.get_response(uri).code # => "200" +# Net::HTTP.get_response(hostname, '/nosuch').code # => "404" +# +# The response subclasses (indentation shows class hierarchy): # -# This class wraps together the response header and the response body (the -# entity requested). +# - Net::HTTPUnknownResponse (for unhandled \HTTP extensions). # -# It mixes in the HTTPHeader module, which provides access to response -# header values both via hash-like methods and via individual readers. +# - Net::HTTPInformation: # -# Note that each possible HTTP response code defines its own -# HTTPResponse subclass. All classes are defined under the Net module. -# Indentation indicates inheritance. For a list of the classes see Net::HTTP. +# - Net::HTTPContinue (100) +# - Net::HTTPSwitchProtocol (101) +# - Net::HTTPProcessing (102) +# - Net::HTTPEarlyHints (103) # -# Correspondence <code>HTTP code => class</code> is stored in CODE_TO_OBJ -# constant: +# - Net::HTTPSuccess: # -# Net::HTTPResponse::CODE_TO_OBJ['404'] #=> Net::HTTPNotFound +# - Net::HTTPOK (200) +# - Net::HTTPCreated (201) +# - Net::HTTPAccepted (202) +# - Net::HTTPNonAuthoritativeInformation (203) +# - Net::HTTPNoContent (204) +# - Net::HTTPResetContent (205) +# - Net::HTTPPartialContent (206) +# - Net::HTTPMultiStatus (207) +# - Net::HTTPAlreadyReported (208) +# - Net::HTTPIMUsed (226) +# +# - Net::HTTPRedirection: +# +# - Net::HTTPMultipleChoices (300) +# - Net::HTTPMovedPermanently (301) +# - Net::HTTPFound (302) +# - Net::HTTPSeeOther (303) +# - Net::HTTPNotModified (304) +# - Net::HTTPUseProxy (305) +# - Net::HTTPTemporaryRedirect (307) +# - Net::HTTPPermanentRedirect (308) +# +# - Net::HTTPClientError: +# +# - Net::HTTPBadRequest (400) +# - Net::HTTPUnauthorized (401) +# - Net::HTTPPaymentRequired (402) +# - Net::HTTPForbidden (403) +# - Net::HTTPNotFound (404) +# - Net::HTTPMethodNotAllowed (405) +# - Net::HTTPNotAcceptable (406) +# - Net::HTTPProxyAuthenticationRequired (407) +# - Net::HTTPRequestTimeOut (408) +# - Net::HTTPConflict (409) +# - Net::HTTPGone (410) +# - Net::HTTPLengthRequired (411) +# - Net::HTTPPreconditionFailed (412) +# - Net::HTTPRequestEntityTooLarge (413) +# - Net::HTTPRequestURITooLong (414) +# - Net::HTTPUnsupportedMediaType (415) +# - Net::HTTPRequestedRangeNotSatisfiable (416) +# - Net::HTTPExpectationFailed (417) +# - Net::HTTPMisdirectedRequest (421) +# - Net::HTTPUnprocessableEntity (422) +# - Net::HTTPLocked (423) +# - Net::HTTPFailedDependency (424) +# - Net::HTTPUpgradeRequired (426) +# - Net::HTTPPreconditionRequired (428) +# - Net::HTTPTooManyRequests (429) +# - Net::HTTPRequestHeaderFieldsTooLarge (431) +# - Net::HTTPUnavailableForLegalReasons (451) +# +# - Net::HTTPServerError: +# +# - Net::HTTPInternalServerError (500) +# - Net::HTTPNotImplemented (501) +# - Net::HTTPBadGateway (502) +# - Net::HTTPServiceUnavailable (503) +# - Net::HTTPGatewayTimeOut (504) +# - Net::HTTPVersionNotSupported (505) +# - Net::HTTPVariantAlsoNegotiates (506) +# - Net::HTTPInsufficientStorage (507) +# - Net::HTTPLoopDetected (508) +# - Net::HTTPNotExtended (510) +# - Net::HTTPNetworkAuthenticationRequired (511) +# +# There is also the Net::HTTPBadResponse exception which is raised when +# there is a protocol error. # class Net::HTTPResponse class << self @@ -108,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 @@ -138,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 @@ -231,6 +366,7 @@ class Net::HTTPResponse @body = nil end @read = true + return if @body.nil? case enc = @body_encoding when Encoding, false, nil @@ -246,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 @@ -504,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) @@ -513,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/net/protocol.rb b/lib/net/protocol.rb index c676854b67..ea0752a971 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -26,7 +26,7 @@ require 'io/wait' module Net # :nodoc: class Protocol #:nodoc: internal use only - VERSION = "0.1.3" + VERSION = "0.2.1" private def Protocol.protocol_param(name, val) @@ -120,6 +120,7 @@ module Net # :nodoc: @continue_timeout = continue_timeout @debug_output = debug_output @rbuf = ''.b + @rbuf_empty = true @rbuf_offset = 0 end @@ -156,7 +157,7 @@ module Net # :nodoc: read_bytes = 0 begin while read_bytes + rbuf_size < len - if s = rbuf_consume_all_shareable! + if s = rbuf_consume_all read_bytes += s.bytesize dest << s end @@ -177,7 +178,7 @@ module Net # :nodoc: read_bytes = 0 begin while true - if s = rbuf_consume_all_shareable! + if s = rbuf_consume_all read_bytes += s.bytesize dest << s end @@ -249,18 +250,8 @@ module Net # :nodoc: @rbuf.bytesize - @rbuf_offset end - # Warning: this method may share the buffer to avoid - # copying. The caller must no longer use the returned - # string once rbuf_fill has been called again - def rbuf_consume_all_shareable! - @rbuf_empty = true - buf = if @rbuf_offset == 0 - @rbuf - else - @rbuf.byteslice(@rbuf_offset..-1) - end - @rbuf_offset = @rbuf.bytesize - buf + def rbuf_consume_all + rbuf_consume if rbuf_size > 0 end def rbuf_consume(len = nil) diff --git a/lib/open-uri.gemspec b/lib/open-uri.gemspec index 12f10ef316..cad63e4d80 100644 --- a/lib/open-uri.gemspec +++ b/lib/open-uri.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "open-uri" - spec.version = "0.2.0" + spec.version = "0.3.0" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A((bin|test|spec|features)/|\.git|[Rr]ake|Gemfile)|\.gemspec\z}) } end spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/open-uri.rb b/lib/open-uri.rb index cb9c3aa505..93e8cfcdb7 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -99,6 +99,8 @@ module OpenURI :open_timeout => true, :ssl_ca_cert => nil, :ssl_verify_mode => nil, + :ssl_min_version => nil, + :ssl_max_version => nil, :ftp_active_mode => false, :redirect => true, :encoding => nil, @@ -298,6 +300,8 @@ module OpenURI require 'net/https' http.use_ssl = true http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER + http.min_version = options[:ssl_min_version] + http.max_version = options[:ssl_max_version] store = OpenSSL::X509::Store.new if options[:ssl_ca_cert] Array(options[:ssl_ca_cert]).each do |cert| @@ -353,7 +357,8 @@ module OpenURI when Net::HTTPMovedPermanently, # 301 Net::HTTPFound, # 302 Net::HTTPSeeOther, # 303 - Net::HTTPTemporaryRedirect # 307 + Net::HTTPTemporaryRedirect, # 307 + Net::HTTPPermanentRedirect # 308 begin loc_uri = URI.parse(resp['location']) rescue URI::InvalidURIError @@ -410,6 +415,13 @@ module OpenURI end end + # :stopdoc: + RE_LWS = /[\r\n\t ]+/n + RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n + RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n + RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n + # :startdoc: + # Mixin for holding meta-information. module Meta def Meta.init(obj, src=nil) # :nodoc: @@ -487,13 +499,6 @@ module OpenURI end end - # :stopdoc: - RE_LWS = /[\r\n\t ]+/n - RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n - RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n - RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n - # :startdoc: - def content_type_parse # :nodoc: vs = @metas['content-type'] # The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045. @@ -698,6 +703,20 @@ module OpenURI # # :ssl_verify_mode is used to specify openssl verify mode. # + # [:ssl_min_version] + # Synopsis: + # :ssl_min_version=>:TLS1_2 + # + # :ssl_min_version option specifies the minimum allowed SSL/TLS protocol + # version. See also OpenSSL::SSL::SSLContext#min_version=. + # + # [:ssl_max_version] + # Synopsis: + # :ssl_max_version=>:TLS1_2 + # + # :ssl_max_version option specifies the maximum allowed SSL/TLS protocol + # version. See also OpenSSL::SSL::SSLContext#max_version=. + # # [:ftp_active_mode] # Synopsis: # :ftp_active_mode=>bool 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 1d42c79045..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.2.0" + OptionParser::Version = "0.3.1" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -1148,6 +1148,7 @@ XXX @summary_indent = indent @default_argv = ARGV @require_exact = false + @raise_unknown = true add_officious yield self if block_given? end @@ -1225,6 +1226,9 @@ XXX # abbreviated long option as short option). attr_accessor :require_exact + # Whether to raise at unknown option. + attr_accessor :raise_unknown + # # Heading banner preceding summary. # @@ -1502,7 +1506,7 @@ XXX style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else - desc.push(o) + desc.push(o) if o && !o.empty? end end @@ -1639,9 +1643,11 @@ XXX begin sw, = complete(:long, opt, true) if require_exact && !sw.long.include?(arg) + throw :terminate, arg unless raise_unknown raise InvalidOption, arg end rescue ParseError + throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin @@ -1673,6 +1679,7 @@ XXX end end rescue ParseError + throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin @@ -1903,10 +1910,13 @@ XXX # directory ~/.options, then the basename with '.options' suffix # under XDG and Haiku standard places. # - def load(filename = nil) + # The optional +into+ keyword argument works exactly like that accepted in + # method #parse. + # + def load(filename = nil, into: nil) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options')) rescue nil + return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil basename << ".options" return [ # XDG @@ -1918,11 +1928,11 @@ XXX '~/config/settings', ].any? {|dir| next if !dir or dir.empty? - load(File.expand_path(basename, dir)) rescue nil + load(File.expand_path(basename, dir), into: into) rescue nil } end begin - parse(*IO.readlines(filename).each {|s| s.chomp!}) + parse(*File.readlines(filename, chomp: true), into: into) true rescue Errno::ENOENT, Errno::ENOTDIR false @@ -2074,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/pp.gemspec b/lib/pp.gemspec index d4b0be83df..3f08f400c4 100644 --- a/lib/pp.gemspec +++ b/lib/pp.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "pp" - spec.version = "0.3.0" + spec.version = "0.4.0" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] |
