diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index acef86d7b5b3aa..8346f056f46fdd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -77,15 +77,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: category: '/language:${{ matrix.language }}' upload: False @@ -115,7 +115,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 5b4121c7178e1c..646a3736f4a108 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -80,6 +80,14 @@ jobs: sudo sysctl -w kern.coredump=1 sudo chmod -R +rwx /cores/ + - name: Delete unused SDKs + # To free up disk space to not run out during the run + run: | + sudo rm -rf ~/.dotnet + sudo rm -rf /Library/Android + sudo rm -rf /Library/Developer/CoreSimulator + continue-on-error: true + - name: Run configure run: ../src/configure -C --disable-install-doc ${ruby_configure_args} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e335c965143494..5534e3defec392 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,7 +96,7 @@ jobs: -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/rbenv/ruby-build/dispatches \ - -d '{"event_type": "update-ruby"}' + -d '{"event_type": "update-ruby", "client_payload": {"ruby_version": "${{ env.RUBY_VERSION }}", "openssl_version": "3.0.15"}}' - name: Update all-ruby definition run: | diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 406dd41c57a58a..5641c4d3d49b39 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ca1074f57b5ad1..83ec71b0b38f3d 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -42,6 +42,8 @@ jobs: - test_task: test-bundled-gems - test_task: check os: ubuntu-20.04 + - test_task: check + os: ubuntu-24.04 fail-fast: false env: diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 51eeb7a75b2ae7..3a5a5923928a39 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -136,7 +136,7 @@ jobs: - run: tar cfz ../install.tar.gz -C ../install . - name: Upload artifacts - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ruby-wasm-install path: ${{ github.workspace }}/install.tar.gz @@ -164,7 +164,7 @@ jobs: - name: Save Pull Request number if: ${{ github.event_name == 'pull_request' }} run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ github.event_name == 'pull_request' }} with: name: github-pr-info diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index dde781b9eeda69..190710d745bf82 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -9,13 +9,8 @@ on: - '**/.document' - '.*.yml' pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' + # Do not use paths-ignore for required status checks + # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: concurrency: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7fad46c601a125..00000000000000 --- a/.travis.yml +++ /dev/null @@ -1,153 +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. - -# When you see Travis CI issues, or you are interested in understanding how to -# manage, please check the link below. -# https://github.com/ruby/ruby/wiki/CI-Servers#travis-ci - -# We enable Travis on the specific branches or forked repositories here. -if: >- - (repo != ruby/ruby OR branch = master OR branch =~ /^ruby_\d_\d$/) - AND (commit_message !~ /\[DOC\]/) - -language: c - -os: linux - -dist: jammy - -git: - quiet: true - -env: - global: - - NPROC="$(nproc)" - - JOBS="-j${NPROC}" - # SETARCH are overridden when necessary. See below. - - SETARCH= - # 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" - # -g0 disables backtraces when SEGV. Do not set that. - - debugflags=-ggdb3 - - RUBY_TESTOPTS="$JOBS -q --tty=no" - -.org.ruby-lang.ci.matrix-definitions: - - &gcc-11 - compiler: gcc-11 - 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 - gcc-11 - g++-11 - libffi-dev - libncurses-dev - libncursesw5-dev - libreadline-dev - libssl-dev - libyaml-dev - openssl - zlib1g-dev - - gcc-11 --version - - &arm64-linux - name: arm64-linux - arch: arm64 - <<: *gcc-11 - - &ppc64le-linux - name: ppc64le-linux - arch: ppc64le - <<: *gcc-11 - - &s390x-linux - name: s390x-linux - arch: s390x - <<: *gcc-11 - env: - # Avoid possible test failures with the zlib applying the following patch - # on s390x CPU architecture. - # https://github.com/madler/zlib/pull/410 - - DFLTCC=0 - - &arm32-linux - name: arm32-linux - arch: arm64 - # https://packages.ubuntu.com/jammy/crossbuild-essential-armhf - compiler: arm-linux-gnueabihf-gcc - env: - - SETARCH='setarch linux32 --verbose --32bit' - # Still keep the -O1 for only arm32, while we want to test with the - # default optflags -O3. - # Because bootstraptest/test_ractor.rb fails with segfualt with the - # default -O3. - # https://bugs.ruby-lang.org/issues/19981 - - optflags=-O1 - 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 - 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: - - <<: *arm64-linux - - <<: *ppc64le-linux - - <<: *s390x-linux - # FIXME: lib/rubygems/util.rb:104 glob_files_in_dir - - # :411:in glob: File name too long - (Errno::ENAMETOOLONG) - # https://github.com/rubygems/rubygems/issues/7132 - - <<: *arm32-linux - allow_failures: - # Allow failures for the unstable jobs. - # - name: arm64-linux - - name: ppc64le-linux - - name: s390x-linux - # The 2nd arm64 pipeline may be unstable. - # - name: arm32-linux - fast_finish: true - -before_script: - - lscpu - - ./autogen.sh - - mkdir build - - cd build - - $SETARCH ../configure -C --disable-install-doc --prefix=$(pwd)/install - - $SETARCH make -s $JOBS - - make -s $JOBS install - # Useful info to report issues to the Ruby. - - $SETARCH $(pwd)/install/bin/ruby -v - # Useful info To report issues to the RubyGems. - - $SETARCH $(pwd)/install/bin/gem env - -script: - - $SETARCH make -s test - - ../tool/travis_wait.sh $SETARCH make -s test-all RUBYOPT="-w" - - ../tool/travis_wait.sh $SETARCH make -s test-spec - -# We want to be notified when something happens. -notifications: - webhooks: - urls: - # ruby-lang slack: ruby/simpler-alerts-bot (travis) - - secure: mRsoS/UbqDkKkW5p3AEqM27d4SZnV6Gsylo3bm8T/deltQzTsGzZwrm7OIBXZv0UFZdE68XmPlyHfZFLSP2V9QZ7apXMf9/vw0GtcSe1gchtnjpAPF6lYBn7nMCbVPPx9cS0dwL927fjdRM1vj7IKZ2bk4F0lAJ25R25S6teqdk= - on_success: never - on_failure: always - email: - recipients: - - jun.aruga@gmail.com - on_success: never - on_failure: always diff --git a/NEWS.md b/NEWS.md index 1dffd2dad0be92..235aeb0b72d0ee 100644 --- a/NEWS.md +++ b/NEWS.md @@ -71,25 +71,28 @@ The following default gems are updated. * RubyGems 3.6.0.dev * bundler 2.6.0.dev * erb 4.0.4 -* fiddle 1.1.3 +* fiddle 1.1.3.dev * io-console 0.7.2 * irb 1.14.0 * json 2.7.2 +* logger 1.6.1 * net-http 0.4.1 * optparse 0.5.0 * prism 1.0.0 * rdoc 6.7.0 * reline 0.5.9 * resolv 0.4.0 -* stringio 3.1.2 -* strscan 3.1.1 +* stringio 3.1.2.dev +* strscan 3.1.1.dev +* uri 0.13.1 +* zlib 3.1.1 The following bundled gems are updated. * minitest 5.25.1 * rake 13.2.1 * test-unit 3.6.2 -* rexml 3.3.6 +* rexml 3.3.7 * rss 0.3.1 * net-ftp 0.3.7 * net-imap 0.4.15 diff --git a/array.c b/array.c index fd2fecfddb65ac..d07382ce63fd9c 100644 --- a/array.c +++ b/array.c @@ -1884,6 +1884,7 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * # Raises TypeError (no implicit conversion of Symbol into Integer): * a[:foo] * + * Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. */ VALUE @@ -8582,7 +8583,7 @@ rb_ary_deconstruct(VALUE ary) * * These methods do not modify +self+. * - * - #[]: Returns one or more elements. + * - #[], #slice: Returns consecutive elements as determined by a given argument. * - #fetch: Returns the element at a given offset. * - #first: Returns one or more leading elements. * - #last: Returns one or more trailing elements. @@ -8604,7 +8605,6 @@ rb_ary_deconstruct(VALUE ary) * - #take: Returns leading elements as determined by a given index. * - #drop_while: Returns trailing elements as determined by a given block. * - #take_while: Returns leading elements as determined by a given block. - * - #slice: Returns consecutive elements as determined by a given argument. * - #sort: Returns all elements in an order determined by <=> or a given block. * - #reverse: Returns all elements in reverse order. * - #compact: Returns an array containing all non-+nil+ elements. diff --git a/ast.c b/ast.c index 338ab00819a37b..12366dd432f7b4 100644 --- a/ast.c +++ b/ast.c @@ -775,12 +775,24 @@ node_locations(VALUE ast_value, const NODE *node) { enum node_type type = nd_type(node); switch (type) { + case NODE_ALIAS: + return rb_ary_new_from_args(2, + location_new(nd_code_loc(node)), + location_new(&RNODE_ALIAS(node)->keyword_loc)); + case NODE_UNDEF: + return rb_ary_new_from_args(2, + location_new(nd_code_loc(node)), + location_new(&RNODE_UNDEF(node)->keyword_loc)); case NODE_UNLESS: return rb_ary_new_from_args(4, location_new(nd_code_loc(node)), location_new(&RNODE_UNLESS(node)->keyword_loc), location_new(&RNODE_UNLESS(node)->then_keyword_loc), location_new(&RNODE_UNLESS(node)->end_keyword_loc)); + case NODE_VALIAS: + return rb_ary_new_from_args(2, + location_new(nd_code_loc(node)), + location_new(&RNODE_VALIAS(node)->keyword_loc)); case NODE_ARGS_AUX: case NODE_LAST: break; diff --git a/benchmark/time_strftime.yml b/benchmark/time_strftime.yml new file mode 100644 index 00000000000000..28f62aec87a20d --- /dev/null +++ b/benchmark/time_strftime.yml @@ -0,0 +1,7 @@ +prelude: | + # frozen_string_literal: true + time = Time.now +benchmark: + - time.strftime("%FT%T") # 19B + - time.strftime("%FT%T.%3N") # 23B + - time.strftime("%FT%T.%6N") # 26B diff --git a/doc/rdoc/markup_reference.rb b/doc/rdoc/markup_reference.rb index d1901b86e2cb7a..bb2dc67eca9bb3 100644 --- a/doc/rdoc/markup_reference.rb +++ b/doc/rdoc/markup_reference.rb @@ -918,10 +918,6 @@ # # - Linked: https://github.com links to https://github.com. # -# [Protocol +www+] -# -# - Linked: www.yahoo.com links to www.yahoo.com. -# # [Protocol +ftp+] # # - Linked: ftp://nosuch.site links to ftp://nosuch.site. diff --git a/enum.c b/enum.c index f734d1814246b6..b044da20d26a81 100644 --- a/enum.c +++ b/enum.c @@ -4955,7 +4955,7 @@ enum_compact(VALUE obj) * * These methods return information about the \Enumerable other than the elements themselves: * - * - #include?, #member?: Returns +true+ if self == object, +false+ otherwise. + * - #member? (aliased as #include?): Returns +true+ if self == object, +false+ otherwise. * - #all?: Returns +true+ if all elements meet a specified criterion; +false+ otherwise. * - #any?: Returns +true+ if any element meets a specified criterion; +false+ otherwise. * - #none?: Returns +true+ if no element meets a specified criterion; +false+ otherwise. @@ -4970,7 +4970,7 @@ enum_compact(VALUE obj) * * Leading, trailing, or all elements: * - * - #entries, #to_a: Returns all elements. + * - #to_a (aliased as #entries): Returns all elements. * - #first: Returns the first element or leading elements. * - #take: Returns a specified number of leading elements. * - #drop: Returns a specified number of trailing elements. @@ -5005,8 +5005,8 @@ enum_compact(VALUE obj) * * These methods return elements that meet a specified criterion: * - * - #find, #detect: Returns an element selected by the block. - * - #find_all, #filter, #select: Returns elements selected by the block. + * - #find (aliased as #detect): Returns an element selected by the block. + * - #find_all (aliased as #filter, #select): Returns elements selected by the block. * - #find_index: Returns the index of an element selected by a given object or block. * - #reject: Returns elements not rejected by the block. * - #uniq: Returns elements that are not duplicates. @@ -5031,14 +5031,14 @@ enum_compact(VALUE obj) * * === Other Methods * - * - #map, #collect: Returns objects returned by the block. + * - #collect (aliased as #map): Returns objects returned by the block. * - #filter_map: Returns truthy objects returned by the block. - * - #flat_map, #collect_concat: Returns flattened objects returned by the block. + * - #flat_map (aliased as #collect_concat): Returns flattened objects returned by the block. * - #grep: Returns elements selected by a given object * or objects returned by a given block. * - #grep_v: Returns elements selected by a given object * or objects returned by a given block. - * - #reduce, #inject: Returns the object formed by combining all elements. + * - #inject (aliased as #reduce): Returns the object formed by combining all elements. * - #sum: Returns the sum of the elements, using method +. * - #zip: Combines each element with elements from other enumerables; * returns the n-tuples or calls the block with each. diff --git a/ext/fiddle/lib/fiddle/version.rb b/ext/fiddle/lib/fiddle/version.rb index 6c5109dca8d3ef..3444e24f926a2a 100644 --- a/ext/fiddle/lib/fiddle/version.rb +++ b/ext/fiddle/lib/fiddle/version.rb @@ -1,3 +1,3 @@ module Fiddle - VERSION = "1.1.3" + VERSION = "1.1.3.dev" end diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 38f6b5b7af42a3..85e6a0613eed59 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -81,7 +81,7 @@ getattr(int fd, conmode *t) #define CSI "\x1b\x5b" -static ID id_getc, id_console, id_close; +static ID id_getc, id_close; static ID id_gets, id_flush, id_chomp_bang; #if defined HAVE_RUBY_FIBER_SCHEDULER_H @@ -1538,10 +1538,8 @@ console_clear_screen(VALUE io) static VALUE io_open_descriptor_fallback(VALUE klass, int descriptor, int mode, VALUE path, VALUE timeout, void *encoding) { - rb_update_max_fd(descriptor); - VALUE arguments[2] = { - INT2NUM(descriptor), + (rb_update_max_fd(descriptor), INT2NUM(descriptor)), INT2FIX(mode), }; @@ -1566,6 +1564,62 @@ rb_io_closed_p(VALUE io) } #endif +#if defined(RB_EXT_RACTOR_SAFE) && defined(HAVE_RB_RACTOR_LOCAL_STORAGE_VALUE_NEWKEY) +# define USE_RACTOR_STORAGE 1 +#else +# define USE_RACTOR_STORAGE 0 +#endif + +#if USE_RACTOR_STORAGE +#include "ruby/ractor.h" +static rb_ractor_local_key_t key_console_dev; + +static bool +console_dev_get(VALUE klass, VALUE *dev) +{ + return rb_ractor_local_storage_value_lookup(key_console_dev, dev); +} + +static void +console_dev_set(VALUE klass, VALUE value) +{ + rb_ractor_local_storage_value_set(key_console_dev, value); +} + +static void +console_dev_remove(VALUE klass) +{ + console_dev_set(klass, Qnil); +} + +#else + +static ID id_console; + +static int +console_dev_get(VALUE klass, VALUE *dev) +{ + if (rb_const_defined(klass, id_console)) { + *dev = rb_const_get(klass, id_console); + return 1; + } + return 0; +} + +static void +console_dev_set(VALUE klass, VALUE value) +{ + rb_const_set(klass, id_console, value); +} + +static void +console_dev_remove(VALUE klass) +{ + rb_const_remove(klass, id_console); +} + +#endif + /* * call-seq: * IO.console -> # @@ -1594,10 +1648,9 @@ console_dev(int argc, VALUE *argv, VALUE klass) // Force the class to be File. if (klass == rb_cIO) klass = rb_cFile; - if (rb_const_defined(klass, id_console)) { - con = rb_const_get(klass, id_console); + if (console_dev_get(klass, &con)) { if (!RB_TYPE_P(con, T_FILE) || RTEST(rb_io_closed_p(con))) { - rb_const_remove(klass, id_console); + console_dev_remove(klass); con = 0; } } @@ -1606,7 +1659,7 @@ console_dev(int argc, VALUE *argv, VALUE klass) if (sym == ID2SYM(id_close) && argc == 1) { if (con) { rb_io_close(con); - rb_const_remove(klass, id_console); + console_dev_remove(klass); con = 0; } return Qnil; @@ -1647,7 +1700,7 @@ console_dev(int argc, VALUE *argv, VALUE klass) #ifdef CONSOLE_DEVICE_FOR_WRITING rb_io_set_write_io(con, out); #endif - rb_const_set(klass, id_console, con); + console_dev_set(klass, con); } if (sym) { @@ -1764,12 +1817,20 @@ io_getpass(int argc, VALUE *argv, VALUE io) void Init_console(void) { +#if USE_RACTOR_STORAGE + RB_EXT_RACTOR_SAFE(true); +#endif + #undef rb_intern +#if USE_RACTOR_STORAGE + key_console_dev = rb_ractor_local_storage_value_newkey(); +#else + id_console = rb_intern("console"); +#endif id_getc = rb_intern("getc"); id_gets = rb_intern("gets"); id_flush = rb_intern("flush"); id_chomp_bang = rb_intern("chomp!"); - id_console = rb_intern("console"); id_close = rb_intern("close"); #define init_rawmode_opt_id(name) \ rawmode_opt_ids[kwd_##name] = rb_intern(#name) diff --git a/ext/io/console/depend b/ext/io/console/depend index 2907722dbca861..e66b6f4f5d2144 100644 --- a/ext/io/console/depend +++ b/ext/io/console/depend @@ -169,6 +169,7 @@ console.o: $(hdrdir)/ruby/io.h console.o: $(hdrdir)/ruby/missing.h console.o: $(hdrdir)/ruby/onigmo.h console.o: $(hdrdir)/ruby/oniguruma.h +console.o: $(hdrdir)/ruby/ractor.h console.o: $(hdrdir)/ruby/ruby.h console.o: $(hdrdir)/ruby/st.h console.o: $(hdrdir)/ruby/subst.h diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 0cd8eaea0f40fb..6161b747b5fca9 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -10,6 +10,7 @@ have_func("rb_io_get_write_io") have_func("rb_io_closed_p") have_func("rb_io_open_descriptor") +have_func("rb_ractor_local_storage_value_newkey") is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"] # `ok` can be `true`, `false`, or `nil`: diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index dc8f406b5b8594..7ece0cee95a7d5 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -31,12 +31,8 @@ # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif -#ifdef HAVE_RUBY_ENCODING_H #include "ruby/encoding.h" #define FORCE_UTF8(obj) rb_enc_associate((obj), rb_utf8_encoding()) -#else -#define FORCE_UTF8(obj) -#endif /* We don't need to guard objects for rbx, so let's do nothing at all. */ #ifndef RB_GC_GUARD diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index e9686192059840..766e199f74fb3c 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -947,23 +947,20 @@ static void generate_json_array(FBuffer *buffer, VALUE Vstate, JSON_Generator_St fbuffer_append_char(buffer, ']'); } -#ifdef HAVE_RUBY_ENCODING_H static int enc_utf8_compatible_p(rb_encoding *enc) { if (enc == rb_usascii_encoding()) return 1; if (enc == rb_utf8_encoding()) return 1; return 0; } -#endif static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj) { fbuffer_append_char(buffer, '"'); -#ifdef HAVE_RUBY_ENCODING_H if (!enc_utf8_compatible_p(rb_enc_get(obj))) { obj = rb_str_export_to_enc(obj, rb_utf8_encoding()); } -#endif + if (state->ascii_only) { convert_UTF8_to_JSON_ASCII(buffer, obj, state->script_safe); } else { diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 7e44d469f8c7e2..128f683e0f45a3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -3,28 +3,6 @@ #include "../fbuffer/fbuffer.h" #include "parser.h" -#if defined HAVE_RUBY_ENCODING_H -# define EXC_ENCODING rb_utf8_encoding(), -# ifndef HAVE_RB_ENC_RAISE -static void -enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) -{ - va_list args; - VALUE mesg; - - va_start(args, fmt); - mesg = rb_enc_vsprintf(enc, fmt, args); - va_end(args); - - rb_exc_raise(rb_exc_new3(exc, mesg)); -} -# define rb_enc_raise enc_raise -# endif -#else -# define EXC_ENCODING /* nothing */ -# define rb_enc_raise rb_raise -#endif - /* unicode */ static const signed char digit_values[256] = { @@ -555,7 +533,7 @@ cs = 0; {p = (( p + 10))-1;} p--; {p++; cs = 29; goto _out;} } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); } } np = JSON_parse_float(json, p, pe, result); @@ -587,7 +565,7 @@ cs = 0; if (json->allow_nan) { *result = CInfinity; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p - 7); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p - 7); } } goto st29; @@ -597,7 +575,7 @@ cs = 0; if (json->allow_nan) { *result = CNaN; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p - 2); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p - 2); } } goto st29; @@ -1436,7 +1414,7 @@ case 16: if(cs >= JSON_array_first_final) { return p + 1; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); return NULL; } } @@ -1500,7 +1478,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, int intern, int ruby_xfree(bufferStart); } rb_enc_raise( - EXC_ENCODING eParserError, + rb_utf8_encoding(), eParserError, "incomplete unicode character escape sequence at '%s'", p ); } else { @@ -1513,7 +1491,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, int intern, int ruby_xfree(bufferStart); } rb_enc_raise( - EXC_ENCODING eParserError, + rb_utf8_encoding(), eParserError, "incomplete surrogate pair at '%s'", p ); } @@ -1777,7 +1755,6 @@ case 7: static VALUE convert_encoding(VALUE source) { -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *enc = rb_enc_get(source); if (enc == rb_ascii8bit_encoding()) { if (OBJ_FROZEN(source)) { @@ -1787,8 +1764,7 @@ static VALUE convert_encoding(VALUE source) } else { source = rb_str_conv_enc(source, rb_enc_get(source), rb_utf8_encoding()); } -#endif - return source; + return source; } /* @@ -2089,7 +2065,7 @@ case 9: if (cs >= JSON_first_final && p == pe) { return result; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); return Qnil; } } diff --git a/ext/json/parser/parser.rl b/ext/json/parser/parser.rl index e2522918dc6728..873c1b30077049 100644 --- a/ext/json/parser/parser.rl +++ b/ext/json/parser/parser.rl @@ -1,28 +1,6 @@ #include "../fbuffer/fbuffer.h" #include "parser.h" -#if defined HAVE_RUBY_ENCODING_H -# define EXC_ENCODING rb_utf8_encoding(), -# ifndef HAVE_RB_ENC_RAISE -static void -enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) -{ - va_list args; - VALUE mesg; - - va_start(args, fmt); - mesg = rb_enc_vsprintf(enc, fmt, args); - va_end(args); - - rb_exc_raise(rb_exc_new3(exc, mesg)); -} -# define rb_enc_raise enc_raise -# endif -#else -# define EXC_ENCODING /* nothing */ -# define rb_enc_raise rb_raise -#endif - /* unicode */ static const signed char digit_values[256] = { @@ -222,14 +200,14 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu if (json->allow_nan) { *result = CNaN; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p - 2); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p - 2); } } action parse_infinity { if (json->allow_nan) { *result = CInfinity; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p - 7); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p - 7); } } action parse_string { @@ -245,7 +223,7 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu fexec p + 10; fhold; fbreak; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); } } np = JSON_parse_float(json, fpc, pe, result); @@ -447,7 +425,7 @@ static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *resul if(cs >= JSON_array_first_final) { return p + 1; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); return NULL; } } @@ -511,7 +489,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, int intern, int ruby_xfree(bufferStart); } rb_enc_raise( - EXC_ENCODING eParserError, + rb_utf8_encoding(), eParserError, "incomplete unicode character escape sequence at '%s'", p ); } else { @@ -524,7 +502,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, int intern, int ruby_xfree(bufferStart); } rb_enc_raise( - EXC_ENCODING eParserError, + rb_utf8_encoding(), eParserError, "incomplete surrogate pair at '%s'", p ); } @@ -849,7 +827,7 @@ static VALUE cParser_parse(VALUE self) if (cs >= JSON_first_final && p == pe) { return result; } else { - rb_enc_raise(EXC_ENCODING eParserError, "unexpected token at '%s'", p); + rb_enc_raise(rb_utf8_encoding(), eParserError, "unexpected token at '%s'", p); return Qnil; } } diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 74515901e0ecd9..07e970f2957e7e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -13,7 +13,7 @@ **********************************************************************/ static const char *const -STRINGIO_VERSION = "3.1.2"; +STRINGIO_VERSION = "3.1.2.dev"; #include diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index fad35925a82851..606c44bc965923 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.1" +#define STRSCAN_VERSION "3.1.1.dev" /* ======================================================================= Data Type Definitions diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index aad9f8d28a4374..50d8dd40c6580c 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 "3.1.0" +#define RUBY_ZLIB_VERSION "3.1.1" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) diff --git a/gc.c b/gc.c index f8de3340e8e709..9df7270b9a4691 100644 --- a/gc.c +++ b/gc.c @@ -2193,54 +2193,69 @@ ruby_stack_check(void) /* ==================== Marking ==================== */ +#define RB_GC_MARK_OR_TRAVERSE(func, obj_or_ptr, obj, check_obj) do { \ + if (!RB_SPECIAL_CONST_P(obj)) { \ + rb_vm_t *vm = GET_VM(); \ + rb_ractor_t *cr = GET_RACTOR(); \ + void *objspace = cr->local_objspace; \ + if (LIKELY(cr->local_gate->mark_func_data == NULL)) { \ + (func)(bjspace, (obj_or_ptr)); \ + } \ + else if (check_obj ? \ + rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj) && \ + !rb_gc_impl_garbage_object_p(objspace, obj) : \ + true) { \ + if (GET_OBJSPACE_OF_VALUE(obj) == objspace || FL_TEST(obj, FL_SHAREABLE)) { /* TODO: Remove condition when shareability rules are fully applied */ \ + GC_ASSERT(!rb_gc_impl_during_gc_p(objspace)); \ + struct gc_mark_func_data_struct *mark_func_data = cr->local_gate->mark_func_data; \ + cr->local_gate->mark_func_data = NULL; \ + mark_func_data->mark_func((obj), mark_func_data->data); \ + cr->local_gate->mark_func_data = mark_func_data; \ + } \ + } \ + } \ +} while (0) + static inline void -gc_mark_internal(void *objspace, VALUE obj) +gc_mark_internal(VALUE obj) { - if (RB_SPECIAL_CONST_P(obj)) return; - - rb_gc_impl_mark(objspace, obj); + RB_GC_MARK_OR_TRAVERSE(rb_gc_impl_mark, obj, obj, false); } void rb_gc_mark_movable(VALUE obj) { - gc_mark_internal(rb_gc_get_objspace(), obj); + gc_mark_internal(obj); } void rb_gc_mark_and_move(VALUE *ptr) { - if (SPECIAL_CONST_P(*ptr)) return; - - rb_gc_impl_mark_and_move(rb_gc_get_objspace(), ptr); + RB_GC_MARK_OR_TRAVERSE(rb_gc_impl_mark_and_move, ptr, *ptr, false); } static inline void -gc_mark_and_pin_internal(void *objspace, VALUE obj) +gc_mark_and_pin_internal(VALUE obj) { - if (RB_SPECIAL_CONST_P(obj)) return; - - rb_gc_impl_mark_and_pin(objspace, obj); + RB_GC_MARK_OR_TRAVERSE(rb_gc_impl_mark_and_pin, obj, obj, false); } void rb_gc_mark(VALUE obj) { - gc_mark_and_pin_internal(rb_gc_get_objspace(), obj); + gc_mark_and_pin_internal(obj); } static inline void -gc_mark_maybe_internal(void *objspace, VALUE obj) +gc_mark_maybe_internal(VALUE obj) { - if (RB_SPECIAL_CONST_P(obj)) return; - - rb_gc_impl_mark_maybe(objspace, obj); + RB_GC_MARK_OR_TRAVERSE(rb_gc_impl_mark_maybe, obj, obj, true); } void rb_gc_mark_maybe(VALUE obj) { - gc_mark_maybe_internal(rb_gc_get_objspace(), obj); + gc_mark_maybe_internal(obj); } static inline void @@ -2263,36 +2278,42 @@ rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr) rb_gc_impl_remove_weak(rb_gc_get_objspace(), parent_obj, ptr); } -ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(static void each_location(register const VALUE *x, register long n, void (*cb)(void *data, VALUE), void *data)); +ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(static void each_location(register const VALUE *x, register long n, void (*cb)(VALUE, void *), void *data)); static void -each_location(register const VALUE *x, register long n, void (*cb)(void *data, VALUE obj), void *data) +each_location(register const VALUE *x, register long n, void (*cb)(VALUE, void *), void *data) { VALUE v; while (n--) { v = *x; - cb(data, v); + cb(v, data); x++; } } static void -each_location_ptr(const VALUE *start, const VALUE *end, void (*cb)(void *, VALUE), void *data) +each_location_ptr(const VALUE *start, const VALUE *end, void (*cb)(VALUE, void *), void *data) { if (end <= start) return; each_location(start, end - start, cb, data); } +static void +gc_mark_maybe_each_location(VALUE obj, void *data) +{ + gc_mark_maybe_internal(obj); +} + void rb_gc_mark_locations(const VALUE *start, const VALUE *end) { - each_location_ptr(start, end, rb_gc_impl_stack_location_mark_maybe, rb_gc_get_objspace()); + each_location_ptr(start, end, rb_gc_impl_stack_location_mark_maybe, NULL); } void rb_gc_mark_values(long n, const VALUE *values) { for (long i = 0; i < n; i++) { - gc_mark_internal(rb_gc_get_objspace(), values[i]); + gc_mark_internal(values[i]); } } @@ -2300,16 +2321,14 @@ void rb_gc_mark_vm_stack_values(long n, const VALUE *values) { for (long i = 0; i < n; i++) { - gc_mark_and_pin_internal(rb_gc_get_objspace(), values[i]); + gc_mark_and_pin_internal(values[i]); } } static int mark_key(st_data_t key, st_data_t value, st_data_t data) { - void *objspace = (void *)data; - - gc_mark_and_pin_internal(objspace, (VALUE)key); + gc_mark_and_pin_internal((VALUE)key); return ST_CONTINUE; } @@ -2325,10 +2344,8 @@ rb_mark_set(st_table *tbl) static int mark_keyvalue(st_data_t key, st_data_t value, st_data_t data) { - void *objspace = (void *)data; - - gc_mark_internal(objspace, (VALUE)key); - gc_mark_internal(objspace, (VALUE)value); + gc_mark_internal((VALUE)key); + gc_mark_internal((VALUE)value); return ST_CONTINUE; } @@ -2336,10 +2353,8 @@ mark_keyvalue(st_data_t key, st_data_t value, st_data_t data) static int pin_key_pin_value(st_data_t key, st_data_t value, st_data_t data) { - void *objspace = (void *)data; - - gc_mark_and_pin_internal(objspace, (VALUE)key); - gc_mark_and_pin_internal(objspace, (VALUE)value); + gc_mark_and_pin_internal((VALUE)key); + gc_mark_and_pin_internal((VALUE)value); return ST_CONTINUE; } @@ -2347,25 +2362,23 @@ pin_key_pin_value(st_data_t key, st_data_t value, st_data_t data) static int pin_key_mark_value(st_data_t key, st_data_t value, st_data_t data) { - void *objspace = (void *)data; - - gc_mark_and_pin_internal(objspace, (VALUE)key); - gc_mark_internal(objspace, (VALUE)value); + gc_mark_and_pin_internal((VALUE)key); + gc_mark_internal((VALUE)value); return ST_CONTINUE; } static void -mark_hash(void *objspace, VALUE hash) +mark_hash(VALUE hash) { if (rb_hash_compare_by_id_p(hash)) { - rb_hash_stlike_foreach(hash, pin_key_mark_value, (st_data_t)objspace); + rb_hash_stlike_foreach(hash, pin_key_mark_value, 0); } else { - rb_hash_stlike_foreach(hash, mark_keyvalue, (st_data_t)objspace); + rb_hash_stlike_foreach(hash, mark_keyvalue, 0); } - gc_mark_internal(objspace, RHASH(hash)->ifnone); + gc_mark_internal(RHASH(hash)->ifnone); } void @@ -2373,13 +2386,13 @@ rb_mark_hash(st_table *tbl) { if (!tbl) return; - st_foreach(tbl, pin_key_pin_value, (st_data_t)rb_gc_get_objspace()); + st_foreach(tbl, pin_key_pin_value, 0); } static enum rb_id_table_iterator_result mark_method_entry_i(VALUE me, void *objspace) { - gc_mark_internal(objspace, me); + gc_mark_internal(me); return ID_TABLE_CONTINUE; } @@ -2402,22 +2415,15 @@ mark_m_tbl(void *objspace, struct rb_id_table *tbl) ((start) = STACK_END, (end) = STACK_START) : ((start) = STACK_START, (end) = STACK_END+(appendix))) #endif -struct mark_machine_stack_location_maybe_data { - void *objspace; -#ifdef RUBY_ASAN_ENABLED - const rb_execution_context_t *ec; -#endif -}; - static void -gc_mark_machine_stack_location_maybe(void *data, VALUE obj) +gc_mark_machine_stack_location_maybe(VALUE obj, void *data) { void *objspace = ((struct mark_machine_stack_location_maybe_data *)data)->objspace; - gc_stack_location_mark_maybe_internal(objspace, obj); + gc_stack_location_mark_maybe_internal(obj); #ifdef RUBY_ASAN_ENABLED - const rb_execution_context_t *ec = ((struct mark_machine_stack_location_maybe_data *)data)->ec; + const rb_execution_context_t *ec = (const rb_execution_context_t *)data; void *fake_frame_start; void *fake_frame_end; bool is_fake_frame = asan_get_fake_stack_extents( @@ -2426,7 +2432,7 @@ gc_mark_machine_stack_location_maybe(void *data, VALUE obj) &fake_frame_start, &fake_frame_end ); if (is_fake_frame) { - each_location_ptr(fake_frame_start, fake_frame_end, rb_gc_impl_stack_location_mark_maybe, objspace); + each_location_ptr(fake_frame_start, fake_frame_end, rb_gc_impl_stack_location_mark_maybe, NULL); } #endif } @@ -2446,26 +2452,26 @@ rb_mark_locations(void *begin, void *end) # if defined(__EMSCRIPTEN__) static void -mark_current_machine_context(void *objspace, rb_execution_context_t *ec) +mark_current_machine_context(rb_execution_context_t *ec) { emscripten_scan_stack(rb_mark_locations); - each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, objspace); + each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, NULL); emscripten_scan_registers(rb_mark_locations); - each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, objspace); + each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, NULL); } # else // use Asyncify version static void -mark_current_machine_context(void *objspace, rb_execution_context_t *ec) +mark_current_machine_context(rb_execution_context_t *ec) { VALUE *stack_start, *stack_end; SET_STACK_END; GET_STACK_BOUNDS(stack_start, stack_end, 1); - each_location_ptr(stack_start, stack_end, rb_gc_impl_stack_location_mark_maybe, objspace); + each_location_ptr(stack_start, stack_end, rb_gc_impl_stack_location_mark_maybe, NULL); rb_wasm_scan_locals(rb_mark_locations); - each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, objspace); + each_location_ptr(rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_stack_location_mark_maybe, NULL); } # endif @@ -2473,7 +2479,7 @@ mark_current_machine_context(void *objspace, rb_execution_context_t *ec) #else // !defined(__wasm__) static void -mark_current_machine_context(void *objspace, rb_execution_context_t *ec) +mark_current_machine_context(rb_execution_context_t *ec) { union { rb_jmp_buf j; @@ -2492,15 +2498,15 @@ mark_current_machine_context(void *objspace, rb_execution_context_t *ec) SET_STACK_END; GET_STACK_BOUNDS(stack_start, stack_end, 1); - struct mark_machine_stack_location_maybe_data data = { - .objspace = objspace, + void *data = #ifdef RUBY_ASAN_ENABLED - .ec = ec + ec; +#else + NULL; #endif - }; - each_location(save_regs_gc_mark.v, numberof(save_regs_gc_mark.v), gc_mark_machine_stack_location_maybe, &data); - each_location_ptr(stack_start, stack_end, gc_mark_machine_stack_location_maybe, &data); + each_location(save_regs_gc_mark.v, numberof(save_regs_gc_mark.v), gc_mark_machine_stack_location_maybe, data); + each_location_ptr(stack_start, stack_end, gc_mark_machine_stack_location_maybe, data); } #endif @@ -2512,24 +2518,22 @@ rb_gc_mark_machine_context(const rb_execution_context_t *ec) GET_STACK_BOUNDS(stack_start, stack_end, 0); RUBY_DEBUG_LOG("ec->th:%u stack_start:%p stack_end:%p", rb_ec_thread_ptr(ec)->serial, stack_start, stack_end); - struct mark_machine_stack_location_maybe_data data = { - .objspace = rb_gc_get_objspace(), + void *data = #ifdef RUBY_ASAN_ENABLED - .ec = ec + ec; +#else + NULL; #endif - }; - each_location_ptr(stack_start, stack_end, gc_mark_machine_stack_location_maybe, &data); + each_location_ptr(stack_start, stack_end, gc_mark_machine_stack_location_maybe, data); int num_regs = sizeof(ec->machine.regs)/(sizeof(VALUE)); - each_location((VALUE*)&ec->machine.regs, num_regs, gc_mark_machine_stack_location_maybe, &data); + each_location((VALUE*)&ec->machine.regs, num_regs, gc_mark_machine_stack_location_maybe, data); } static int rb_mark_tbl_i(st_data_t key, st_data_t value, st_data_t data) { - void *objspace = (void *)data; - - gc_mark_and_pin_internal(objspace, (VALUE)value); + gc_mark_and_pin_internal((VALUE)value); return ST_CONTINUE; } @@ -2539,21 +2543,21 @@ rb_mark_tbl(st_table *tbl) { if (!tbl || tbl->num_entries == 0) return; - st_foreach(tbl, rb_mark_tbl_i, (st_data_t)rb_gc_get_objspace()); + st_foreach(tbl, rb_mark_tbl_i, 0); } static void -gc_mark_tbl_no_pin(void *objspace, st_table *tbl) +gc_mark_tbl_no_pin(st_table *tbl) { if (!tbl || tbl->num_entries == 0) return; - st_foreach(tbl, gc_mark_tbl_no_pin_i, (st_data_t)objspace); + st_foreach(tbl, gc_mark_tbl_no_pin_i, 0); } void rb_mark_tbl_no_pin(st_table *tbl) { - gc_mark_tbl_no_pin(rb_gc_get_objspace(), tbl); + gc_mark_tbl_no_pin(tbl); } static enum rb_id_table_iterator_result @@ -2564,7 +2568,7 @@ mark_cvc_tbl_i(VALUE cvc_entry, void *objspace) 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))); - gc_mark_internal(objspace, (VALUE)entry->cref); + gc_mark_internal((VALUE)entry->cref); return ID_TABLE_CONTINUE; } @@ -2589,8 +2593,8 @@ mark_const_table_i(VALUE value, void *objspace) { const rb_const_entry_t *ce = (const rb_const_entry_t *)value; - gc_mark_internal(objspace, ce->value); - gc_mark_internal(objspace, ce->file); + gc_mark_internal(ce->value); + gc_mark_internal(ce->file); return ID_TABLE_CONTINUE; } @@ -2610,7 +2614,7 @@ rb_gc_mark_roots(void *objspace, const char **categoryp) MARK_CHECKPOINT("vm"); if (objspace == vm->objspace) { rb_vm_mark(vm); - if (vm->self) gc_mark_internal(objspace, vm->self); + if (vm->self) gc_mark_internal(vm->self); } MARK_CHECKPOINT("ractor"); @@ -2618,7 +2622,7 @@ rb_gc_mark_roots(void *objspace, const char **categoryp) rb_gc_mark(local_gate->self); MARK_CHECKPOINT("machine_context"); - mark_current_machine_context(objspace, ec); + mark_current_machine_context(ec); MARK_CHECKPOINT("end_proc"); rb_mark_end_proc(objspace); @@ -2683,28 +2687,28 @@ rb_gc_mark_children(void *objspace, VALUE obj) break; } - gc_mark_internal(objspace, RBASIC(obj)->klass); + gc_mark_internal(RBASIC(obj)->klass); switch (BUILTIN_TYPE(obj)) { case T_CLASS: if (FL_TEST(obj, FL_SINGLETON)) { - gc_mark_internal(objspace, RCLASS_ATTACHED_OBJECT(obj)); + gc_mark_internal(RCLASS_ATTACHED_OBJECT(obj)); } // Continue to the shared T_CLASS/T_MODULE case T_MODULE: if (RCLASS_SUPER(obj)) { - gc_mark_internal(objspace, RCLASS_SUPER(obj)); + gc_mark_internal(RCLASS_SUPER(obj)); } mark_m_tbl(objspace, RCLASS_M_TBL(obj)); mark_cvc_tbl(objspace, obj); rb_cc_table_mark(obj); if (rb_shape_obj_too_complex(obj)) { - gc_mark_tbl_no_pin(objspace, (st_table *)RCLASS_IVPTR(obj)); + gc_mark_tbl_no_pin((st_table *)RCLASS_IVPTR(obj)); } else { for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { - gc_mark_internal(objspace, RCLASS_IVPTR(obj)[i]); + gc_mark_internal(RCLASS_IVPTR(obj)[i]); } } @@ -2715,7 +2719,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) } rb_native_mutex_lock(&vm->classpath_lock); - gc_mark_internal(objspace, RCLASS_EXT(obj)->classpath); + gc_mark_internal(RCLASS_EXT(obj)->classpath); rb_native_mutex_unlock(&vm->classpath_lock); break; @@ -2724,11 +2728,11 @@ rb_gc_mark_children(void *objspace, VALUE obj) mark_m_tbl(objspace, RCLASS_M_TBL(obj)); } if (RCLASS_SUPER(obj)) { - gc_mark_internal(objspace, RCLASS_SUPER(obj)); + gc_mark_internal(RCLASS_SUPER(obj)); } if (RCLASS_INCLUDER(obj)) { - gc_mark_internal(objspace, RCLASS_INCLUDER(obj)); + gc_mark_internal(RCLASS_INCLUDER(obj)); } mark_m_tbl(objspace, RCLASS_CALLABLE_M_TBL(obj)); rb_cc_table_mark(obj); @@ -2737,19 +2741,19 @@ rb_gc_mark_children(void *objspace, VALUE obj) case T_ARRAY: if (ARY_SHARED_P(obj)) { VALUE root = ARY_SHARED_ROOT(obj); - gc_mark_internal(objspace, root); + gc_mark_internal(root); } else { long len = RARRAY_LEN(obj); const VALUE *ptr = RARRAY_CONST_PTR(obj); for (long i = 0; i < len; i++) { - gc_mark_internal(objspace, ptr[i]); + gc_mark_internal(ptr[i]); } } break; case T_HASH: - mark_hash(objspace, obj); + mark_hash(obj); break; case T_STRING: @@ -2759,10 +2763,10 @@ rb_gc_mark_children(void *objspace, VALUE obj) * points into the slot of the shared string. There may be code * using the RSTRING_PTR on the stack, which would pin this * string but not pin the shared string, causing it to move. */ - gc_mark_and_pin_internal(objspace, RSTRING(obj)->as.heap.aux.shared); + gc_mark_and_pin_internal(RSTRING(obj)->as.heap.aux.shared); } else { - gc_mark_internal(objspace, RSTRING(obj)->as.heap.aux.shared); + gc_mark_internal(RSTRING(obj)->as.heap.aux.shared); } } break; @@ -2775,7 +2779,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) size_t *offset_list = (size_t *)RTYPEDDATA(obj)->type->function.dmark; for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) { - gc_mark_internal(objspace, *(VALUE *)((char *)ptr + offset)); + gc_mark_internal(*(VALUE *)((char *)ptr + offset)); } } else { @@ -2793,14 +2797,14 @@ rb_gc_mark_children(void *objspace, VALUE obj) rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj)); if (rb_shape_obj_too_complex(obj)) { - gc_mark_tbl_no_pin(objspace, ROBJECT_IV_HASH(obj)); + gc_mark_tbl_no_pin(ROBJECT_IV_HASH(obj)); } else { const VALUE * const ptr = ROBJECT_IVPTR(obj); uint32_t len = ROBJECT_IV_COUNT(obj); for (uint32_t i = 0; i < len; i++) { - gc_mark_internal(objspace, ptr[i]); + gc_mark_internal(ptr[i]); } } @@ -2819,36 +2823,36 @@ rb_gc_mark_children(void *objspace, VALUE obj) case T_FILE: if (RFILE(obj)->fptr) { - gc_mark_internal(objspace, RFILE(obj)->fptr->self); - gc_mark_internal(objspace, RFILE(obj)->fptr->pathv); - gc_mark_internal(objspace, RFILE(obj)->fptr->tied_io_for_writing); - gc_mark_internal(objspace, RFILE(obj)->fptr->writeconv_asciicompat); - gc_mark_internal(objspace, RFILE(obj)->fptr->writeconv_pre_ecopts); - gc_mark_internal(objspace, RFILE(obj)->fptr->encs.ecopts); - gc_mark_internal(objspace, RFILE(obj)->fptr->write_lock); - gc_mark_internal(objspace, RFILE(obj)->fptr->timeout); + gc_mark_internal(RFILE(obj)->fptr->self); + gc_mark_internal(RFILE(obj)->fptr->pathv); + gc_mark_internal(RFILE(obj)->fptr->tied_io_for_writing); + gc_mark_internal(RFILE(obj)->fptr->writeconv_asciicompat); + gc_mark_internal(RFILE(obj)->fptr->writeconv_pre_ecopts); + gc_mark_internal(RFILE(obj)->fptr->encs.ecopts); + gc_mark_internal(RFILE(obj)->fptr->write_lock); + gc_mark_internal(RFILE(obj)->fptr->timeout); } break; case T_REGEXP: - gc_mark_internal(objspace, RREGEXP(obj)->src); + gc_mark_internal(RREGEXP(obj)->src); break; case T_MATCH: - gc_mark_internal(objspace, RMATCH(obj)->regexp); + gc_mark_internal(RMATCH(obj)->regexp); if (RMATCH(obj)->str) { - gc_mark_internal(objspace, RMATCH(obj)->str); + gc_mark_internal(RMATCH(obj)->str); } break; case T_RATIONAL: - gc_mark_internal(objspace, RRATIONAL(obj)->num); - gc_mark_internal(objspace, RRATIONAL(obj)->den); + gc_mark_internal(RRATIONAL(obj)->num); + gc_mark_internal(RRATIONAL(obj)->den); break; case T_COMPLEX: - gc_mark_internal(objspace, RCOMPLEX(obj)->real); - gc_mark_internal(objspace, RCOMPLEX(obj)->imag); + gc_mark_internal(RCOMPLEX(obj)->real); + gc_mark_internal(RCOMPLEX(obj)->imag); break; case T_STRUCT: { @@ -2856,7 +2860,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) const VALUE * const ptr = RSTRUCT_CONST_PTR(obj); for (long i = 0; i < len; i++) { - gc_mark_internal(objspace, ptr[i]); + gc_mark_internal(ptr[i]); } break; @@ -3802,13 +3806,6 @@ ruby_gc_set_params(void) rb_gc_impl_set_params(rb_gc_get_objspace()); } -void -rb_gc_reachable_objects_from_callback(VALUE obj) -{ - rb_ractor_t *cr = GET_RACTOR(); - MARK_FUNC_RUN(cr, obj); -} - void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data) { @@ -3848,7 +3845,8 @@ rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, { if (rb_gc_impl_during_gc_p(rb_gc_get_objspace())) rb_bug("rb_gc_impl_objspace_reachable_objects_from_root() is not supported while during GC"); - rb_ractor_t *cr = GET_RACTOR(); + rb_vm_t *vm = GET_VM(); + struct root_objects_data data = { .func = func, .data = passing_data, diff --git a/gc/default.c b/gc/default.c index 12e8ce84e50efa..1a33c6b25cdc02 100644 --- a/gc/default.c +++ b/gc/default.c @@ -522,7 +522,6 @@ typedef struct rb_objspace { struct { struct heap_page **sorted; size_t allocated_pages; - size_t allocatable_pages; size_t sorted_length; uintptr_t range[2]; size_t freeable_pages; @@ -1764,13 +1763,15 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr) { rb_objspace_t *objspace = objspace_ptr; - switch (BUILTIN_TYPE(ptr)) { - case T_NONE: - case T_MOVED: - case T_ZOMBIE: - return true; - default: - break; + asan_unpoisoning_object(ptr) { + switch (BUILTIN_TYPE(ptr)) { + case T_NONE: + case T_MOVED: + case T_ZOMBIE: + return true; + default: + break; + } } return is_lazy_sweeping(objspace) && GET_HEAP_PAGE(ptr)->flags.before_sweep && @@ -5252,32 +5253,25 @@ gc_mark(rb_objspace_t *objspace, VALUE obj) { VM_ASSERT(GET_OBJSPACE_OF_VALUE(obj) == objspace || FL_TEST(obj, FL_SHAREABLE) || !using_local_limits(objspace)); - if (RB_LIKELY(during_gc)) { - if (!confirm_global_connections(objspace, obj)) return; - rgengc_check_relation(objspace, obj); - if (!gc_mark_set(objspace, obj)) return; /* already marked */ - - if (0) { // for debug GC marking miss - if (objspace->rgengc.parent_object) { - RUBY_DEBUG_LOG("%p (%s) parent:%p (%s)", - (void *)obj, obj_type_name(obj), - (void *)objspace->rgengc.parent_object, obj_type_name(objspace->rgengc.parent_object)); - } - else { - RUBY_DEBUG_LOG("%p (%s)", (void *)obj, obj_type_name(obj)); - } - } + GC_ASSERT(during_gc); + if (!confirm_global_connections(objspace, obj)) return; + rgengc_check_relation(objspace, obj); + if (!gc_mark_set(objspace, obj)) return; /* already marked */ - check_not_tnone(obj); - gc_aging(obj); - gc_grey(objspace, obj); - } - else { - VM_ASSERT(dont_gc_val() == TRUE); - if (GET_OBJSPACE_OF_VALUE(obj) == objspace || FL_TEST(obj, FL_SHAREABLE)) { //TODO: Remove condition when shareability rules are fully applied - rb_gc_reachable_objects_from_callback(obj); + if (0) { // for debug GC marking miss + if (objspace->rgengc.parent_object) { + RUBY_DEBUG_LOG("%p (%s) parent:%p (%s)", + (void *)obj, obj_type_name(obj), + (void *)objspace->rgengc.parent_object, obj_type_name(objspace->rgengc.parent_object)); + } + else { + RUBY_DEBUG_LOG("%p (%s)", (void *)obj, obj_type_name(obj)); } } + + check_not_tnone(obj); + gc_aging(obj); + gc_grey(objspace, obj); } static inline void diff --git a/gc/gc.h b/gc/gc.h index 2ee96365f2ebc2..f0c8d8bbdd0de5 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -23,7 +23,6 @@ size_t rb_gc_obj_optimal_size(VALUE obj); void rb_gc_mark_children(void *objspace, VALUE obj); void rb_gc_update_object_references(void *objspace, VALUE obj); void rb_gc_update_vm_references(void *objspace); -void rb_gc_reachable_objects_from_callback(VALUE obj); void rb_gc_event_hook(VALUE obj, rb_event_flag_t event); void *rb_gc_get_objspace(void); size_t rb_size_mul_or_raise(size_t x, size_t y, VALUE exc); diff --git a/gems/bundled_gems b/gems/bundled_gems index 4e8ca298a0aff3..0e82d9e2fd5a39 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -10,7 +10,7 @@ minitest 5.25.1 https://github.com/minitest/minitest power_assert 2.0.3 https://github.com/ruby/power_assert 84e85124c5014a139af39161d484156cfe87a9ed rake 13.2.1 https://github.com/ruby/rake test-unit 3.6.2 https://github.com/test-unit/test-unit -rexml 3.3.6 https://github.com/ruby/rexml +rexml 3.3.7 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.7 https://github.com/ruby/net-ftp net-imap 0.4.15 https://github.com/ruby/net-imap diff --git a/hash.c b/hash.c index 6e3a97162a8364..4944d06aa34e19 100644 --- a/hash.c +++ b/hash.c @@ -7020,10 +7020,9 @@ static const rb_data_type_t env_data_type = { * - #empty?: Returns whether there are no entries. * - #eql?: Returns whether a given object is equal to +self+. * - #hash: Returns the integer hash code. - * - #has_value?: Returns whether a given object is a value in +self+. - * - #include?, #has_key?, #member?, #key?: Returns whether a given object is a key in +self+. - * - #length, #size: Returns the count of entries. - * - #value?: Returns whether a given object is a value in +self+. + * - #has_value? (aliased as #value?): Returns whether a given object is a value in +self+. + * - #include? (aliased as #has_key?, #member?, #key?): Returns whether a given object is a key in +self+. + * - #size (aliased as #length): Returns the count of entries. * * ==== Methods for Comparing * @@ -7050,10 +7049,10 @@ static const rb_data_type_t env_data_type = { * * ==== Methods for Assigning * - * - #[]=, #store: Associates a given key with a given value. + * - #[]= (aliased as #store): Associates a given key with a given value. * - #merge: Returns the hash formed by merging each given hash into a copy of +self+. - * - #merge!, #update: Merges each given hash into +self+. - * - #replace: Replaces the entire contents of +self+ with the contents of a given hash. + * - #update (aliased as #merge!): Merges each given hash into +self+. + * - #replace (aliased as #initialize_copy): Replaces the entire contents of +self+ with the contents of a given hash. * * ==== Methods for Deleting * @@ -7063,7 +7062,7 @@ static const rb_data_type_t env_data_type = { * - #compact!: Removes all +nil+-valued entries from +self+. * - #delete: Removes the entry for a given key. * - #delete_if: Removes entries selected by a given block. - * - #filter!, #select!: Keep only those entries selected by a given block. + * - #select! (aliased as #filter!): Keep only those entries selected by a given block. * - #keep_if: Keep only those entries selected by a given block. * - #reject!: Removes entries selected by a given block. * - #shift: Removes and returns the first entry. @@ -7072,18 +7071,18 @@ static const rb_data_type_t env_data_type = { * * - #compact: Returns a copy of +self+ with all +nil+-valued entries removed. * - #except: Returns a copy of +self+ with entries removed for specified keys. - * - #filter, #select: Returns a copy of +self+ with only those entries selected by a given block. + * - #select (aliased as #filter): Returns a copy of +self+ with only those entries selected by a given block. * - #reject: Returns a copy of +self+ with entries removed as specified by a given block. * - #slice: Returns a hash containing the entries for given keys. * * ==== Methods for Iterating - * - #each, #each_pair: Calls a given block with each key-value pair. + * - #each_pair (aliased as #each): Calls a given block with each key-value pair. * - #each_key: Calls a given block with each key. * - #each_value: Calls a given block with each value. * * ==== Methods for Converting * - * - #inspect, #to_s: Returns a new String containing the hash entries. + * - #inspect (aliased as #to_s): Returns a new String containing the hash entries. * - #to_a: Returns a new array of 2-element arrays; * each nested array contains a key-value pair from +self+. * - #to_h: Returns +self+ if a +Hash+; diff --git a/io.c b/io.c index 54b60b7357e5c0..590e8d11d74093 100644 --- a/io.c +++ b/io.c @@ -6956,7 +6956,10 @@ sysopen_func(void *ptr) static inline int rb_sysopen_internal(struct sysopen_struct *data) { - int fd = IO_WITHOUT_GVL_INT(sysopen_func, data); + int fd; + do { + fd = IO_WITHOUT_GVL_INT(sysopen_func, data); + } while (fd < 0 && errno == EINTR); if (0 <= fd) rb_update_max_fd(fd); return fd; diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index c29b1bfed89122..35b5a55038e489 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -217,15 +217,15 @@ def initialize(orig_exception, msg) end class InsecureInstallPathError < BundlerError - def initialize(path) + def initialize(name, path) + @name = name @path = path end def message - "The installation path is insecure. Bundler cannot continue.\n" \ - "#{@path} is world-writable (without sticky bit).\n" \ - "Bundler cannot safely replace gems in world-writeable directories due to potential vulnerabilities.\n" \ - "Please change the permissions of this directory or choose a different install path." + "Bundler cannot reinstall #{@name} because there's a previous installation of it at #{@path} that is unsafe to remove.\n" \ + "The parent of #{@path} is world-writable and does not have the sticky bit set, making it insecure to remove due to potential vulnerabilities.\n" \ + "Please change the permissions of #{File.dirname(@path)} or choose a different install path." end status_code(38) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 6288b22dcd0636..14721623f9ded6 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -3,7 +3,7 @@ require_relative "vendored_persistent" require_relative "vendored_timeout" require "cgi" -require "securerandom" +require_relative "vendored_securerandom" require "zlib" module Bundler @@ -182,7 +182,7 @@ def user_agent agent << " ci/#{cis.join(",")}" if cis.any? # add a random ID so we can consolidate runs server-side - agent << " " << SecureRandom.hex(8) + agent << " " << Gem::SecureRandom.hex(8) # add any user agent strings set in the config extra_ua = Bundler.settings[:user_agent] diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index ca82383c321542..dae05bd9458701 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,24 +1,12 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-ADD" "1" "August 2024" "" +.TH "BUNDLE\-ADD" "1" "September 2024" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" -\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT] [\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-skip\-install] [\-\-strict] [\-\-optimistic] +\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-skip\-install] [\-\-strict|\-\-optimistic] .SH "DESCRIPTION" -Adds the named gem to the Gemfile and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\. -.P -Example: -.P -bundle add rails -.P -bundle add rails \-\-version "< 3\.0, > 1\.1" -.P -bundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development" -.P -bundle add rails \-\-skip\-install -.P -bundle add rails \-\-group "development, test" +Adds the named gem to the [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\. .SH "OPTIONS" .TP \fB\-\-version\fR, \fB\-v\fR @@ -56,4 +44,27 @@ Adds optimistic declaration of version\. .TP \fB\-\-strict\fR Adds strict declaration of version\. - +.SH "EXAMPLES" +.IP "1." 4 +You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\. +.IP +\fBbundle add rails\fR +.IP "2." 4 +You can add the \fBrails\fR gem with version greater than 1\.1 (not including 1\.1) and less than 3\.0\. +.IP +\fBbundle add rails \-\-version "> 1\.1, < 3\.0"\fR +.IP "3." 4 +You can use the \fBhttps://gems\.example\.com\fR custom source and assign the gem to a group\. +.IP +\fBbundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR +.IP "4." 4 +The following adds the \fBgem\fR entry to the Gemfile without installing the gem\. You can install gems later via \fBbundle install\fR\. +.IP +\fBbundle add rails \-\-skip\-install\fR +.IP "5." 4 +You can assign the gem to more than one group\. +.IP +\fBbundle add rails \-\-group "development, test"\fR +.IP "" 0 +.SH "SEE ALSO" +Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR diff --git a/lib/bundler/man/bundle-add.1.ronn b/lib/bundler/man/bundle-add.1.ronn index 37c92e5fcdc70d..8b38c7a2483047 100644 --- a/lib/bundler/man/bundle-add.1.ronn +++ b/lib/bundler/man/bundle-add.1.ronn @@ -1,26 +1,19 @@ bundle-add(1) -- Add gem to the Gemfile and run bundle install -================================================================ +============================================================== ## SYNOPSIS -`bundle add` [--group=GROUP] [--version=VERSION] [--source=SOURCE] [--path=PATH] [--git=GIT] [--github=GITHUB] [--branch=BRANCH] [--ref=REF] [--skip-install] [--strict] [--optimistic] +`bundle add` [--group=GROUP] [--version=VERSION] [--source=SOURCE] + [--path=PATH] [--git=GIT|--github=GITHUB] [--branch=BRANCH] [--ref=REF] + [--skip-install] [--strict|--optimistic] ## DESCRIPTION -Adds the named gem to the Gemfile and run `bundle install`. `bundle install` can be avoided by using the flag `--skip-install`. -Example: - -bundle add rails - -bundle add rails --version "< 3.0, > 1.1" - -bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development" - -bundle add rails --skip-install - -bundle add rails --group "development, test" +Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`. +`bundle install` can be avoided by using the flag `--skip-install`. ## OPTIONS + * `--version`, `-v`: Specify version requirements(s) for the added gem. @@ -56,3 +49,33 @@ bundle add rails --group "development, test" * `--strict`: Adds strict declaration of version. + +## EXAMPLES + +1. You can add the `rails` gem to the Gemfile without any version restriction. + The source of the gem will be the global source. + + `bundle add rails` + +2. You can add the `rails` gem with version greater than 1.1 (not including 1.1) and less than 3.0. + + `bundle add rails --version "> 1.1, < 3.0"` + +3. You can use the `https://gems.example.com` custom source and assign the gem + to a group. + + `bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development"` + +4. The following adds the `gem` entry to the Gemfile without installing the + gem. You can install gems later via `bundle install`. + + `bundle add rails --skip-install` + +5. You can assign the gem to more than one group. + + `bundle add rails --group "development, test"` + +## SEE ALSO + +[Gemfile(5)](https://bundler.io/man/gemfile.5.html), +[bundle-remove(1)](bundle-remove.1.html) diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index ab47bf97bb2de6..56c9966e75c1ca 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-BINSTUBS" "1" "August 2024" "" +.TH "BUNDLE\-BINSTUBS" "1" "September 2024" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 981b3c791aa219..d634eef203ee37 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CACHE" "1" "August 2024" "" +.TH "BUNDLE\-CACHE" "1" "September 2024" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 5e6f69530c4ce9..e15a41e4fddba4 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CHECK" "1" "August 2024" "" +.TH "BUNDLE\-CHECK" "1" "September 2024" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 751bcfe9ed58e6..aa5ccf7594fffc 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CLEAN" "1" "August 2024" "" +.TH "BUNDLE\-CLEAN" "1" "September 2024" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index a81869114d95f6..47104fb5c65b4d 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONFIG" "1" "August 2024" "" +.TH "BUNDLE\-CONFIG" "1" "September 2024" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index bd998476862df9..f2b2ddaed055bc 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONSOLE" "1" "August 2024" "" +.TH "BUNDLE\-CONSOLE" "1" "September 2024" "" .SH "NAME" \fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index 1d383e2a48f83b..f225d0cd791fbe 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-DOCTOR" "1" "August 2024" "" +.TH "BUNDLE\-DOCTOR" "1" "September 2024" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 0b1a7316012009..e16b7bc7474b56 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-EXEC" "1" "August 2024" "" +.TH "BUNDLE\-EXEC" "1" "September 2024" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index ee3d8e27883907..e6e58cd409382f 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-GEM" "1" "August 2024" "" +.TH "BUNDLE\-GEM" "1" "September 2024" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index cd28a1ec3a06e3..d7a05f824e972f 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-HELP" "1" "August 2024" "" +.TH "BUNDLE\-HELP" "1" "September 2024" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index ace71632826d8a..6b401a57f42bca 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INFO" "1" "August 2024" "" +.TH "BUNDLE\-INFO" "1" "September 2024" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 1a7557f048a224..f2e444c7c283f8 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INIT" "1" "August 2024" "" +.TH "BUNDLE\-INIT" "1" "September 2024" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index 9508d97786080d..8eb3633837cf0c 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INJECT" "1" "August 2024" "" +.TH "BUNDLE\-INJECT" "1" "September 2024" "" .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 5856a255ac10b3..7539d18f81fd9b 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INSTALL" "1" "August 2024" "" +.TH "BUNDLE\-INSTALL" "1" "September 2024" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 081014bba8cae6..5cbb1c3cfe6215 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LIST" "1" "August 2024" "" +.TH "BUNDLE\-LIST" "1" "September 2024" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index dc7ac86bc71d31..5f0d43a9aa9041 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LOCK" "1" "August 2024" "" +.TH "BUNDLE\-LOCK" "1" "September 2024" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 9a3eba09d815c4..fb5ff1fee71cb3 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OPEN" "1" "August 2024" "" +.TH "BUNDLE\-OPEN" "1" "September 2024" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 85865f52c96466..ea3005dd87d829 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OUTDATED" "1" "August 2024" "" +.TH "BUNDLE\-OUTDATED" "1" "September 2024" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 52c657fd99157b..c3058175fc8d75 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLATFORM" "1" "August 2024" "" +.TH "BUNDLE\-PLATFORM" "1" "September 2024" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 07b85c4d1862ab..34437d99739e19 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLUGIN" "1" "August 2024" "" +.TH "BUNDLE\-PLUGIN" "1" "September 2024" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 255585cb7e2a74..103c6f68ae040b 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PRISTINE" "1" "August 2024" "" +.TH "BUNDLE\-PRISTINE" "1" "September 2024" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 082617a29e6317..4a2ed4eb132007 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-REMOVE" "1" "August 2024" "" +.TH "BUNDLE\-REMOVE" "1" "September 2024" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 95335f6b360ac5..dfbb43921855b3 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-SHOW" "1" "August 2024" "" +.TH "BUNDLE\-SHOW" "1" "September 2024" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 355b0b40d6d102..5eb9514f03e82c 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-UPDATE" "1" "August 2024" "" +.TH "BUNDLE\-UPDATE" "1" "September 2024" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 565148b8e7b2ed..a29858181a8ba9 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VERSION" "1" "August 2024" "" +.TH "BUNDLE\-VERSION" "1" "September 2024" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index 34f5abddde401e..9609e098dd85d5 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VIZ" "1" "August 2024" "" +.TH "BUNDLE\-VIZ" "1" "September 2024" "" .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 19a704ca52c77b..d84d788748a348 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE" "1" "August 2024" "" +.TH "BUNDLE" "1" "September 2024" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index d503abc9d17fdb..f24a1c540d904e 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "GEMFILE" "5" "August 2024" "" +.TH "GEMFILE" "5" "September 2024" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 4d4fd20fea6706..858ebf32cf4a8e 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -150,12 +150,13 @@ def prepare_extension_build(extension_dir) def strict_rm_rf(dir) return unless File.exist?(dir) + return if Dir.empty?(dir) parent = File.dirname(dir) parent_st = File.stat(parent) if parent_st.world_writable? && !parent_st.sticky? - raise InsecureInstallPathError.new(parent) + raise InsecureInstallPathError.new(spec.full_name, dir) end begin diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 08a41a59ba66e7..4e94645f6b8ad6 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -392,9 +392,12 @@ def fetch def validate_spec(_spec); end def load_gemspec(file) - stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent) - stub.full_gem_path = Pathname.new(file).dirname.expand_path(root).to_s - StubSpecification.from_stub(stub) + dirname = Pathname.new(file).dirname + SharedHelpers.chdir(dirname.to_s) do + stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent) + stub.full_gem_path = dirname.expand_path(root).to_s + StubSpecification.from_stub(stub) + end end def git_scope diff --git a/lib/bundler/vendor/securerandom/.document b/lib/bundler/vendor/securerandom/.document new file mode 100644 index 00000000000000..0c43bbd6b38177 --- /dev/null +++ b/lib/bundler/vendor/securerandom/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/securerandom/lib/random/formatter.rb b/lib/bundler/vendor/securerandom/lib/random/formatter.rb new file mode 100644 index 00000000000000..e4297097890a34 --- /dev/null +++ b/lib/bundler/vendor/securerandom/lib/random/formatter.rb @@ -0,0 +1,373 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +# == \Random number formatter. +# +# Formats generated random numbers in many manners. When 'random/formatter' +# is required, several methods are added to empty core module Bundler::Random::Formatter, +# making them available as Random's instance and module methods. +# +# Standard library Bundler::SecureRandom is also extended with the module, and the methods +# described below are available as a module methods in it. +# +# === Examples +# +# Generate random hexadecimal strings: +# +# require 'bundler/vendor/securerandom/lib/random/formatter' +# +# prng = Random.new +# prng.hex(10) #=> "52750b30ffbc7de3b362" +# prng.hex(10) #=> "92b15d6c8dc4beb5f559" +# prng.hex(13) #=> "39b290146bea6ce975c37cfc23" +# # or just +# Random.hex #=> "1aed0c631e41be7f77365415541052ee" +# +# Generate random base64 strings: +# +# prng.base64(10) #=> "EcmTPZwWRAozdA==" +# prng.base64(10) #=> "KO1nIU+p9DKxGg==" +# prng.base64(12) #=> "7kJSM/MzBJI+75j8" +# Random.base64(4) #=> "bsQ3fQ==" +# +# Generate random binary strings: +# +# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" +# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" +# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" +# +# Generate alphanumeric strings: +# +# prng.alphanumeric(10) #=> "S8baxMJnPl" +# prng.alphanumeric(10) #=> "aOxAg8BAJe" +# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" +# +# Generate UUIDs: +# +# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" +# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" +# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" +# +# All methods are available in the standard library Bundler::SecureRandom, too: +# +# Bundler::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" + +module Bundler::Random::Formatter + + # Generate a random binary string. + # + # The argument _n_ specifies the length of the result string. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in future. + # + # The result may contain any byte: "\x00" - "\xff". + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" + # # or + # prng = Random.new + # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" + def random_bytes(n=nil) + n = n ? n.to_int : 16 + gen_random(n) + end + + # Generate a random hexadecimal string. + # + # The argument _n_ specifies the length, in bytes, of the random number to be generated. + # The length of the resulting hexadecimal string is twice of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain 0-9 and a-f. + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" + # # or + # prng = Random.new + # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" + def hex(n=nil) + random_bytes(n).unpack1("H*") + end + + # Generate a random base64 string. + # + # The argument _n_ specifies the length, in bytes, of the random number + # to be generated. The length of the result string is about 4/3 of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" + # # or + # prng = Random.new + # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" + # + # See RFC 3548 for the definition of base64. + def base64(n=nil) + [random_bytes(n)].pack("m0") + end + + # Generate a random URL-safe base64 string. + # + # The argument _n_ specifies the length, in bytes, of the random number + # to be generated. The length of the result string is about 4/3 of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The boolean argument _padding_ specifies the padding. + # If it is false or nil, padding is not generated. + # Otherwise padding is generated. + # By default, padding is not generated because "=" may be used as a URL delimiter. + # + # The result may contain A-Z, a-z, 0-9, "-" and "_". + # "=" is also used if _padding_ is true. + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" + # # or + # prng = Random.new + # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" + # + # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" + # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" + # + # See RFC 3548 for the definition of URL-safe base64. + def urlsafe_base64(n=nil, padding=false) + s = [random_bytes(n)].pack("m0") + s.tr!("+/", "-_") + s.delete!("=") unless padding + s + end + + # Generate a random v4 UUID (Universally Unique IDentifier). + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" + # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" + # # or + # prng = Random.new + # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" + # + # The version 4 UUID is purely random (except the version). + # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. + # + # The result contains 122 random bits (15.25 random bytes). + # + # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. + # + def uuid + ary = random_bytes(16).unpack("NnnnnN") + ary[2] = (ary[2] & 0x0fff) | 0x4000 + ary[3] = (ary[3] & 0x3fff) | 0x8000 + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + alias uuid_v4 uuid + + # Generate a random v7 UUID (Universally Unique IDentifier). + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" + # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" + # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" + # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" + # # |<--sorted-->| |<----- random ---->| + # + # # or + # prng = Random.new + # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" + # + # The version 7 UUID starts with the least significant 48 bits of a 64 bit + # Unix timestamp (milliseconds since the epoch) and fills the remaining bits + # with random data, excluding the version and variant bits. + # + # This allows version 7 UUIDs to be sorted by creation time. Time ordered + # UUIDs can be used for better database index locality of newly inserted + # records, which may have a significant performance benefit compared to random + # data inserts. + # + # The result contains 74 random bits (9.25 random bytes). + # + # Note that this method cannot be made reproducable because its output + # includes not only random bits but also timestamp. + # + # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] + # for details of UUIDv7. + # + # ==== Monotonicity + # + # UUIDv7 has millisecond precision by default, so multiple UUIDs created + # within the same millisecond are not issued in monotonically increasing + # order. To create UUIDs that are time-ordered with sub-millisecond + # precision, up to 12 bits of additional timestamp may added with + # +extra_timestamp_bits+. The extra timestamp precision comes at the expense + # of random bits. Setting extra_timestamp_bits: 12 provides ~244ns + # of precision, but only 62 random bits (7.75 random bytes). + # + # prng = Random.new + # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } + # # => + # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", + # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", + # "0188d4c7-13da-754a-88ea-ac0baeedd8db", + # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] + # # |<--- sorted --->| |<-- random --->| + # + # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } + # # => + # ["0188d4c7-3333-7a95-850a-de6edb858f7e", + # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order + # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order + # "0188d4c7-3333-7af9-87c3-8f612edac82e"] + # # |<--- sorted -->||<---- random --->| + # + # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based + # on UTC, which excludes leap seconds and can rollback the clock. To avoid + # this, the system clock can synchronize with an NTP server configured to use + # a "leap smear" approach. NTP or PTP will also be needed to synchronize + # across distributed nodes. + # + # Counters and other mechanisms for stronger guarantees of monotonicity are + # not implemented. Applications with stricter requirements should follow + # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] + # of the specification. + # + def uuid_v7(extra_timestamp_bits: 0) + case (extra_timestamp_bits = Integer(extra_timestamp_bits)) + when 0 # min timestamp precision + ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) + rand = random_bytes(10) + rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version + rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant + "%08x-%04x-%s" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + rand.unpack("H4H4H12").join("-") + ] + + when 12 # max timestamp precision + ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + .divmod(1_000_000) + extra_bits = ns * 4096 / 1_000_000 + rand = random_bytes(8) + rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant + "%08x-%04x-7%03x-%s" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + extra_bits, + rand.unpack("H4H12").join("-") + ] + + when (0..12) # the generic version is slower than the special cases above + rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") + rand_mask_bits = 12 - extra_timestamp_bits + ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + .divmod(1_000_000) + "%08x-%04x-%04x-%04x-%04x%08x" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + 0x7000 | + ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | + rand_a & ((1 << rand_mask_bits) - 1), + 0x8000 | (rand_b1 & 0x3fff), + rand_b2, + rand_b3 + ] + + else + raise ArgumentError, "extra_timestamp_bits must be in 0..12" + end + end + + # Internal interface to Random; Generate random data _n_ bytes. + private def gen_random(n) + self.bytes(n) + end + + # Generate a string that randomly draws from a + # source array of characters. + # + # The argument _source_ specifies the array of characters from which + # to generate the string. + # The argument _n_ specifies the length, in characters, of the string to be + # generated. + # + # The result may contain whatever characters are in the source array. + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" + # prng.choose([*'0'..'9'], 5) #=> "27309" + private def choose(source, n) + size = source.size + m = 1 + limit = size + while limit * size <= 0x100000000 + limit *= size + m += 1 + end + result = ''.dup + while m <= n + rs = random_number(limit) + is = rs.digits(size) + (m-is.length).times { is << 0 } + result << source.values_at(*is).join('') + n -= m + end + if 0 < n + rs = random_number(limit) + is = rs.digits(size) + if is.length < n + (n-is.length).times { is << 0 } + else + is.pop while n < is.length + end + result.concat source.values_at(*is).join('') + end + result + end + + # The default character list for #alphanumeric. + ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] + + # Generate a random alphanumeric string. + # + # The argument _n_ specifies the length, in characters, of the alphanumeric + # string to be generated. + # The argument _chars_ specifies the character list which the result is + # consist of. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. + # + # require 'bundler/vendor/securerandom/lib/random/formatter' + # + # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" + # # or + # prng = Random.new + # prng.alphanumeric(10) #=> "i6K93NdqiH" + # + # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" + # # or + # prng = Random.new + # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end +end diff --git a/lib/bundler/vendor/securerandom/lib/securerandom.rb b/lib/bundler/vendor/securerandom/lib/securerandom.rb new file mode 100644 index 00000000000000..e797054468edcc --- /dev/null +++ b/lib/bundler/vendor/securerandom/lib/securerandom.rb @@ -0,0 +1,96 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +require_relative 'random/formatter' + +# == Secure random number generator interface. +# +# This library is an interface to secure random number generators which are +# suitable for generating session keys in HTTP cookies, etc. +# +# You can use this library in your application by requiring it: +# +# require 'bundler/vendor/securerandom/lib/securerandom' +# +# It supports the following secure random number generators: +# +# * openssl +# * /dev/urandom +# * Win32 +# +# Bundler::SecureRandom is extended by the Bundler::Random::Formatter module which +# defines the following methods: +# +# * alphanumeric +# * base64 +# * choose +# * gen_random +# * hex +# * rand +# * random_bytes +# * random_number +# * urlsafe_base64 +# * uuid +# +# These methods are usable as class methods of Bundler::SecureRandom such as +# +Bundler::SecureRandom.hex+. +# +# If a secure random number generator is not available, +# +NotImplementedError+ is raised. + +module Bundler::SecureRandom + + # The version + VERSION = "0.3.1" + + class << self + # Returns a random binary string containing +size+ bytes. + # + # See Random.bytes + def bytes(n) + return gen_random(n) + end + + private + + # :stopdoc: + + # Implementation using OpenSSL + def gen_random_openssl(n) + return OpenSSL::Random.random_bytes(n) + end + + # Implementation using system random device + def gen_random_urandom(n) + ret = Random.urandom(n) + unless ret + raise NotImplementedError, "No random device" + end + unless ret.length == n + raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" + end + ret + end + + begin + # Check if Random.urandom is available + Random.urandom(1) + alias gen_random gen_random_urandom + rescue RuntimeError + begin + require 'openssl' + rescue NoMethodError + raise NotImplementedError, "No random device" + else + alias gen_random gen_random_openssl + end + end + + # :startdoc: + + # Generate random data bytes for Bundler::Random::Formatter + public :gen_random + end +end + +Bundler::SecureRandom.extend(Bundler::Random::Formatter) diff --git a/lib/bundler/vendored_securerandom.rb b/lib/bundler/vendored_securerandom.rb new file mode 100644 index 00000000000000..6c15f4a2b2aef5 --- /dev/null +++ b/lib/bundler/vendored_securerandom.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Use RubyGems vendored copy when available. Otherwise fallback to Bundler +# vendored copy. The vendored copy in Bundler can be removed once support for +# RubyGems 3.5.18 is dropped. + +begin + require "rubygems/vendored_securerandom" +rescue LoadError + module Bundler::Random; end + require_relative "vendor/securerandom/lib/securerandom" + Gem::SecureRandom = Bundler::SecureRandom + Gem::Random = Bundler::Random +end diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb index 439e5755bd74d0..14dc93fc9c9a19 100644 --- a/lib/irb/easter-egg.rb +++ b/lib/irb/easter-egg.rb @@ -107,13 +107,14 @@ def render_frame(i) end private def easter_egg(type = nil) + print "\e[?1049h" type ||= [:logo, :dancing].sample case type when :logo - require "rdoc" - RDoc::RI::Driver.new.page do |io| - type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large - io.write easter_egg_logo(type) + Pager.page do |io| + logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large + io.write easter_egg_logo(logo_type) + STDIN.raw { STDIN.getc } if io == STDOUT end when :dancing STDOUT.cooked do @@ -138,10 +139,11 @@ def render_frame(i) end rescue Interrupt ensure - print "\e[0m\e[?1049l" trap("SIGINT", prev_trap) end end + ensure + print "\e[0m\e[?1049l" end end end diff --git a/lib/logger/version.rb b/lib/logger/version.rb index 202b6e4fbaed53..2a0801be633474 100644 --- a/lib/logger/version.rb +++ b/lib/logger/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Logger - VERSION = "1.6.0" + VERSION = "1.6.1" end diff --git a/lib/optparse.rb b/lib/optparse.rb index d52401a31b60da..50641867f08db7 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1115,7 +1115,7 @@ def help_exit Switch::OptionalArgument.new do |pkg| if pkg begin - require 'optparse/version' + require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index c3b4bc88875537..ae026b42ac0447 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -155,7 +155,7 @@ def code_units_offset(byte_offset, encoding) # Specialized version of `code_units_column` that does not depend on # `code_units_offset`, which is a more expensive operation. This is - # essentialy the same as `Prism::Source#column`. + # essentially the same as `Prism::Source#column`. def code_units_column(byte_offset, encoding) byte_offset - line_start(byte_offset) end diff --git a/lib/rdoc.rb b/lib/rdoc.rb index 209042ed0e861d..3821569f45fae5 100644 --- a/lib/rdoc.rb +++ b/lib/rdoc.rb @@ -21,7 +21,7 @@ # see RDoc::Markup and refer to rdoc --help for command line usage. # # If you want to set the default markup format see -# RDoc::Markup@Supported+Formats +# RDoc::Markup@Markup+Formats # # If you want to store rdoc configuration in your gem (such as the default # markup format) see RDoc::Options@Saved+Options diff --git a/lib/reline.rb b/lib/reline.rb index b851144627ffc3..ddb0224180cb1f 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -324,14 +324,17 @@ def readline(prompt = '', add_hist = false) line_editor.prompt_proc = prompt_proc line_editor.auto_indent_proc = auto_indent_proc line_editor.dig_perfect_match_proc = dig_perfect_match_proc + + # Readline calls pre_input_hook just after printing the first prompt. + line_editor.print_nomultiline_prompt pre_input_hook&.call + unless Reline::IOGate.dumb? @dialog_proc_list.each_pair do |name_sym, d| line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) end end - line_editor.print_nomultiline_prompt line_editor.update_dialogs line_editor.rerender @@ -343,7 +346,7 @@ def readline(prompt = '', add_hist = false) inputs.each do |key| if key.char == :bracketed_paste_start text = io_gate.read_bracketed_paste - line_editor.insert_pasted_text(text) + line_editor.insert_multiline_text(text) line_editor.scroll_into_view else line_editor.update(key) @@ -457,8 +460,8 @@ def ambiguous_width def_single_delegator :line_editor, :byte_pointer, :point def_single_delegator :line_editor, :byte_pointer=, :point= - def self.insert_text(*args, &block) - line_editor.insert_text(*args, &block) + def self.insert_text(text) + line_editor.insert_multiline_text(text) self end diff --git a/lib/reline/config.rb b/lib/reline/config.rb index 16ed7eee05b4cb..6aa6ba8d9418a9 100644 --- a/lib/reline/config.rb +++ b/lib/reline/config.rb @@ -29,6 +29,17 @@ class InvalidInputrc < RuntimeError attr_accessor :autocompletion def initialize + reset_variables + end + + def reset + if editing_mode_is?(:vi_command) + @editing_mode_label = :vi_insert + end + @oneshot_key_bindings.clear + end + + def reset_variables @additional_key_bindings = { # from inputrc emacs: Reline::KeyActor::Base.new, vi_insert: Reline::KeyActor::Base.new, @@ -51,16 +62,11 @@ def initialize @keyseq_timeout = 500 @test_mode = false @autocompletion = false - @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding) + @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding) @loaded = false @enable_bracketed_paste = true - end - - def reset - if editing_mode_is?(:vi_command) - @editing_mode_label = :vi_insert - end - @oneshot_key_bindings.clear + @show_mode_in_prompt = false + @default_inputrc_path = nil end def editing_mode @@ -360,6 +366,11 @@ def parse_keyseq(str) ret end + def reload + reset_variables + read + end + private def seven_bit_encoding?(encoding) encoding == Encoding::US_ASCII end diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index 6ba4b830d6225e..40025db5046fcc 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -159,14 +159,24 @@ def call(*args) FILE_NAME_INFO = 2 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + # Calling Win32API with console handle is reported to fail after executing some external command. + # We need to refresh console handle and retry the call again. + private def call_with_console_handle(win32func, *args) + val = win32func.call(@hConsoleHandle, *args) + return val if val != 0 + + @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) + win32func.call(@hConsoleHandle, *args) + end + private def getconsolemode mode = "\000\000\000\000" - @GetConsoleMode.call(@hConsoleHandle, mode) + call_with_console_handle(@GetConsoleMode, mode) mode.unpack1('L') end private def setconsolemode(mode) - @SetConsoleMode.call(@hConsoleHandle, mode) + call_with_console_handle(@SetConsoleMode, mode) end #if @legacy_console @@ -334,7 +344,7 @@ def get_console_screen_buffer_info # [18,2] dwMaximumWindowSize.X # [20,2] dwMaximumWindowSize.Y csbi = 0.chr * 22 - return if @GetConsoleScreenBufferInfo.call(@hConsoleHandle, csbi) == 0 + return if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) == 0 csbi end @@ -355,14 +365,14 @@ def cursor_pos end def move_cursor_column(val) - @SetConsoleCursorPosition.call(@hConsoleHandle, cursor_pos.y * 65536 + val) + call_with_console_handle(@SetConsoleCursorPosition, cursor_pos.y * 65536 + val) end def move_cursor_up(val) if val > 0 y = cursor_pos.y - val y = 0 if y < 0 - @SetConsoleCursorPosition.call(@hConsoleHandle, y * 65536 + cursor_pos.x) + call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + cursor_pos.x) elsif val < 0 move_cursor_down(-val) end @@ -374,7 +384,7 @@ def move_cursor_down(val) screen_height = get_screen_size.first y = cursor_pos.y + val y = screen_height - 1 if y > (screen_height - 1) - @SetConsoleCursorPosition.call(@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x) + call_with_console_handle(@SetConsoleCursorPosition, (cursor_pos.y + val) * 65536 + cursor_pos.x) elsif val < 0 move_cursor_up(-val) end @@ -385,8 +395,8 @@ def erase_after_cursor attributes = csbi[8, 2].unpack1('S') cursor = csbi[4, 4].unpack1('L') written = 0.chr * 4 - @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) - @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written) + call_with_console_handle(@FillConsoleOutputCharacter, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) + call_with_console_handle(@FillConsoleOutputAttribute, attributes, get_screen_size.last - cursor_pos.x, cursor, written) end def scroll_down(val) @@ -404,7 +414,7 @@ def scroll_down(val) scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4') destination_origin = 0 # y * 65536 + x fill = [' '.ord, attributes].pack('SS') - @ScrollConsoleScreenBuffer.call(@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) + call_with_console_handle(@ScrollConsoleScreenBuffer, scroll_rectangle, nil, destination_origin, fill) else origin_x = x + 1 origin_y = y - window_top + 1 @@ -423,9 +433,9 @@ def clear_screen fill_length = buffer_width * (window_bottom - window_top + 1) screen_topleft = window_top * 65536 written = 0.chr * 4 - @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, fill_length, screen_topleft, written) - @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, fill_length, screen_topleft, written) - @SetConsoleCursorPosition.call(@hConsoleHandle, screen_topleft) + call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) + call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) + call_with_console_handle(@SetConsoleCursorPosition, screen_topleft) else @output.write "\e[2J" "\e[H" end @@ -439,14 +449,14 @@ def hide_cursor size = 100 visible = 0 # 0 means false cursor_info = [size, visible].pack('Li') - @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info) + call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end def show_cursor size = 100 visible = 1 # 1 means true cursor_info = [size, visible].pack('Li') - @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info) + call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end def set_winch_handler(&handler) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 9c97415050b233..c71a5f79eef9c5 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -176,9 +176,8 @@ def handle_signal scroll_into_view Reline::IOGate.move_cursor_up @rendered_screen.cursor_y @rendered_screen.base_y = Reline::IOGate.cursor_pos.y - @rendered_screen.lines = [] - @rendered_screen.cursor_y = 0 - render_differential + clear_rendered_screen_cache + render end private def handle_interrupted @@ -186,11 +185,11 @@ def handle_signal @interrupted = false clear_dialogs - scrolldown = render_differential - Reline::IOGate.scroll_down scrolldown + render + cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y + Reline::IOGate.scroll_down cursor_to_bottom_offset Reline::IOGate.move_cursor_column 0 - @rendered_screen.lines = [] - @rendered_screen.cursor_y = 0 + clear_rendered_screen_cache case @old_trap when 'DEFAULT', 'SYSTEM_DEFAULT' raise Interrupt @@ -460,28 +459,7 @@ def update_dialogs(key = nil) end def render_finished - clear_rendered_lines - render_full_content - end - - def clear_rendered_lines - Reline::IOGate.move_cursor_up @rendered_screen.cursor_y - Reline::IOGate.move_cursor_column 0 - - num_lines = @rendered_screen.lines.size - return unless num_lines && num_lines >= 1 - - Reline::IOGate.move_cursor_down num_lines - 1 - (num_lines - 1).times do - Reline::IOGate.erase_after_cursor - Reline::IOGate.move_cursor_up 1 - end - Reline::IOGate.erase_after_cursor - @rendered_screen.lines = [] - @rendered_screen.cursor_y = 0 - end - - def render_full_content + render_differential([], 0, 0) lines = @buffer_of_lines.size.times.map do |i| line = prompt_list[i] + modified_lines[i] wrapped_lines, = split_by_width(line, screen_width) @@ -495,10 +473,8 @@ def print_nomultiline_prompt @output.write @prompt if @prompt && !@is_multiline end - def render_differential + def render wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position - - rendered_lines = @rendered_screen.lines new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line| prompt_width = Reline::Unicode.calculate_width(prompt, true) [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]] @@ -516,12 +492,21 @@ def render_differential x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top y_range.each do |row| next if row < 0 || row >= screen_height + dialog_rows = new_lines[row] ||= [] # index 0 is for prompt, index 1 is for line, index 2.. is for dialog dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] end end + render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top + end + + # Reflects lines to be rendered and new cursor position to the screen + # by calculating the difference from the previous render. + + private def render_differential(new_lines, new_cursor_x, new_cursor_y) + rendered_lines = @rendered_screen.lines cursor_y = @rendered_screen.cursor_y if new_lines != rendered_lines # Hide cursor while rendering to avoid cursor flickering. @@ -548,11 +533,14 @@ def render_differential @rendered_screen.lines = new_lines Reline::IOGate.show_cursor end - y = wrapped_cursor_y - screen_scroll_top - Reline::IOGate.move_cursor_column wrapped_cursor_x - Reline::IOGate.move_cursor_down y - cursor_y - @rendered_screen.cursor_y = y - new_lines.size - y + Reline::IOGate.move_cursor_column new_cursor_x + Reline::IOGate.move_cursor_down new_cursor_y - cursor_y + @rendered_screen.cursor_y = new_cursor_y + end + + private def clear_rendered_screen_cache + @rendered_screen.lines = [] + @rendered_screen.cursor_y = 0 end def upper_space_height(wrapped_cursor_y) @@ -564,7 +552,7 @@ def rest_height(wrapped_cursor_y) end def rerender - render_differential unless @in_pasting + render unless @in_pasting end class DialogProcScope @@ -1333,7 +1321,7 @@ def confirm_multiline_termination @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n") end - def insert_pasted_text(text) + def insert_multiline_text(text) save_old_buffer pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer) post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..) @@ -1978,9 +1966,8 @@ def finish private def ed_clear_screen(key) Reline::IOGate.clear_screen @screen_size = Reline::IOGate.get_screen_size - @rendered_screen.lines = [] @rendered_screen.base_y = 0 - @rendered_screen.cursor_y = 0 + clear_rendered_screen_cache end alias_method :clear_screen, :ed_clear_screen @@ -2554,4 +2541,8 @@ def finish private def set_next_action_state(type, value) @next_action_state = [type, value] end + + private def re_read_init_file(_key) + @config.reload + end end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 2b52cde0a7494c..bd9f240e2091d5 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -773,18 +773,14 @@ def self.refresh # Safely read a file in binary mode on all platforms. def self.read_binary(path) - open_file(path, "rb+", &:read) - rescue Errno::EACCES, Errno::EROFS - open_file(path, "rb", &:read) + File.binread(path) end ## # Safely write a file in binary mode on all platforms. def self.write_binary(path, data) - open_file(path, "wb") do |io| - io.write data - end + File.binwrite(path, data) rescue Errno::ENOSPC # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. File.delete(path) if File.exist?(path) @@ -798,24 +794,20 @@ def self.open_file(path, flags, &block) File.open(path, flags, &block) end + MODE_TO_FLOCK = IO::RDONLY | IO::APPEND | IO::CREAT # :nodoc: + ## # Open a file with given flags, and protect access with flock def self.open_file_with_flock(path, &block) - flags = File.exist?(path) ? "r+" : "a+" - - File.open(path, flags) do |io| + File.open(path, MODE_TO_FLOCK) do |io| begin io.flock(File::LOCK_EX) rescue Errno::ENOSYS, Errno::ENOTSUP + rescue Errno::ENOLCK # NFS + raise unless Thread.main == Thread.current end yield io - rescue Errno::ENOLCK # NFS - if Thread.main != Thread.current - raise - else - open_file(path, flags, &block) - end end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 9f47b795f118dd..bb2246ca319281 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -336,6 +336,8 @@ def install_rdoc require_relative "../rdoc" + return false unless defined?(Gem::RDoc) + fake_spec = Gem::Specification.new "rubygems", Gem::VERSION def fake_spec.full_gem_path File.expand_path "../../..", __dir__ diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index c3a41592f670f5..4b5c74e0ea60e7 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -75,7 +75,6 @@ def self.fetcher def initialize(proxy=nil, dns=nil, headers={}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled require_relative "vendored_net_http" - require "stringio" require_relative "vendor/uri/lib/uri" Socket.do_not_reverse_lookup = true diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index d90e311b65a197..4e5545982d6432 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -213,14 +213,16 @@ def download(spec, dir=Dir.pwd) end def pretty_print(q) # :nodoc: - q.group 2, "[Remote:", "]" do - q.breakable - q.text @uri.to_s - - if api = uri + q.object_group(self) do + q.group 2, "[Remote:", "]" do q.breakable - q.text "API URI: " - q.text api.to_s + q.text @uri.to_s + + if api = uri + q.breakable + q.text "API URI: " + q.text api.to_s + end end end end diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index bda63c6844d92c..34f6851bc40557 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -157,12 +157,14 @@ def install_dir # :nodoc: end def pretty_print(q) # :nodoc: - q.group 2, "[Git: ", "]" do - q.breakable - q.text @repository + q.object_group(self) do + q.group 2, "[Git: ", "]" do + q.breakable + q.text @repository - q.breakable - q.text @reference + q.breakable + q.text @reference + end end end diff --git a/lib/rubygems/source/installed.rb b/lib/rubygems/source/installed.rb index cbe12a05163642..f5c96fee5129b4 100644 --- a/lib/rubygems/source/installed.rb +++ b/lib/rubygems/source/installed.rb @@ -32,6 +32,8 @@ def download(spec, path) end def pretty_print(q) # :nodoc: - q.text "[Installed]" + q.object_group(self) do + q.text "[Installed]" + end end end diff --git a/lib/rubygems/source/local.rb b/lib/rubygems/source/local.rb index d81d8343a818ff..ba6eea1f9aa5c7 100644 --- a/lib/rubygems/source/local.rb +++ b/lib/rubygems/source/local.rb @@ -117,10 +117,14 @@ def download(spec, cache_dir = nil) # :nodoc: end def pretty_print(q) # :nodoc: - q.group 2, "[Local gems:", "]" do - q.breakable - q.seplist @specs.keys do |v| - q.text v.full_name + q.object_group(self) do + q.group 2, "[Local gems:", "]" do + q.breakable + if @specs + q.seplist @specs.keys do |v| + q.text v.full_name + end + end end end end diff --git a/lib/rubygems/source/specific_file.rb b/lib/rubygems/source/specific_file.rb index e9b27536469300..dde1d48a219d76 100644 --- a/lib/rubygems/source/specific_file.rb +++ b/lib/rubygems/source/specific_file.rb @@ -42,9 +42,11 @@ def download(spec, dir = nil) # :nodoc: end def pretty_print(q) # :nodoc: - q.group 2, "[SpecificFile:", "]" do - q.breakable - q.text @path + q.object_group(self) do + q.group 2, "[SpecificFile:", "]" do + q.breakable + q.text @path + end end end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index d0e0e4e91ac6ab..594d882c6b6c05 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -1318,7 +1318,7 @@ def self._load(str) spec.instance_variable_set :@has_rdoc, array[15] spec.instance_variable_set :@new_platform, array[16] spec.instance_variable_set :@platform, array[16].to_s - spec.instance_variable_set :@license, array[17] + spec.instance_variable_set :@licenses, [array[17]] spec.instance_variable_set :@metadata, array[18] spec.instance_variable_set :@loaded, false spec.instance_variable_set :@activated, false diff --git a/lib/rubygems/vendor/net-http/lib/net/https.rb b/lib/rubygems/vendor/net-http/lib/net/https.rb index d2784f0be08774..f104c85c8134d4 100644 --- a/lib/rubygems/vendor/net-http/lib/net/https.rb +++ b/lib/rubygems/vendor/net-http/lib/net/https.rb @@ -4,7 +4,7 @@ = net/https -- SSL/TLS enhancement for Gem::Net::HTTP. This file has been merged with net/http. There is no longer any need to - require 'rubygems/vendor/net-http/lib/net/https' to use HTTPS. + require_relative 'https' to use HTTPS. See Gem::Net::HTTP for details on how to make HTTPS connections. diff --git a/lib/rubygems/vendor/optparse/lib/optparse.rb b/lib/rubygems/vendor/optparse/lib/optparse.rb index 59374317201d8b..00dc7c8a67355d 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse.rb @@ -1084,7 +1084,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: Switch::OptionalArgument.new do |pkg| if pkg begin - require 'rubygems/vendor/optparse/lib/optparse/version' + require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index ac0ba0b3136a79..0f5ded3b767a4e 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -5,7 +5,7 @@ require 'io/wait' begin - require 'securerandom' + require_relative '../../../vendored_securerandom' rescue LoadError end @@ -602,10 +602,10 @@ def extract_resources(msg, name, typeclass) # :nodoc: } end - if defined? SecureRandom + if defined? Gem::SecureRandom def self.random(arg) # :nodoc: begin - SecureRandom.random_number(arg) + Gem::SecureRandom.random_number(arg) rescue NotImplementedError rand(arg) end diff --git a/lib/rubygems/vendor/securerandom/.document b/lib/rubygems/vendor/securerandom/.document new file mode 100644 index 00000000000000..0c43bbd6b38177 --- /dev/null +++ b/lib/rubygems/vendor/securerandom/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/securerandom/lib/random/formatter.rb b/lib/rubygems/vendor/securerandom/lib/random/formatter.rb new file mode 100644 index 00000000000000..3544033340be3f --- /dev/null +++ b/lib/rubygems/vendor/securerandom/lib/random/formatter.rb @@ -0,0 +1,373 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +# == \Random number formatter. +# +# Formats generated random numbers in many manners. When 'random/formatter' +# is required, several methods are added to empty core module Gem::Random::Formatter, +# making them available as Random's instance and module methods. +# +# Standard library Gem::SecureRandom is also extended with the module, and the methods +# described below are available as a module methods in it. +# +# === Examples +# +# Generate random hexadecimal strings: +# +# require 'rubygems/vendor/securerandom/lib/random/formatter' +# +# prng = Random.new +# prng.hex(10) #=> "52750b30ffbc7de3b362" +# prng.hex(10) #=> "92b15d6c8dc4beb5f559" +# prng.hex(13) #=> "39b290146bea6ce975c37cfc23" +# # or just +# Random.hex #=> "1aed0c631e41be7f77365415541052ee" +# +# Generate random base64 strings: +# +# prng.base64(10) #=> "EcmTPZwWRAozdA==" +# prng.base64(10) #=> "KO1nIU+p9DKxGg==" +# prng.base64(12) #=> "7kJSM/MzBJI+75j8" +# Random.base64(4) #=> "bsQ3fQ==" +# +# Generate random binary strings: +# +# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" +# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" +# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" +# +# Generate alphanumeric strings: +# +# prng.alphanumeric(10) #=> "S8baxMJnPl" +# prng.alphanumeric(10) #=> "aOxAg8BAJe" +# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" +# +# Generate UUIDs: +# +# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" +# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" +# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" +# +# All methods are available in the standard library Gem::SecureRandom, too: +# +# Gem::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" + +module Gem::Random::Formatter + + # Generate a random binary string. + # + # The argument _n_ specifies the length of the result string. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in future. + # + # The result may contain any byte: "\x00" - "\xff". + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" + # # or + # prng = Random.new + # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" + def random_bytes(n=nil) + n = n ? n.to_int : 16 + gen_random(n) + end + + # Generate a random hexadecimal string. + # + # The argument _n_ specifies the length, in bytes, of the random number to be generated. + # The length of the resulting hexadecimal string is twice of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain 0-9 and a-f. + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" + # # or + # prng = Random.new + # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" + def hex(n=nil) + random_bytes(n).unpack1("H*") + end + + # Generate a random base64 string. + # + # The argument _n_ specifies the length, in bytes, of the random number + # to be generated. The length of the result string is about 4/3 of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" + # # or + # prng = Random.new + # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" + # + # See RFC 3548 for the definition of base64. + def base64(n=nil) + [random_bytes(n)].pack("m0") + end + + # Generate a random URL-safe base64 string. + # + # The argument _n_ specifies the length, in bytes, of the random number + # to be generated. The length of the result string is about 4/3 of _n_. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The boolean argument _padding_ specifies the padding. + # If it is false or nil, padding is not generated. + # Otherwise padding is generated. + # By default, padding is not generated because "=" may be used as a URL delimiter. + # + # The result may contain A-Z, a-z, 0-9, "-" and "_". + # "=" is also used if _padding_ is true. + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" + # # or + # prng = Random.new + # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" + # + # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" + # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" + # + # See RFC 3548 for the definition of URL-safe base64. + def urlsafe_base64(n=nil, padding=false) + s = [random_bytes(n)].pack("m0") + s.tr!("+/", "-_") + s.delete!("=") unless padding + s + end + + # Generate a random v4 UUID (Universally Unique IDentifier). + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" + # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" + # # or + # prng = Random.new + # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" + # + # The version 4 UUID is purely random (except the version). + # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. + # + # The result contains 122 random bits (15.25 random bytes). + # + # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. + # + def uuid + ary = random_bytes(16).unpack("NnnnnN") + ary[2] = (ary[2] & 0x0fff) | 0x4000 + ary[3] = (ary[3] & 0x3fff) | 0x8000 + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + alias uuid_v4 uuid + + # Generate a random v7 UUID (Universally Unique IDentifier). + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" + # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" + # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" + # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" + # # |<--sorted-->| |<----- random ---->| + # + # # or + # prng = Random.new + # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" + # + # The version 7 UUID starts with the least significant 48 bits of a 64 bit + # Unix timestamp (milliseconds since the epoch) and fills the remaining bits + # with random data, excluding the version and variant bits. + # + # This allows version 7 UUIDs to be sorted by creation time. Time ordered + # UUIDs can be used for better database index locality of newly inserted + # records, which may have a significant performance benefit compared to random + # data inserts. + # + # The result contains 74 random bits (9.25 random bytes). + # + # Note that this method cannot be made reproducable because its output + # includes not only random bits but also timestamp. + # + # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] + # for details of UUIDv7. + # + # ==== Monotonicity + # + # UUIDv7 has millisecond precision by default, so multiple UUIDs created + # within the same millisecond are not issued in monotonically increasing + # order. To create UUIDs that are time-ordered with sub-millisecond + # precision, up to 12 bits of additional timestamp may added with + # +extra_timestamp_bits+. The extra timestamp precision comes at the expense + # of random bits. Setting extra_timestamp_bits: 12 provides ~244ns + # of precision, but only 62 random bits (7.75 random bytes). + # + # prng = Random.new + # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } + # # => + # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", + # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", + # "0188d4c7-13da-754a-88ea-ac0baeedd8db", + # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] + # # |<--- sorted --->| |<-- random --->| + # + # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } + # # => + # ["0188d4c7-3333-7a95-850a-de6edb858f7e", + # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order + # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order + # "0188d4c7-3333-7af9-87c3-8f612edac82e"] + # # |<--- sorted -->||<---- random --->| + # + # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based + # on UTC, which excludes leap seconds and can rollback the clock. To avoid + # this, the system clock can synchronize with an NTP server configured to use + # a "leap smear" approach. NTP or PTP will also be needed to synchronize + # across distributed nodes. + # + # Counters and other mechanisms for stronger guarantees of monotonicity are + # not implemented. Applications with stricter requirements should follow + # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] + # of the specification. + # + def uuid_v7(extra_timestamp_bits: 0) + case (extra_timestamp_bits = Integer(extra_timestamp_bits)) + when 0 # min timestamp precision + ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) + rand = random_bytes(10) + rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version + rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant + "%08x-%04x-%s" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + rand.unpack("H4H4H12").join("-") + ] + + when 12 # max timestamp precision + ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + .divmod(1_000_000) + extra_bits = ns * 4096 / 1_000_000 + rand = random_bytes(8) + rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant + "%08x-%04x-7%03x-%s" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + extra_bits, + rand.unpack("H4H12").join("-") + ] + + when (0..12) # the generic version is slower than the special cases above + rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") + rand_mask_bits = 12 - extra_timestamp_bits + ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + .divmod(1_000_000) + "%08x-%04x-%04x-%04x-%04x%08x" % [ + (ms & 0x0000_ffff_ffff_0000) >> 16, + (ms & 0x0000_0000_0000_ffff), + 0x7000 | + ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | + rand_a & ((1 << rand_mask_bits) - 1), + 0x8000 | (rand_b1 & 0x3fff), + rand_b2, + rand_b3 + ] + + else + raise ArgumentError, "extra_timestamp_bits must be in 0..12" + end + end + + # Internal interface to Random; Generate random data _n_ bytes. + private def gen_random(n) + self.bytes(n) + end + + # Generate a string that randomly draws from a + # source array of characters. + # + # The argument _source_ specifies the array of characters from which + # to generate the string. + # The argument _n_ specifies the length, in characters, of the string to be + # generated. + # + # The result may contain whatever characters are in the source array. + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" + # prng.choose([*'0'..'9'], 5) #=> "27309" + private def choose(source, n) + size = source.size + m = 1 + limit = size + while limit * size <= 0x100000000 + limit *= size + m += 1 + end + result = ''.dup + while m <= n + rs = random_number(limit) + is = rs.digits(size) + (m-is.length).times { is << 0 } + result << source.values_at(*is).join('') + n -= m + end + if 0 < n + rs = random_number(limit) + is = rs.digits(size) + if is.length < n + (n-is.length).times { is << 0 } + else + is.pop while n < is.length + end + result.concat source.values_at(*is).join('') + end + result + end + + # The default character list for #alphanumeric. + ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] + + # Generate a random alphanumeric string. + # + # The argument _n_ specifies the length, in characters, of the alphanumeric + # string to be generated. + # The argument _chars_ specifies the character list which the result is + # consist of. + # + # If _n_ is not specified or is nil, 16 is assumed. + # It may be larger in the future. + # + # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. + # + # require 'rubygems/vendor/securerandom/lib/random/formatter' + # + # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" + # # or + # prng = Random.new + # prng.alphanumeric(10) #=> "i6K93NdqiH" + # + # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" + # # or + # prng = Random.new + # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end +end diff --git a/lib/rubygems/vendor/securerandom/lib/securerandom.rb b/lib/rubygems/vendor/securerandom/lib/securerandom.rb new file mode 100644 index 00000000000000..f83d2a74fc705a --- /dev/null +++ b/lib/rubygems/vendor/securerandom/lib/securerandom.rb @@ -0,0 +1,96 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +require_relative 'random/formatter' + +# == Secure random number generator interface. +# +# This library is an interface to secure random number generators which are +# suitable for generating session keys in HTTP cookies, etc. +# +# You can use this library in your application by requiring it: +# +# require 'rubygems/vendor/securerandom/lib/securerandom' +# +# It supports the following secure random number generators: +# +# * openssl +# * /dev/urandom +# * Win32 +# +# Gem::SecureRandom is extended by the Gem::Random::Formatter module which +# defines the following methods: +# +# * alphanumeric +# * base64 +# * choose +# * gen_random +# * hex +# * rand +# * random_bytes +# * random_number +# * urlsafe_base64 +# * uuid +# +# These methods are usable as class methods of Gem::SecureRandom such as +# +Gem::SecureRandom.hex+. +# +# If a secure random number generator is not available, +# +NotImplementedError+ is raised. + +module Gem::SecureRandom + + # The version + VERSION = "0.3.1" + + class << self + # Returns a random binary string containing +size+ bytes. + # + # See Random.bytes + def bytes(n) + return gen_random(n) + end + + private + + # :stopdoc: + + # Implementation using OpenSSL + def gen_random_openssl(n) + return OpenSSL::Random.random_bytes(n) + end + + # Implementation using system random device + def gen_random_urandom(n) + ret = Random.urandom(n) + unless ret + raise NotImplementedError, "No random device" + end + unless ret.length == n + raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" + end + ret + end + + begin + # Check if Random.urandom is available + Random.urandom(1) + alias gen_random gen_random_urandom + rescue RuntimeError + begin + require 'openssl' + rescue NoMethodError + raise NotImplementedError, "No random device" + else + alias gen_random gen_random_openssl + end + end + + # :startdoc: + + # Generate random data bytes for Gem::Random::Formatter + public :gen_random + end +end + +Gem::SecureRandom.extend(Gem::Random::Formatter) diff --git a/lib/rubygems/vendored_securerandom.rb b/lib/rubygems/vendored_securerandom.rb new file mode 100644 index 00000000000000..0ce26905c455ec --- /dev/null +++ b/lib/rubygems/vendored_securerandom.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Gem::Random; end +require_relative "vendor/securerandom/lib/securerandom" diff --git a/lib/uri/version.rb b/lib/uri/version.rb index 2dafa57d59a2b5..bfe3f476708714 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '001300'.freeze + VERSION_CODE = '001301'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/node_dump.c b/node_dump.c index f23aeca6f1ba41..54a4eb0e64441a 100644 --- a/node_dump.c +++ b/node_dump.c @@ -922,8 +922,9 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: alias [nd_1st] [nd_2nd]"); ANN("example: alias bar foo"); F_NODE(nd_1st, RNODE_ALIAS, "new name"); - LAST_NODE; F_NODE(nd_2nd, RNODE_ALIAS, "old name"); + LAST_NODE; + F_LOC(keyword_loc, RNODE_ALIAS); return; case NODE_VALIAS: @@ -932,6 +933,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("example: alias $y $x"); F_ID(nd_alias, RNODE_VALIAS, "new name"); F_ID(nd_orig, RNODE_VALIAS, "old name"); + F_LOC(keyword_loc, RNODE_VALIAS); return; case NODE_UNDEF: @@ -940,6 +942,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("example: undef foo"); LAST_NODE; F_ARRAY(nd_undefs, RNODE_UNDEF, "nd_undefs"); + F_LOC(keyword_loc, RNODE_UNDEF); return; case NODE_CLASS: diff --git a/parse.y b/parse.y index b92f9936401329..6ebd60844adc51 100644 --- a/parse.y +++ b/parse.y @@ -1138,8 +1138,8 @@ static rb_node_splat_t *rb_node_splat_new(struct parser_params *p, NODE *nd_head static rb_node_block_pass_t *rb_node_block_pass_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_defn_t *rb_node_defn_new(struct parser_params *p, ID nd_mid, NODE *nd_defn, const YYLTYPE *loc); static rb_node_defs_t *rb_node_defs_new(struct parser_params *p, NODE *nd_recv, ID nd_mid, NODE *nd_defn, const YYLTYPE *loc); -static rb_node_alias_t *rb_node_alias_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc); -static rb_node_valias_t *rb_node_valias_new(struct parser_params *p, ID nd_alias, ID nd_orig, const YYLTYPE *loc); +static rb_node_alias_t *rb_node_alias_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *keyword_loc); +static rb_node_valias_t *rb_node_valias_new(struct parser_params *p, ID nd_alias, ID nd_orig, const YYLTYPE *loc, const YYLTYPE *keyword_loc); static rb_node_undef_t *rb_node_undef_new(struct parser_params *p, NODE *nd_undef, const YYLTYPE *loc); static rb_node_class_t *rb_node_class_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, NODE *nd_super, const YYLTYPE *loc); static rb_node_module_t *rb_node_module_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, const YYLTYPE *loc); @@ -1246,8 +1246,8 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_BLOCK_PASS(b,loc) rb_node_block_pass_new(p,b,loc) #define NEW_DEFN(i,s,loc) (NODE *)rb_node_defn_new(p,i,s,loc) #define NEW_DEFS(r,i,s,loc) (NODE *)rb_node_defs_new(p,r,i,s,loc) -#define NEW_ALIAS(n,o,loc) (NODE *)rb_node_alias_new(p,n,o,loc) -#define NEW_VALIAS(n,o,loc) (NODE *)rb_node_valias_new(p,n,o,loc) +#define NEW_ALIAS(n,o,loc,k_loc) (NODE *)rb_node_alias_new(p,n,o,loc,k_loc) +#define NEW_VALIAS(n,o,loc,k_loc) (NODE *)rb_node_valias_new(p,n,o,loc,k_loc) #define NEW_UNDEF(i,loc) (NODE *)rb_node_undef_new(p,i,loc) #define NEW_CLASS(n,b,s,loc) (NODE *)rb_node_class_new(p,n,b,s,loc) #define NEW_MODULE(n,b,loc) (NODE *)rb_node_module_new(p,n,b,loc) @@ -3128,12 +3128,12 @@ k_END : keyword_END lex_ctxt stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem { - $$ = NEW_ALIAS($2, $4, &@$); + $$ = NEW_ALIAS($2, $4, &@$, &@1); /*% ripper: alias!($:2, $:4) %*/ } | keyword_alias tGVAR tGVAR { - $$ = NEW_VALIAS($2, $3, &@$); + $$ = NEW_VALIAS($2, $3, &@$, &@1); /*% ripper: var_alias!($:2, $:3) %*/ } | keyword_alias tGVAR tBACK_REF @@ -3141,7 +3141,7 @@ stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem char buf[2]; buf[0] = '$'; buf[1] = (char)RNODE_BACK_REF($3)->nd_nth; - $$ = NEW_VALIAS($2, rb_intern2(buf, 2), &@$); + $$ = NEW_VALIAS($2, rb_intern2(buf, 2), &@$, &@1); /*% ripper: var_alias!($:2, $:3) %*/ } | keyword_alias tGVAR tNTH_REF @@ -3156,6 +3156,7 @@ stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem | keyword_undef undef_list { nd_set_first_loc($2, @1.beg_pos); + RNODE_UNDEF($2)->keyword_loc = @1; $$ = $2; /*% ripper: undef!($:2) %*/ } @@ -6908,11 +6909,7 @@ static void tokaddmbc(struct parser_params *p, int c, rb_encoding *enc); static enum yytokentype parse_string(struct parser_params*,rb_strterm_literal_t*); static enum yytokentype here_document(struct parser_params*,rb_strterm_heredoc_t*); -#ifndef RIPPER -#define set_parser_s_value(x) (void)(x) -#else -#define set_parser_s_value(x) (p->s_value = (x)) -#endif +#define set_parser_s_value(x) (ifdef_ripper(p->s_value = (x), (void)0)) # define set_yylval_node(x) { \ YYLTYPE _cur_loc; \ @@ -12300,21 +12297,23 @@ rb_node_block_pass_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *lo } static rb_node_alias_t * -rb_node_alias_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc) +rb_node_alias_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc, const YYLTYPE *keyword_loc) { rb_node_alias_t *n = NODE_NEWNODE(NODE_ALIAS, rb_node_alias_t, loc); n->nd_1st = nd_1st; n->nd_2nd = nd_2nd; + n->keyword_loc = *keyword_loc; return n; } static rb_node_valias_t * -rb_node_valias_new(struct parser_params *p, ID nd_alias, ID nd_orig, const YYLTYPE *loc) +rb_node_valias_new(struct parser_params *p, ID nd_alias, ID nd_orig, const YYLTYPE *loc, const YYLTYPE *keyword_loc) { rb_node_valias_t *n = NODE_NEWNODE(NODE_VALIAS, rb_node_valias_t, loc); n->nd_alias = nd_alias; n->nd_orig = nd_orig; + n->keyword_loc = *keyword_loc; return n; } @@ -12324,6 +12323,7 @@ rb_node_undef_new(struct parser_params *p, NODE *nd_undef, const YYLTYPE *loc) { rb_node_undef_t *n = NODE_NEWNODE(NODE_UNDEF, rb_node_undef_t, loc); n->nd_undefs = rb_parser_ary_new_capa_for_node(p, 1); + n->keyword_loc = NULL_LOC; rb_parser_ary_push_node(p, n->nd_undefs, nd_undef); return n; diff --git a/prism/config.yml b/prism/config.yml index b7c673813b46f5..585d17a4c6c789 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2933,7 +2933,7 @@ nodes: a, (b, *) = 1, 2, 3, 4 ^ - If the `*` is omitted, the field will containt an `ImplicitRestNode` + If the `*` is omitted, this field will contain an `ImplicitRestNode` a, (b,) = 1, 2, 3, 4 ^ @@ -3020,7 +3020,7 @@ nodes: a, b, * = 1, 2, 3, 4 ^ - If the `*` is omitted, the field will containt an `ImplicitRestNode` + If the `*` is omitted, this field will contain an `ImplicitRestNode` a, b, = 1, 2, 3, 4 ^ diff --git a/prism_compile.c b/prism_compile.c index 5bd02ed04f2bdf..d1c9d5431e044c 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -10405,7 +10405,7 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node, VALUE *script_lines for (size_t index = 0; index < parser->newline_list.size; index++) { size_t offset = parser->newline_list.offsets[index]; - size_t length = index == parser->newline_list.size - 1 ? (parser->end - (parser->start + offset)) : (parser->newline_list.offsets[index + 1] - offset); + size_t length = index == parser->newline_list.size - 1 ? ((size_t) (parser->end - (parser->start + offset))) : (parser->newline_list.offsets[index + 1] - offset); rb_ary_push(*script_lines, rb_enc_str_new((const char *) parser->start + offset, length, scope_node->encoding)); } diff --git a/range.c b/range.c index bf53fd3812fa7f..fcda493fb9e535 100644 --- a/range.c +++ b/range.c @@ -310,6 +310,34 @@ range_each_func(VALUE range, int (*func)(VALUE, VALUE), VALUE arg) } } +// NB: Two functions below (step_i_iter and step_i) are used only to maintain the +// backward-compatible behavior for string ranges with integer steps. If that branch +// will be removed from range_step, these two can go, too. +static bool +step_i_iter(VALUE arg) +{ + VALUE *iter = (VALUE *)arg; + + if (FIXNUM_P(iter[0])) { + iter[0] -= INT2FIX(1) & ~FIXNUM_FLAG; + } + else { + iter[0] = rb_funcall(iter[0], '-', 1, INT2FIX(1)); + } + if (iter[0] != INT2FIX(0)) return false; + iter[0] = iter[1]; + return true; +} + +static int +step_i(VALUE i, VALUE arg) +{ + if (step_i_iter(arg)) { + rb_yield(i); + } + return 0; +} + static int discrete_object_p(VALUE obj) { @@ -431,9 +459,18 @@ range_step_size(VALUE range, VALUE args, VALUE eobj) * * For non-Numeric ranges, step absence is an error: * - * ('a'..'z').step { p _1 } + * (Time.utc(2022, 3, 1)..Time.utc(2022, 2, 24)).step { p _1 } * # raises: step is required for non-numeric ranges (ArgumentError) * + * For backward compatibility reasons, String ranges support the iteration both with + * string step and with integer step. In the latter case, the iteration is performed + * by calculating the next values with String#succ: + * + * ('a'..'e').step(2) { p _1 } + * # Prints: a, c, e + * ('a'..'e').step { p _1 } + * # Default step 1; prints: a, b, c, d, e + * */ static VALUE range_step(int argc, VALUE *argv, VALUE range) @@ -446,11 +483,15 @@ range_step(int argc, VALUE *argv, VALUE range) const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric); const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric); + // For backward compatibility reasons (conforming to behavior before 3.4), String supports + // both old behavior ('a'..).step(1) and new behavior ('a'..).step('a') + // Hence the additional conversion/addional checks. + const VALUE sb = rb_check_string_type(b); if (rb_check_arity(argc, 0, 1)) step = argv[0]; else { - if (b_num_p || (NIL_P(b) && e_num_p)) + if (b_num_p || !NIL_P(sb) || (NIL_P(b) && e_num_p)) step = INT2FIX(1); else rb_raise(rb_eArgError, "step is required for non-numeric ranges"); @@ -521,8 +562,19 @@ range_step(int argc, VALUE *argv, VALUE range) } else if (b_num_p && step_num_p && ruby_float_step(b, e, step, EXCL(range), TRUE)) { /* done */ - } - else { + } else if (!NIL_P(sb) && FIXNUM_P(step)) { + // backwards compatibility behavior for String only, when no step/Integer step is passed + // See discussion in https://bugs.ruby-lang.org/issues/18368 + + VALUE iter[2] = {INT2FIX(1), step}; + + if (NIL_P(e)) { + rb_str_upto_endless_each(sb, step_i, (VALUE)iter); + } + else { + rb_str_upto_each(sb, e, EXCL(range), step_i, (VALUE)iter); + } + } else { v = b; if (!NIL_P(e)) { if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) { diff --git a/rubyparser.h b/rubyparser.h index 0af019850050c1..5383c31f03b330 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -820,6 +820,7 @@ typedef struct RNode_ALIAS { struct RNode *nd_1st; struct RNode *nd_2nd; + rb_code_location_t keyword_loc; } rb_node_alias_t; typedef struct RNode_VALIAS { @@ -827,12 +828,14 @@ typedef struct RNode_VALIAS { ID nd_alias; ID nd_orig; + rb_code_location_t keyword_loc; } rb_node_valias_t; typedef struct RNode_UNDEF { NODE node; rb_parser_ary_t *nd_undefs; + rb_code_location_t keyword_loc; } rb_node_undef_t; typedef struct RNode_CLASS { diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index c89ed0c870563b..9353d24d95225d 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -1055,7 +1055,35 @@ def run bundle "install --redownload", raise_on_error: false - expect(err).to include("The installation path is insecure. Bundler cannot continue.") + expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove") + end + end + + describe "when gems path is world writable (no sticky bit set), but previous install is just an empty dir (like it happens with default gems)", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + let(:full_path) { gems_path.join("foo-1.0.0") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem 'foo' + G + end + + it "does not try to remove the directory and thus don't abort with an error about unsafe directory removal" do + bundle "config set --local path vendor" + + FileUtils.mkdir_p(gems_path) + FileUtils.chmod(0o777, gems_path) + Dir.mkdir(full_path) + + bundle "install" end end diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index a544080fe6c1d3..83b3c06cbe82f2 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -836,6 +836,32 @@ expect(the_bundle).to include_gems "rails 2.3.2" end + it "runs the gemspec in the context of its parent directory, when using local overrides" do + build_git "foo", path: lib_path("foo"), gemspec: false do |s| + s.write lib_path("foo/lib/foo/version.rb"), %(FOO_VERSION = '1.0') + s.write "foo.gemspec", <<-G + $:.unshift Dir.pwd + require 'lib/foo/version' + Gem::Specification.new do |s| + s.name = 'foo' + s.author = 'no one' + s.version = FOO_VERSION + s.summary = 'Foo' + s.files = Dir["lib/**/*.rb"] + end + G + end + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "https://github.com/gems/foo", branch: "main" + G + + bundle %(config set local.foo #{lib_path("foo")}) + + expect(the_bundle).to include_gems "foo 1.0" + end + it "installs from git even if a rubygems gem is present" do build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s| s.write "lib/foo.rb", "raise 'FAIL'" diff --git a/spec/bundler/support/env.rb b/spec/bundler/support/env.rb index 4d99c892cd48c0..2d13c449fe9d83 100644 --- a/spec/bundler/support/env.rb +++ b/spec/bundler/support/env.rb @@ -3,7 +3,7 @@ module Spec module Env def ruby_core? - !ENV["GEM_COMMAND"].nil? + File.exist?(File.expand_path("../../../lib/bundler/bundler.gemspec", __dir__)) end end end diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb index f858a6a6bb891f..31cfd400ccc965 100644 --- a/spec/ruby/core/range/step_spec.rb +++ b/spec/ruby/core/range/step_spec.rb @@ -175,21 +175,21 @@ end describe "and String values" do - ruby_version_is ""..."3.4" do - it "yields String values incremented by #succ and less than or equal to end when not passed a step" do - ("A".."E").step { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "B", "C", "D", "E"] - end + it "yields String values incremented by #succ and less than or equal to end when not passed a step" do + ("A".."E").step { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "B", "C", "D", "E"] + end - it "yields String values incremented by #succ called Integer step times" do - ("A".."G").step(2) { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "C", "E", "G"] - end + it "yields String values incremented by #succ called Integer step times" do + ("A".."G").step(2) { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "C", "E", "G"] + end - it "raises a TypeError when passed a Float step" do - -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError) - end + it "raises a TypeError when passed a Float step" do + -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError) + end + ruby_version_is ""..."3.4" do it "calls #succ on begin and each element returned by #succ" do obj = mock("Range#step String start") obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0) @@ -201,17 +201,13 @@ end ruby_version_is "3.4" do - it "raises an ArgumentError when not passed a step" do - -> { ("A".."E").step { } }.should raise_error(ArgumentError) - end - it "yields String values adjusted by step and less than or equal to end" do ("A".."AAA").step("A") { |x| ScratchPad << x } ScratchPad.recorded.should == ["A", "AA", "AAA"] end it "raises a TypeError when passed an incompatible type step" do - -> { ("A".."G").step(2) { } }.should raise_error(TypeError) + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) end it "calls #+ on begin and each element returned by #+" do @@ -397,17 +393,13 @@ end ruby_version_is "3.4" do - it "raises an ArgumentError when not passed a step" do - -> { ("A".."E").step { } }.should raise_error(ArgumentError) - end - it "yields String values adjusted by step and less than or equal to end" do ("A"..."AAA").step("A") { |x| ScratchPad << x } ScratchPad.recorded.should == ["A", "AA"] end it "raises a TypeError when passed an incompatible type step" do - -> { ("A".."G").step(2) { } }.should raise_error(TypeError) + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) end end end @@ -482,36 +474,30 @@ end describe "and String values" do - ruby_version_is ""..."3.4" do - it "yields String values incremented by #succ and less than or equal to end when not passed a step" do - eval("('A'..)").step { |x| break if x > "D"; ScratchPad << x } - ScratchPad.recorded.should == ["A", "B", "C", "D"] + it "yields String values incremented by #succ and less than or equal to end when not passed a step" do + eval("('A'..)").step { |x| break if x > "D"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "B", "C", "D"] - ScratchPad.record [] - eval("('A'...)").step { |x| break if x > "D"; ScratchPad << x } - ScratchPad.recorded.should == ["A", "B", "C", "D"] - end + ScratchPad.record [] + eval("('A'...)").step { |x| break if x > "D"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "B", "C", "D"] + end - it "yields String values incremented by #succ called Integer step times" do - eval("('A'..)").step(2) { |x| break if x > "F"; ScratchPad << x } - ScratchPad.recorded.should == ["A", "C", "E"] + it "yields String values incremented by #succ called Integer step times" do + eval("('A'..)").step(2) { |x| break if x > "F"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "C", "E"] - ScratchPad.record [] - eval("('A'...)").step(2) { |x| break if x > "F"; ScratchPad << x } - ScratchPad.recorded.should == ["A", "C", "E"] - end + ScratchPad.record [] + eval("('A'...)").step(2) { |x| break if x > "F"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "C", "E"] + end - it "raises a TypeError when passed a Float step" do - -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError) - -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError) - end + it "raises a TypeError when passed a Float step" do + -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError) + -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError) end ruby_version_is "3.4" do - it "raises an ArgumentError when not passed a step" do - -> { ("A"..).step { } }.should raise_error(ArgumentError) - end - it "yields String values adjusted by step" do eval("('A'..)").step("A") { |x| break if x > "AAA"; ScratchPad << x } ScratchPad.recorded.should == ["A", "AA", "AAA"] @@ -522,8 +508,8 @@ end it "raises a TypeError when passed an incompatible type step" do - -> { eval("('A'..)").step(2) { } }.should raise_error(TypeError) - -> { eval("('A'...)").step(2) { } }.should raise_error(TypeError) + -> { eval("('A'..)").step([]) { } }.should raise_error(TypeError) + -> { eval("('A'...)").step([]) { } }.should raise_error(TypeError) end end end diff --git a/sprintf.c b/sprintf.c index 4fa531d53e6659..1d3b3e0d07a36b 100644 --- a/sprintf.c +++ b/sprintf.c @@ -1165,7 +1165,9 @@ ruby_vsprintf0(VALUE result, char *p, const char *fmt, va_list ap) RBASIC_SET_CLASS_RAW(result, klass); p = RSTRING_PTR(result); long blen = (char *)f._p - p; - if (scanned < blen) { + + coderange = ENC_CODERANGE(result); + if (coderange != ENC_CODERANGE_UNKNOWN && scanned < blen) { rb_str_coderange_scan_restartable(p + scanned, p + blen, rb_enc_get(result), &coderange); ENC_CODERANGE_SET(result, coderange); } diff --git a/strftime.c b/strftime.c index 33e7d3fdb8c519..edaa9f02ced73c 100644 --- a/strftime.c +++ b/strftime.c @@ -171,7 +171,9 @@ resize_buffer(VALUE ftime, char *s, const char **start, const char **endp, ptrdiff_t n, size_t maxsize) { size_t len = s - *start; - size_t nlen = len + n * 2; + size_t need = len + n * 2; + size_t nlen = rb_str_capacity(ftime); + while (nlen < need) nlen <<= 1; if (nlen < len || nlen > maxsize) { return 0; @@ -930,6 +932,7 @@ rb_strftime(const char *format, size_t format_len, rb_encoding *enc, VALUE time, const struct vtm *vtm, VALUE timev, int gmt) { VALUE result = rb_enc_str_new(0, 0, enc); + ENC_CODERANGE_CLEAR(result); return rb_strftime_with_timespec(result, format, format_len, enc, time, vtm, timev, NULL, gmt, strftime_size_limit(format_len)); @@ -940,6 +943,7 @@ rb_strftime_timespec(const char *format, size_t format_len, rb_encoding *enc, VALUE time, const struct vtm *vtm, struct timespec *ts, int gmt) { VALUE result = rb_enc_str_new(0, 0, enc); + ENC_CODERANGE_CLEAR(result); return rb_strftime_with_timespec(result, format, format_len, enc, time, vtm, Qnil, ts, gmt, strftime_size_limit(format_len)); diff --git a/string.c b/string.c index 556a348a131a89..5ccafad3835d64 100644 --- a/string.c +++ b/string.c @@ -138,10 +138,10 @@ VALUE rb_cSymbol; } while (0) static inline bool -str_enc_fastpath(VALUE str) +str_encindex_fastpath(int encindex) { // The overwhelming majority of strings are in one of these 3 encodings. - switch (ENCODING_GET_INLINED(str)) { + switch (encindex) { case ENCINDEX_ASCII_8BIT: case ENCINDEX_UTF_8: case ENCINDEX_US_ASCII: @@ -151,6 +151,12 @@ str_enc_fastpath(VALUE str) } } +static inline bool +str_enc_fastpath(VALUE str) +{ + return str_encindex_fastpath(ENCODING_GET_INLINED(str)); +} + #define TERM_LEN(str) (str_enc_fastpath(str) ? 1 : rb_enc_mbminlen(rb_enc_from_index(ENCODING_GET(str)))) #define TERM_FILL(ptr, termlen) do {\ char *const term_fill_ptr = (ptr);\ @@ -872,16 +878,24 @@ rb_enc_str_coderange(VALUE str) return cr; } +static inline bool +rb_enc_str_asciicompat(VALUE str) +{ + int encindex = ENCODING_GET_INLINED(str); + return str_encindex_fastpath(encindex) || rb_enc_asciicompat(rb_enc_get_from_index(encindex)); +} + int rb_enc_str_asciionly_p(VALUE str) { - rb_encoding *enc = STR_ENC_GET(str); - - if (!rb_enc_asciicompat(enc)) - return FALSE; - else if (is_ascii_string(str)) - return TRUE; - return FALSE; + switch(ENC_CODERANGE(str)) { + case ENC_CODERANGE_UNKNOWN: + return rb_enc_str_asciicompat(str) && is_ascii_string(str); + case ENC_CODERANGE_7BIT: + return true; + default: + return false; + } } static inline void @@ -4355,7 +4369,19 @@ rb_str_byteindex_m(int argc, VALUE *argv, VALUE str) return Qnil; } -#ifdef HAVE_MEMRCHR +#ifndef HAVE_MEMRCHR +static void* +memrchr(const char *search_str, int chr, long search_len) +{ + const char *ptr = search_str + search_len; + do { + if ((unsigned char)*(--ptr) == chr) return (void *)ptr; + } while (ptr >= search_str); + + return ((void *)0); +} +#endif + static long str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) { @@ -4372,6 +4398,10 @@ str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) c = *t & 0xff; searchlen = s - sbeg + 1; + if (memcmp(s, t, slen) == 0) { + return s - sbeg; + } + do { hit = memrchr(sbeg, c, searchlen); if (!hit) break; @@ -4387,29 +4417,6 @@ str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) return -1; } -#else -static long -str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) -{ - long slen; - char *sbeg, *e, *t; - - sbeg = RSTRING_PTR(str); - e = RSTRING_END(str); - t = RSTRING_PTR(sub); - slen = RSTRING_LEN(sub); - - while (s) { - if (memcmp(s, t, slen) == 0) { - return s - sbeg; - } - if (s <= sbeg) break; - s = rb_enc_prev_char(sbeg, s, e, enc); - } - - return -1; -} -#endif /* found index in byte */ static long @@ -4483,7 +4490,7 @@ rb_str_rindex(VALUE str, VALUE sub, long pos) * $~ #=> # * * Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the - * string to _end_ the search: + * string to _end_ the search: * * 'foo'.rindex('o', 0) # => nil * 'foo'.rindex('o', 1) # => 1 @@ -4616,7 +4623,7 @@ rb_str_byterindex(VALUE str, VALUE sub, long pos) * $~ #=> # * * Integer argument +offset+, if given and non-negative, specifies the maximum starting byte-based position in the - * string to _end_ the search: + * string to _end_ the search: * * 'foo'.byterindex('o', 0) # => nil * 'foo'.byterindex('o', 1) # => 1 diff --git a/test/io/console/test_ractor.rb b/test/io/console/test_ractor.rb new file mode 100644 index 00000000000000..b30988f47e0c11 --- /dev/null +++ b/test/io/console/test_ractor.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true +require 'test/unit' +require 'rbconfig' + +class TestIOConsoleInRactor < Test::Unit::TestCase + def test_ractor + ext = "/io/console.#{RbConfig::CONFIG['DLEXT']}" + path = $".find {|path| path.end_with?(ext)} + assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], []) + begin; + $VERBOSE = nil + r = Ractor.new do + $stdout.console_mode + rescue SystemCallError + true + rescue Ractor::UnsafeError + false + else + true # should not success + end + puts r.take + end; + + assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], []) + begin; + console = IO.console + $VERBOSE = nil + r = Ractor.new do + IO.console + end + puts console.class == r.take.class + end; + end +end if defined? Ractor diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 567c3216ccec2f..69931e3e4f95a7 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -765,7 +765,7 @@ def test_show_doc ensure # this is the only way to reset the redefined method without coupling the test with its implementation EnvUtil.suppress_warning { load "irb/command/help.rb" } - end + end if defined?(RDoc) def test_show_doc_without_rdoc _, err = without_rdoc do diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 9d0f393ce001c8..bd107551df4733 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -1,7 +1,10 @@ # frozen_string_literal: false require "irb" -require "rdoc" +begin + require "rdoc" +rescue LoadError +end require_relative "helper" module TestIRB @@ -67,6 +70,7 @@ def test_initialization_without_use_autocomplete end def test_initialization_with_use_autocomplete + omit 'This test requires RDoc' unless defined?(RDoc) original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc empty_proc = Proc.new {} Reline.add_dialog_proc(:show_doc, empty_proc) @@ -187,5 +191,5 @@ def test_perfect_matching_handles_nil_namespace def has_rdoc_content? File.exist?(RDoc::RI::Paths::BASE) end - end + end if defined?(RDoc) end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 526bb8c1febd2c..19b4c7434f5272 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -243,7 +243,7 @@ def test_buffer_initial_length def test_gc if respond_to?(:assert_in_out_err) && !(RUBY_PLATFORM =~ /java/) - assert_in_out_err(%w[-rjson], <<-EOS, [], []) + assert_in_out_err(%w[-rjson -Ilib -Iext], <<-EOS, [], []) bignum_too_long_to_embed_as_string = 1234567890123456789012345 expect = bignum_too_long_to_embed_as_string.to_s GC.stress = true diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb index c14069d11704b3..68a102a599cf17 100644 --- a/test/reline/test_config.rb +++ b/test/reline/test_config.rb @@ -13,6 +13,7 @@ def setup Dir.chdir(@tmpdir) Reline.test_mode @config = Reline::Config.new + @inputrc_backup = ENV['INPUTRC'] end def teardown @@ -20,10 +21,15 @@ def teardown FileUtils.rm_rf(@tmpdir) Reline.test_reset @config.reset + ENV['INPUTRC'] = @inputrc_backup + end + + def get_config_variable(variable) + @config.instance_variable_get(variable) end def additional_key_bindings(keymap_label) - @config.instance_variable_get(:@additional_key_bindings)[keymap_label].instance_variable_get(:@key_bindings) + get_config_variable(:@additional_key_bindings)[keymap_label].instance_variable_get(:@key_bindings) end def registered_key_bindings(keys) @@ -36,7 +42,7 @@ def test_read_lines set show-mode-in-prompt on LINES - assert_equal true, @config.instance_variable_get(:@show_mode_in_prompt) + assert_equal true, get_config_variable(:@show_mode_in_prompt) end def test_read_lines_with_variable @@ -44,7 +50,7 @@ def test_read_lines_with_variable set disable-completion on LINES - assert_equal true, @config.instance_variable_get(:@disable_completion) + assert_equal true, get_config_variable(:@disable_completion) end def test_string_value @@ -53,7 +59,7 @@ def test_string_value set emacs-mode-string Emacs LINES - assert_equal 'Emacs', @config.instance_variable_get(:@emacs_mode_string) + assert_equal 'Emacs', get_config_variable(:@emacs_mode_string) end def test_string_value_with_brackets @@ -62,7 +68,7 @@ def test_string_value_with_brackets set emacs-mode-string [Emacs] LINES - assert_equal '[Emacs]', @config.instance_variable_get(:@emacs_mode_string) + assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string) end def test_string_value_with_brackets_and_quotes @@ -71,7 +77,7 @@ def test_string_value_with_brackets_and_quotes set emacs-mode-string "[Emacs]" LINES - assert_equal '[Emacs]', @config.instance_variable_get(:@emacs_mode_string) + assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string) end def test_string_value_with_parens @@ -80,7 +86,7 @@ def test_string_value_with_parens set emacs-mode-string (Emacs) LINES - assert_equal '(Emacs)', @config.instance_variable_get(:@emacs_mode_string) + assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string) end def test_string_value_with_parens_and_quotes @@ -89,7 +95,7 @@ def test_string_value_with_parens_and_quotes set emacs-mode-string "(Emacs)" LINES - assert_equal '(Emacs)', @config.instance_variable_get(:@emacs_mode_string) + assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string) end def test_encoding_is_ascii @@ -103,7 +109,7 @@ def test_encoding_is_ascii def test_encoding_is_not_ascii @config = Reline::Config.new - assert_equal nil, @config.convert_meta + assert_equal false, @config.convert_meta end def test_invalid_keystroke @@ -167,7 +173,7 @@ def test_include $include included_partial LINES - assert_equal true, @config.instance_variable_get(:@show_mode_in_prompt) + assert_equal true, get_config_variable(:@show_mode_in_prompt) end def test_include_expand_path @@ -182,7 +188,7 @@ def test_include_expand_path $include ~/included_partial LINES - assert_equal true, @config.instance_variable_get(:@show_mode_in_prompt) + assert_equal true, get_config_variable(:@show_mode_in_prompt) ensure ENV['HOME'] = home_backup end @@ -196,7 +202,7 @@ def test_if $endif LINES - assert_equal '(cmd)', @config.instance_variable_get(:@vi_cmd_mode_string) + assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string) end def test_if_with_false @@ -208,7 +214,7 @@ def test_if_with_false $endif LINES - assert_equal '[cmd]', @config.instance_variable_get(:@vi_cmd_mode_string) + assert_equal '[cmd]', get_config_variable(:@vi_cmd_mode_string) end def test_if_with_indent @@ -222,7 +228,7 @@ def test_if_with_indent $endif LINES - assert_equal '(cmd)', @config.instance_variable_get(:@vi_cmd_mode_string) + assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string) end end @@ -444,7 +450,7 @@ def test_history_size set history-size 5000 LINES - assert_equal 5000, @config.instance_variable_get(:@history_size) + assert_equal 5000, get_config_variable(:@history_size) history = Reline::History.new(@config) history << "a\n" assert_equal 1, history.size @@ -475,7 +481,7 @@ def test_inputrc_raw_value set vi-ins-mode-string aaa aaa set vi-cmd-mode-string bbb ccc # comment LINES - assert_equal :vi_insert, @config.instance_variable_get(:@editing_mode_label) + assert_equal :vi_insert, get_config_variable(:@editing_mode_label) assert_equal 'aaa aaa', @config.vi_ins_mode_string assert_equal 'bbb ccc # comment', @config.vi_cmd_mode_string end @@ -562,4 +568,21 @@ def test_relative_xdg_config_home ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup ENV['HOME'] = home_backup end + + def test_reload + inputrc = "#{@tmpdir}/inputrc" + ENV['INPUTRC'] = inputrc + + File.write(inputrc, "set emacs-mode-string !") + @config.read + assert_equal '!', @config.emacs_mode_string + + File.write(inputrc, "set emacs-mode-string ?") + @config.reload + assert_equal '?', @config.emacs_mode_string + + File.write(inputrc, "") + @config.reload + assert_equal '@', @config.emacs_mode_string + end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 883fcf53fb4ec9..c90d3d6a7f1092 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -1841,6 +1841,40 @@ def test_print_before_readline EOC end + def test_pre_input_hook_with_redisplay + code = <<~'RUBY' + puts 'Multiline REPL.' + Reline.pre_input_hook = -> do + Reline.insert_text 'abc' + Reline.redisplay # Reline doesn't need this but Readline requires calling redisplay + end + Reline.readline('prompt> ') + RUBY + start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.') + assert_screen(<<~EOC) + Multiline REPL. + prompt> abc + EOC + end + + def test_pre_input_hook_with_multiline_text_insert + # Frequently used pattern of pre_input_hook + code = <<~'RUBY' + puts 'Multiline REPL.' + Reline.pre_input_hook = -> do + Reline.insert_text "abc\nef" + end + Reline.readline('>') + RUBY + start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.') + write("\C-ad") + assert_screen(<<~EOC) + Multiline REPL. + >abc + def + EOC + end + def test_thread_safe start_terminal(6, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') write("[Thread.new{Reline.readline'>'},Thread.new{Reline.readmultiline('>'){true}}].map(&:join).size\n") diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 936dd6d3e661aa..d1422fc2c0d6fc 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1330,6 +1330,11 @@ def test_lineno_and_column assert_locations(node.locations, [[1, 0, 1, 5]]) end + def test_alias_locations + node = RubyVM::AbstractSyntaxTree.parse("alias foo bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + end + def test_unless_locations node = RubyVM::AbstractSyntaxTree.parse("unless cond then 1 else 2 end") assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]]) @@ -1338,6 +1343,22 @@ def test_unless_locations assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 2, 1, 8], nil, nil]) end + def test_undef_locations + node = RubyVM::AbstractSyntaxTree.parse("undef foo") + assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 5]]) + + node = RubyVM::AbstractSyntaxTree.parse("undef foo, bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 14], [1, 0, 1, 5]]) + end + + def test_valias_locations + node = RubyVM::AbstractSyntaxTree.parse("alias $foo $bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 5]]) + + node = RubyVM::AbstractSyntaxTree.parse("alias $foo $&") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + end + private def assert_locations(locations, expected) ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] } diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 9029f2bda800b1..43444fe8e74d38 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -374,15 +374,17 @@ def test_latest_gc_info_need_major_by objects.append(100.times.map { '*' }) end - # We need to ensure that no GC gets ran before the call to GC.start since - # it would trigger a major GC. Assertions could allocate objects and - # trigger a GC so we don't run assertions until we perform the major GC. - need_major_by = GC.latest_gc_info(:need_major_by) - GC.start(full_mark: false) # should be upgraded to major - major_by = GC.latest_gc_info(:major_by) - - assert_not_nil(need_major_by) - assert_not_nil(major_by) + EnvUtil.without_gc do + # We need to ensure that no GC gets ran before the call to GC.start since + # it would trigger a major GC. Assertions could allocate objects and + # trigger a GC so we don't run assertions until we perform the major GC. + need_major_by = GC.latest_gc_info(:need_major_by) + GC.start(full_mark: false) # should be upgraded to major + major_by = GC.latest_gc_info(:major_by) + + assert_not_nil(need_major_by) + assert_not_nil(major_by) + end end def test_latest_gc_info_weak_references_count diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 8b4dc1ed98f746..1ca05ae362f6b6 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -3876,8 +3876,10 @@ def test_close_uninitialized end def test_open_fifo_does_not_block_other_threads - mkcdtmpdir { + mkcdtmpdir do File.mkfifo("fifo") + rescue NotImplementedError + else assert_separately([], <<-'EOS') t1 = Thread.new { open("fifo", "r") {|r| @@ -3892,8 +3894,32 @@ def test_open_fifo_does_not_block_other_threads t1_value, _ = assert_join_threads([t1, t2]) assert_equal("foo", t1_value) EOS - } - end if /mswin|mingw|bccwin|cygwin/ !~ RUBY_PLATFORM + end + end + + def test_open_fifo_restart_at_signal_intterupt + mkcdtmpdir do + File.mkfifo("fifo") + rescue NotImplementedError + else + wait = EnvUtil.apply_timeout_scale(0.1) + data = "writing to fifo" + + # Do not use assert_separately, because reading from stdin + # prevents to reproduce [Bug #20708] + assert_in_out_err(["-e", "#{<<~"begin;"}\n#{<<~'end;'}"], [], [data]) + wait, data = #{wait}, #{data.dump} + ; + begin; + trap(:USR1) {} + Thread.new do + sleep wait; Process.kill(:USR1, $$) + sleep wait; File.write("fifo", data) + end + puts File.read("fifo") + end; + end + end if Signal.list[:USR1] # Pointless on platforms without signal def test_open_flag make_tempfile do |t| diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index b7541424d113d5..6c591b31550bdd 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -427,11 +427,11 @@ def test_step_non_numeric_range assert_raise(ArgumentError) { (...'aaa').step('a') } # step is not provided - assert_raise(ArgumentError) { ('a'...'aaaa').step } + assert_raise(ArgumentError) { (Time.new(2022)...Time.new(2023)).step } # step is incompatible - assert_raise(TypeError) { ('a'...'aaaa').step(1) {} } - assert_raise(TypeError) { ('a'...'aaaa').step(1).to_a } + assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a') {} } + assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a').to_a } # step is compatible, but shouldn't convert into numeric domain: a = [] @@ -467,6 +467,58 @@ def test_step_non_numeric_range assert_equal([], a) end + def test_step_string_legacy + # finite + a = [] + ('a'..'g').step(2) { a << _1 } + assert_equal(%w[a c e g], a) + + assert_kind_of(Enumerator, ('a'..'g').step(2)) + assert_equal(%w[a c e g], ('a'..'g').step(2).to_a) + + a = [] + ('a'...'g').step(2) { a << _1 } + assert_equal(%w[a c e], a) + + assert_kind_of(Enumerator, ('a'...'g').step(2)) + assert_equal(%w[a c e], ('a'...'g').step(2).to_a) + + # endless + a = [] + ('a'...).step(2) { a << _1; break if a.size == 3 } + assert_equal(%w[a c e], a) + + assert_kind_of(Enumerator, ('a'...).step(2)) + assert_equal(%w[a c e], ('a'...).step(2).take(3)) + + # beginless + assert_raise(ArgumentError) { (...'g').step(2) {} } + assert_raise(ArgumentError) { (...'g').step(2) } + + # step is not provided + a = [] + ('a'..'d').step { a << _1 } + assert_equal(%w[a b c d], a) + + assert_kind_of(Enumerator, ('a'..'d').step) + assert_equal(%w[a b c d], ('a'..'d').step.to_a) + + a = [] + ('a'...'d').step { a << _1 } + assert_equal(%w[a b c], a) + + assert_kind_of(Enumerator, ('a'...'d').step) + assert_equal(%w[a b c], ('a'...'d').step.to_a) + + # endless + a = [] + ('a'...).step { a << _1; break if a.size == 3 } + assert_equal(%w[a b c], a) + + assert_kind_of(Enumerator, ('a'...).step) + assert_equal(%w[a b c], ('a'...).step.take(3)) + end + def test_step_bug15537 assert_equal([10.0, 9.0, 8.0, 7.0], (10 ..).step(-1.0).take(4)) assert_equal([10.0, 9.0, 8.0, 7.0], (10.0 ..).step(-1).take(4)) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 5b09512ac45c24..1bd96600f33983 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -667,7 +667,7 @@ def test_execute_rdoc assert_path_exist File.join(a2.doc_dir, "ri") assert_path_exist File.join(a2.doc_dir, "rdoc") - end + end if defined?(Gem::RDoc) def test_execute_rdoc_with_path specs = spec_fetcher do |fetcher| @@ -703,7 +703,7 @@ def test_execute_rdoc_with_path wait_for_child_process_to_exit assert_path_exist "whatever/doc/a-2", "documentation not installed" - end + end if defined?(Gem::RDoc) def test_execute_saves_build_args specs = spec_fetcher do |fetcher| diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 194ebb030a8fed..642a62a3736643 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -506,7 +506,7 @@ def test_execute_rdoc a2 = @specs["a-2"] assert_path_exist File.join(a2.doc_dir, "rdoc") - end + end if defined?(Gem::RDoc) def test_execute_named spec_fetcher do |fetcher| diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 52498cdd6d6c8f..a61d1b6fff28d6 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -2386,10 +2386,10 @@ def test_leaves_no_empty_cached_spec_when_no_more_disk_space installer = Gem::Installer.for_spec @spec installer.gem_home = @gemhome - File.class_eval do - alias_method :original_write, :write + File.singleton_class.class_eval do + alias_method :original_binwrite, :binwrite - def write(data) + def binwrite(path, data) raise Errno::ENOSPC end end @@ -2400,10 +2400,10 @@ def write(data) assert_path_not_exist @spec.spec_file ensure - File.class_eval do - remove_method :write - alias_method :write, :original_write - remove_method :original_write + File.singleton_class.class_eval do + remove_method :binwrite + alias_method :binwrite, :original_binwrite + remove_method :original_binwrite end end diff --git a/test/rubygems/test_gem_rdoc.rb b/test/rubygems/test_gem_rdoc.rb index f9b1df6cd55aa1..19ccf1e5871b70 100644 --- a/test/rubygems/test_gem_rdoc.rb +++ b/test/rubygems/test_gem_rdoc.rb @@ -134,4 +134,4 @@ def test_setup_unwritable FileUtils.rm_r @a.doc_dir end end -end +end if defined?(Gem::RDoc) diff --git a/test/rubygems/test_gem_safe_marshal.rb b/test/rubygems/test_gem_safe_marshal.rb index ebb000a9efba8a..1085aca9fd705e 100644 --- a/test/rubygems/test_gem_safe_marshal.rb +++ b/test/rubygems/test_gem_safe_marshal.rb @@ -305,6 +305,18 @@ def test_rational end end + def test_gem_spec_unmarshall_license + spec = Gem::Specification.new do |s| + s.name = "hi" + s.version = "1.2.3" + s.license = "MIT" + end + + unmarshalled_spec = Gem::SafeMarshal.safe_load(Marshal.dump(spec)) + + assert_equal ["MIT"], unmarshalled_spec.license + end + def test_gem_spec_disallowed_symbol e = assert_raise(Gem::SafeMarshal::Visitors::ToRuby::UnpermittedSymbolError) do spec = Gem::Specification.new do |s| diff --git a/test/rubygems/test_gem_source_git.rb b/test/rubygems/test_gem_source_git.rb index 20e750a0d4000d..abcd55907ed096 100644 --- a/test/rubygems/test_gem_source_git.rb +++ b/test/rubygems/test_gem_source_git.rb @@ -292,6 +292,12 @@ def test_uri assert_equal Gem::URI(@repository), @source.uri end + def test_pretty_print + assert_equal "#\n", @source.pretty_inspect + end + def test_uri_hash assert_equal @hash, @source.uri_hash diff --git a/test/rubygems/test_gem_source_installed.rb b/test/rubygems/test_gem_source_installed.rb index 0d6171b0e52682..0ef14d7470cc92 100644 --- a/test/rubygems/test_gem_source_installed.rb +++ b/test/rubygems/test_gem_source_installed.rb @@ -32,4 +32,9 @@ def test_spaceship assert_equal(1, vendor.<=>(installed), "vendor <=> installed") assert_equal(-1, installed.<=>(vendor), "installed <=> vendor") end + + def test_pretty_print + local = Gem::Source::Installed.new + assert_equal "#\n", local.pretty_inspect + end end diff --git a/test/rubygems/test_gem_source_local.rb b/test/rubygems/test_gem_source_local.rb index c15e0e07c0a7f7..ed6aa24f940db1 100644 --- a/test/rubygems/test_gem_source_local.rb +++ b/test/rubygems/test_gem_source_local.rb @@ -104,4 +104,9 @@ def test_spaceship assert_equal(-1, specific.<=>(local), "specific <=> local") assert_equal(1, local.<=>(specific), "local <=> specific") end + + def test_pretty_print + local = Gem::Source::Local.new + assert_equal "#\n", local.pretty_inspect + end end diff --git a/test/rubygems/test_gem_source_specific_file.rb b/test/rubygems/test_gem_source_specific_file.rb index 3bc1901ee19926..bcc41684444f47 100644 --- a/test/rubygems/test_gem_source_specific_file.rb +++ b/test/rubygems/test_gem_source_specific_file.rb @@ -73,4 +73,8 @@ def test_spaceship assert_equal(0, a1_source.<=>(a1_source), "a1_source <=> a1_source") # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands assert_equal(1, a2_source.<=>(a1_source), "a2_source <=> a1_source") end + + def test_pretty_print + assert_equal "#\n", @sf.pretty_inspect + end end diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index 7f9dc53cae266b..6a016da71d380b 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -70,7 +70,7 @@ def test_initialize_resolv_timeout end def test_initialize_connect_timeout - assert_raise(IO::TimeoutError, Errno::ENETUNREACH) do + assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do TCPSocket.new("192.0.2.1", 80, connect_timeout: 0) end end diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index f02d02656d2e45..9bb3d3b037afb6 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -10,6 +10,7 @@ gem "optparse", "0.4.0" gem "pub_grub", github: "jhawthorn/pub_grub" gem "resolv", "0.4.0" +gem "securerandom", "0.3.1" gem "timeout", "0.4.1" gem "thor", "1.3.0" gem "tsort", "0.2.0" diff --git a/tool/format-release b/tool/format-release index 737148e0ce4674..258c25406d2649 100755 --- a/tool/format-release +++ b/tool/format-release @@ -1,6 +1,12 @@ #!/usr/bin/env ruby -# https://rubygems.org/gems/diffy -require "diffy" + +require "bundler/inline" + +gemfile do + source "https://rubygems.org" + gem "diffy" +end + require "open-uri" require "yaml" diff --git a/tool/lib/path.rb b/tool/lib/path.rb index 5582b2851e46ba..f16a164338d66a 100644 --- a/tool/lib/path.rb +++ b/tool/lib/path.rb @@ -40,7 +40,7 @@ def clean_link(src, dest) # Extensions to FileUtils module Mswin - def ln_safe(src, dest, *opt) + def ln_safe(src, dest, real_src, *opt) cmd = ["mklink", dest.tr("/", "\\"), src.tr("/", "\\")] cmd[1, 0] = opt return if system("cmd", "/c", *cmd) @@ -48,23 +48,23 @@ def ln_safe(src, dest, *opt) puts cmd.join(" ") end - def ln_dir_safe(src, dest) + def ln_dir_safe(src, dest, real_src) ln_safe(src, dest, "/d") end end module HardlinkExcutable - def ln_exe(src, dest) + def ln_exe(relative_src, dest, src) ln(src, dest, force: true) end end - def ln_safe(src, dest) + def ln_safe(src, dest, real_src) ln_sf(src, dest) rescue Errno::ENOENT # Windows disallows to create broken symboic links, probably because # it is a kind of reparse points. - raise if File.exist?(src) + raise if File.exist?(real_src) end alias ln_dir_safe ln_safe @@ -75,16 +75,16 @@ def ln_relative(src, dest, executable = false) parent = File.dirname(dest) File.directory?(parent) or mkdir_p(parent) if executable - return (ln_exe(src, dest) if File.exist?(src)) + return (ln_exe(relative(src, parent), dest, src) if File.exist?(src)) end - clean_link(relative(src, parent), dest) {|s, d| ln_safe(s, d)} + clean_link(relative(src, parent), dest) {|s, d| ln_safe(s, d, src)} end def ln_dir_relative(src, dest) return if File.identical?(src, dest) parent = File.dirname(dest) File.directory?(parent) or mkdir_p(parent) - clean_link(relative(src, parent), dest) {|s, d| ln_dir_safe(s, d)} + clean_link(relative(src, parent), dest) {|s, d| ln_dir_safe(s, d, src)} end case (CROSS_COMPILING || RUBY_PLATFORM) diff --git a/tool/lib/test/unit/assertions.rb b/tool/lib/test/unit/assertions.rb index aad422f7e7aa33..fe3351107ec407 100644 --- a/tool/lib/test/unit/assertions.rb +++ b/tool/lib/test/unit/assertions.rb @@ -522,7 +522,7 @@ def refute_same exp, act, msg = nil # Skips the current test. Gets listed at the end of the run but # doesn't cause a failure exit code. - def pend msg = nil, bt = caller + def pend msg = nil, bt = caller, &_ msg ||= "Skipped, no message given" @skip = true raise Test::Unit::PendedError, msg, bt diff --git a/tool/redmine-backporter.rb b/tool/redmine-backporter.rb index 73d375b146f22c..7f08eb8d1a956b 100755 --- a/tool/redmine-backporter.rb +++ b/tool/redmine-backporter.rb @@ -351,27 +351,22 @@ class << @changesets next end - if rev.nil? && log = find_git_log("##@issue]") - /^commit (?\h{40})$/ =~ log - end - if log && rev - str = log[/merge revision\(s\) ([^:]+)(?=:)/] - if str - str.sub!(/\Amerge/, 'merged') - str.gsub!(/\h{8,40}/, 'commit:\0') - str = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev} #{str}." + if rev && has_commit(rev, "ruby_#{TARGET_VERSION.tr('.','_')}") + notes = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev}." + elsif rev.nil? && (log = find_git_log("##@issue]")) && !(revs = log.scan(/^commit (\h{40})$/).flatten).empty? + commits = revs.map { |rev| "commit:#{rev}" }.join(", ") + if merged_revs = log[/merge revision\(s\) ([^:]+)(?=:)/] + merged_revs.sub!(/\Amerge/, 'merged') + merged_revs.gsub!(/\h{8,40}/, 'commit:\0') + str = "ruby_#{TARGET_VERSION.tr('.','_')} #{commits} #{merged_revs}." else - str = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev}." + str = "ruby_#{TARGET_VERSION.tr('.','_')} #{commits}." end if notes str << "\n" str << notes end notes = str - elsif rev && has_commit(rev, "ruby_#{TARGET_VERSION.tr('.','_')}") - # Backport commit's log doesn't have the issue number. - # Instead of that manually it's provided. - notes = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev}." else puts "no commit is found whose log include ##@issue" next diff --git a/tool/release.sh b/tool/release.sh index 6b8cf3ea504bfc..d467d8f24b8b38 100755 --- a/tool/release.sh +++ b/tool/release.sh @@ -5,6 +5,11 @@ # tool/release.sh 3.0.0-rc1 EXTS='.tar.gz .tar.xz .zip' +if [[ -n $AWS_ACCESS_KEY_ID ]]; then + AWS_CLI_OPTS="" +else + AWS_CLI_OPTS="--profile ruby" +fi ver=$1 if [[ $ver =~ ^([1-9]\.[0-9])\.([0-9]|[1-9][0-9]|0-(preview[1-9]|rc[1-9]))$ ]]; then @@ -18,5 +23,5 @@ short=${BASH_REMATCH[1]} echo $ver echo $short for ext in $EXTS; do - aws --profile ruby s3 cp s3://ftp.r-l.o/pub/tmp/ruby-$ver-draft$ext s3://ftp.r-l.o/pub/ruby/$short/ruby-$ver$ext + aws $AWS_CLI_OPTS s3 cp s3://ftp.r-l.o/pub/tmp/ruby-$ver-draft$ext s3://ftp.r-l.o/pub/ruby/$short/ruby-$ver$ext done diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 11a3a1a0da8727..8c43052d561b9a 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -397,11 +397,38 @@ def sync_default_gems(gem) sync_lib gem, upstream end + check_prerelease_version(gem) + # Architecture-dependent files must not pollute libdir. rm_rf(Dir["lib/**/*.#{RbConfig::CONFIG['DLEXT']}"]) replace_rdoc_ref_all end + def check_prerelease_version(gem) + return if gem == "rubygems" + + gem = gem.downcase + + require "net/https" + require "json" + require "uri" + + uri = URI("https://rubygems.org/api/v1/versions/#{gem}/latest.json") + response = Net::HTTP.get(uri) + latest_version = JSON.parse(response)["version"] + + gemspec = [ + "lib/#{gem}/#{gem}.gemspec", + "lib/#{gem}.gemspec", + "ext/#{gem}/#{gem}.gemspec", + "ext/#{gem.split("-").join("/")}/#{gem}.gemspec", + "lib/#{gem.split("-").first}/#{gem}.gemspec", + "lib/#{gem.split("-").join("/")}/#{gem}.gemspec", + ].find{|gemspec| File.exist?(gemspec)} + spec = Gem::Specification.load(gemspec) + puts "#{gem}-#{spec.version} is not latest version of rubygems.org" if spec.version.to_s != latest_version + end + def ignore_file_pattern_for(gem) patterns = [] diff --git a/vm.c b/vm.c index 726098b3e3dc22..c1dcfeea997767 100644 --- a/vm.c +++ b/vm.c @@ -4263,7 +4263,7 @@ Init_VM(void) rb_define_global_const("TOPLEVEL_BINDING", rb_binding_new()); #ifdef _WIN32 - rb_objspace_gc_enable(vm->objspace); + rb_objspace_gc_enable(vm->gc.objspace); #endif } vm_init_redefined_flag(); diff --git a/vm_eval.c b/vm_eval.c index 0962369f12493c..bf1628a26bfb83 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1721,7 +1721,9 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, // Add our empty local scope at the very end of the array for our eval // scope's locals. pm_options_scope_init(&result.options.scopes[scopes_count], 0); - VALUE error = pm_parse_string(&result, src, fname, NULL); + + VALUE script_lines; + VALUE error = pm_parse_string(&result, src, fname, ruby_vm_keep_script_lines ? &script_lines : NULL); // If the parse failed, clean up and raise. if (error != Qnil) {