diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index 1de7fce1d34dd3..17f749d69ebf60 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -74,64 +74,6 @@ btests='' tests='' spec_opts='' -# Launchable -launchable_record_session() { - launchable record session \ - --build "${build_name}" \ - --flavor test_task=$1 \ - --flavor workflow=Compilations \ - --flavor with-gcc="${INPUT_WITH_GCC}" \ - --flavor CFLAGS="${INPUT_CFLAGS}" \ - --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \ - --flavor optflags="${INPUT_OPTFLAGS}" \ - --flavor cppflags="${INPUT_CPPFLAGS}" \ - --test-suite ${2-$1} -} -setup_launchable() { - pushd ${srcdir} - # To prevent a slowdown in CI, disable request retries when the Launchable server is unstable. - export LAUNCHABLE_SKIP_TIMEOUT_RETRY=1 - export LAUNCHABLE_COMMIT_TIMEOUT=1 - # Launchable creates .launchable file in the current directory, but cannot a file to ${srcdir} directory. - # As a workaround, we set LAUNCHABLE_SESSION_DIR to ${builddir}. - export LAUNCHABLE_SESSION_DIR=${builddir} - local github_ref="${GITHUB_REF//\//_}" - local build_name="${github_ref}"_"${GITHUB_PR_HEAD_SHA}" - launchable record build --name "${build_name}" || true - btest_session=$(launchable_record_session test btest) \ - && btests+=--launchable-test-reports="${btest_report_path}" || : - if [ "$INPUT_CHECK" = "true" ]; then - test_all_session=$(launchable_record_session test-all) \ - && tests+=--launchable-test-reports="${test_report_path}" || : - mkdir "${builddir}"/"${test_spec_report_path}" - test_spec_session=$(launchable_record_session test-spec) \ - && spec_opts+=--launchable-test-reports="${test_spec_report_path}" || : - fi -} -launchable_record_test() { - pushd "${builddir}" - grouped launchable record tests --session "${btest_session}" raw "${btest_report_path}" || true - if [ "$INPUT_CHECK" = "true" ]; then - grouped launchable record tests --session "${test_all_session}" raw "${test_report_path}" || true - grouped launchable record tests --session "${test_spec_session}" raw "${test_spec_report_path}"/* || true - fi -} -if [ "$LAUNCHABLE_ENABLED" = "true" ]; then - echo "::group::Setup Launchable" - btest_report_path='launchable_bootstraptest.json' - test_report_path='launchable_test_all.json' - test_spec_report_path='launchable_test_spec_report' - setup_pid=$$ - (sleep 180; echo "setup_launchable timed out; killing"; kill -INT "-$setup_pid" 2> /dev/null) & sleep_pid=$! - launchable_failed=false - trap "launchable_failed=true" INT - setup_launchable - kill "$sleep_pid" 2> /dev/null - trap - INT - echo "::endgroup::" - $launchable_failed || trap launchable_record_test EXIT -fi - pushd ${builddir} grouped make showflags diff --git a/.github/actions/setup/macos/action.yml b/.github/actions/setup/macos/action.yml index 5da7c6d44cfb2c..d0072ff82890c4 100644 --- a/.github/actions/setup/macos/action.yml +++ b/.github/actions/setup/macos/action.yml @@ -21,7 +21,7 @@ runs: dir_config() { local args=() lib var="$1"; shift for lib in "$@"; do - args+="--with-${lib%@*}-dir=$(brew --prefix $lib)" + args+=("--with-${lib%@*}-dir=$(brew --prefix $lib)") done echo "$var=${args[*]}" >> $GITHUB_ENV } diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 4dd1fdd2e78d37..d0be762ceeb7c2 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -78,11 +78,11 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - name: 'GCC 13 LTO' + - name: 'GCC 15 LTO' uses: './.github/actions/compilers' with: - tag: gcc-13 - with_gcc: 'gcc-13 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' + tag: gcc-15 + with_gcc: 'gcc-15 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' optflags: '-O2' enable_shared: false - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' } } diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3b43080201e510..8cc7e00c47ec26 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f2ea147fec3c2f0d459703eba7405b5e9bcd8c8f # v2.4.2 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9deb2c6c27a9e1..7c8fd75d2df2bf 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -121,7 +121,7 @@ jobs: - name: Restore vcpkg artifact uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: - path: ${{ github.workspace }}/src/vcpkg_installed + path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} - name: Install libraries with vcpkg diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index eadb23ce864d97..0c7c2e32abd93e 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -108,37 +108,37 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ + ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_exception.rb \ ../src/bootstraptest/test_fiber.rb \ ../src/bootstraptest/test_finalizer.rb \ ../src/bootstraptest/test_flip.rb \ ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ + ../src/bootstraptest/test_massign.rb \ + ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ + ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb - # ../src/bootstraptest/test_block.rb \ - # ../src/bootstraptest/test_class.rb \ # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_exception.rb \ - # ../src/bootstraptest/test_gc.rb \ - # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_load.rb \ - # ../src/bootstraptest/test_massign.rb \ - # ../src/bootstraptest/test_method.rb \ - # ../src/bootstraptest/test_proc.rb \ - # ../src/bootstraptest/test_ractor.rb \ - # ../src/bootstraptest/test_thread.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index afcb8230ac2326..268eb427f5aa26 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -130,37 +130,37 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ + ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_exception.rb \ ../src/bootstraptest/test_fiber.rb \ ../src/bootstraptest/test_finalizer.rb \ ../src/bootstraptest/test_flip.rb \ ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ ../src/bootstraptest/test_massign.rb \ + ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ + ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb - # ../src/bootstraptest/test_block.rb \ - # ../src/bootstraptest/test_class.rb \ # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_exception.rb \ - # ../src/bootstraptest/test_gc.rb \ - # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_load.rb \ - # ../src/bootstraptest/test_method.rb \ - # ../src/bootstraptest/test_proc.rb \ - # ../src/bootstraptest/test_ractor.rb \ - # ../src/bootstraptest/test_thread.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/NEWS.md b/NEWS.md index c6dc961360862d..dcd67abbe261c3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -119,7 +119,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.4.1 * logger 1.7.0 -* rdoc 6.14.1 +* rdoc 6.14.2 * win32ole 1.9.2 * irb 1.15.2 * reline 0.6.1 @@ -158,7 +158,7 @@ The following bundled gems are updated. * minitest 5.25.5 * rake 13.3.0 -* test-unit 3.6.8 +* test-unit 3.6.9 * rexml 3.4.1 * net-imap 0.5.9 * net-smtp 0.5.1 diff --git a/benchmark/lib/benchmark_driver/runner/ractor.rb b/benchmark/lib/benchmark_driver/runner/ractor.rb index c730b8e4a54bf2..fd9c2dd4db731d 100644 --- a/benchmark/lib/benchmark_driver/runner/ractor.rb +++ b/benchmark/lib/benchmark_driver/runner/ractor.rb @@ -87,7 +87,7 @@ def render(results:) <% end %> # Wait for all Ractors before executing code to write results -__bmdv_ractors.map!(&:take) +__bmdv_ractors.map!(&:value) <% results.each do |result| %> File.write(<%= result.dump %>, __bmdv_ractors.shift) diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 16bfdd9ea248d4..24bbdafe8ecd7a 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -891,4 +891,8 @@ def yjit_enabled? ENV.key?('RUBY_YJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('yjit') || BT.ruby.include?('yjit') end +def zjit_enabled? + ENV.key?('RUBY_ZJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('zjit') || BT.ruby.include?('zjit') +end + exit main diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 1c89cd40ee7e8b..834c7ceebb04b5 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -389,7 +389,7 @@ def test n end 3.times.map{Ractor.receive}.tally -} unless yjit_enabled? # `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec() +} unless yjit_enabled? || zjit_enabled? # YJIT: `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec(), ZJIT hangs # unshareable object are copied assert_equal 'false', %q{ diff --git a/class.c b/class.c index 96e9aaed21b0eb..bef54eae2f38c1 100644 --- a/class.c +++ b/class.c @@ -980,20 +980,15 @@ copy_tables(VALUE clone, VALUE orig) rb_id_table_free(RCLASS_M_TBL(clone)); RCLASS_WRITE_M_TBL_EVEN_WHEN_PROMOTED(clone, 0); if (!RB_TYPE_P(clone, T_ICLASS)) { - st_data_t id; - rb_fields_tbl_copy(clone, orig); - CONST_ID(id, "__tmp_classpath__"); - rb_attr_delete(clone, id); - CONST_ID(id, "__classpath__"); - rb_attr_delete(clone, id); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; struct rb_id_table *const_tbl; - arg.tbl = const_tbl = rb_id_table_create(0); + struct rb_id_table *orig_tbl = RCLASS_CONST_TBL(orig); + arg.tbl = const_tbl = rb_id_table_create(rb_id_table_size(orig_tbl)); arg.klass = clone; - rb_id_table_foreach(RCLASS_CONST_TBL(orig), clone_const_i, &arg); + rb_id_table_foreach(orig_tbl, clone_const_i, &arg); RCLASS_WRITE_CONST_TBL(clone, const_tbl, false); } } diff --git a/common.mk b/common.mk index 2a1e43604092be..0c4428bef0c59b 100644 --- a/common.mk +++ b/common.mk @@ -1671,12 +1671,8 @@ test-bundler-prepare: $(TEST_RUNNABLE)-test-bundler-prepare no-test-bundler-prepare: no-test-bundler-precheck yes-test-bundler-prepare: yes-test-bundler-precheck $(ACTIONS_GROUP) - $(XRUBY) -C $(srcdir) -Ilib \ - -e 'ENV["GEM_HOME"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_APP_CONFIG"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_PATH__SYSTEM"] = "true"' \ - -e 'ENV["BUNDLE_WITHOUT"] = "lint doc"' \ - -e 'load "spec/bundler/support/bundle.rb"' -- install --quiet --gemfile=tool/bundler/dev_gems.rb + $(XRUBY) -C $(srcdir) -Ilib -r./tool/lib/bundle_env.rb \ + spec/bin/bundle install --quiet --gemfile=tool/bundler/dev_gems.rb $(ACTIONS_ENDGROUP) RSPECOPTS = -r formatter_overrides @@ -19519,6 +19515,7 @@ transcode.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h transcode.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h transcode.$(OBJEXT): {$(VPATH)}config.h transcode.$(OBJEXT): {$(VPATH)}constant.h +transcode.$(OBJEXT): {$(VPATH)}debug_counter.h transcode.$(OBJEXT): {$(VPATH)}defines.h transcode.$(OBJEXT): {$(VPATH)}encoding.h transcode.$(OBJEXT): {$(VPATH)}id.h @@ -19682,6 +19679,8 @@ transcode.$(OBJEXT): {$(VPATH)}st.h transcode.$(OBJEXT): {$(VPATH)}subst.h transcode.$(OBJEXT): {$(VPATH)}transcode.c transcode.$(OBJEXT): {$(VPATH)}transcode_data.h +transcode.$(OBJEXT): {$(VPATH)}vm_debug.h +transcode.$(OBJEXT): {$(VPATH)}vm_sync.h util.$(OBJEXT): $(hdrdir)/ruby/ruby.h util.$(OBJEXT): $(top_srcdir)/internal/array.h util.$(OBJEXT): $(top_srcdir)/internal/compilers.h diff --git a/doc/case_mapping.rdoc b/doc/case_mapping.rdoc index 57c037b9d8ef46..d40155db031f43 100644 --- a/doc/case_mapping.rdoc +++ b/doc/case_mapping.rdoc @@ -37,7 +37,7 @@ Context-dependent case mapping as described in {Table 3-17 (Context Specification for Casing) of the Unicode standard}[https://www.unicode.org/versions/latest/ch03.pdf] is currently not supported. -In most cases, case conversions of a string have the same number of characters. +In most cases, the case conversion of a string has the same number of characters as before. There are exceptions (see also +:fold+ below): s = "\u00DF" # => "ß" @@ -58,25 +58,18 @@ Case changes may not be reversible: s.downcase.upcase # => "HELLO WORLD!" # Different from original s. Case changing methods may not maintain Unicode normalization. -See String#unicode_normalize). +See String#unicode_normalize. -== Options for Case Mapping +== Case Mappings Except for +casecmp+ and +casecmp?+, each of the case-mapping methods listed above -accepts optional arguments, *options. +accepts an optional argument, mapping. -The arguments may be: +The argument is one of: -- +:ascii+ only. -- +:fold+ only. -- +:turkic+ or +:lithuanian+ or both. - -The options: - -- +:ascii+: - ASCII-only mapping: - uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); +- +:ascii+: ASCII-only mapping. + Uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); other characters are not changed s = "Foo \u00D8 \u00F8 Bar" # => "Foo Ø ø Bar" @@ -85,8 +78,8 @@ The options: s.upcase(:ascii) # => "FOO Ø ø BAR" s.downcase(:ascii) # => "foo Ø ø bar" -- +:turkic+: - Full Unicode case mapping, adapted for the Turkic languages +- +:turkic+: Full Unicode case mapping. + For the Turkic languages that distinguish dotted and dotless I, for example Turkish and Azeri. s = 'Türkiye' # => "Türkiye" @@ -97,11 +90,8 @@ The options: s.downcase # => "türkiye" s.downcase(:turkic) # => "türkıye" # No dot above. -- +:lithuanian+: - Not yet implemented. - - +:fold+ (available only for String#downcase, String#downcase!, - and Symbol#downcase): + and Symbol#downcase). Unicode case folding, which is more far-reaching than Unicode case mapping. diff --git a/doc/security.rdoc b/doc/security.rdoc index e428036cf571de..af9970d336e5ff 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -37,7 +37,7 @@ programs for configuration and database persistence of Ruby object trees. Similar to +Marshal+, it is able to deserialize into arbitrary Ruby classes. For example, the following YAML data will create an +ERB+ object when -deserialized, using the `unsafe_load` method: +deserialized, using the +unsafe_load+ method: !ruby/object:ERB src: puts `uname` @@ -53,19 +53,16 @@ method, variable and constant names. The reason for this is that symbols are simply integers with names attached to them, so they are faster to look up in hashtables. -Starting in version 2.2, most symbols can be garbage collected; these are -called mortal symbols. Most symbols you create (e.g. by calling -+to_sym+) are mortal. +Most symbols can be garbage collected; these are called _mortal_ +symbols. Most symbols you create (e.g. by calling +to_sym+) are mortal. -Immortal symbols on the other hand will never be garbage collected. +_Immortal_ symbols on the other hand will never be garbage collected. They are created when modifying code: * defining a method (e.g. with +define_method+), * setting an instance variable (e.g. with +instance_variable_set+), * creating a variable or constant (e.g. with +const_set+) -C extensions that have not been updated and are still calling `SYM2ID` +C extensions that have not been updated and are still calling +SYM2ID+ will create immortal symbols. -Bugs in 2.2.0: +send+ and +__send__+ also created immortal symbols, -and calling methods with keyword arguments could also create some. Don't create immortal symbols from user inputs. Otherwise, this would allow a user to mount a denial of service attack against your application by @@ -128,12 +125,3 @@ Note that the use of +public_send+ is also dangerous, as +send+ itself is public: 1.public_send("send", "eval", "...ruby code to be executed...") - -== DRb - -As DRb allows remote clients to invoke arbitrary methods, it is not suitable to -expose to untrusted clients. - -When using DRb, try to avoid exposing it over the network if possible. If this -isn't possible and you need to expose DRb to the world, you *must* configure an -appropriate security policy with DRb::ACL. diff --git a/doc/standard_library.md b/doc/standard_library.md index f2700ef5c2ebd8..0c48ac0cdd7e0a 100644 --- a/doc/standard_library.md +++ b/doc/standard_library.md @@ -71,6 +71,8 @@ of each. - Etc ([GitHub][etc]): Provides access to information typically stored in the UNIX /etc directory - Fcntl ([GitHub][fcntl]): Loads constants defined in the OS fcntl.h C header file - IO.console ([GitHub][io-console]): Extensions for the IO class, including `IO.console`, `IO.winsize`, etc. +- IO#nonblock ([GitHub][io-nonblock]): Enable non-blocking mode with IO class. +- IO#wait ([GitHub][io-wait]): Provides the feature for waiting until IO is readable or writable without blocking. - JSON ([GitHub][json]): Implements JavaScript Object Notation for Ruby - OpenSSL ([GitHub][openssl]): Provides SSL, TLS, and general-purpose cryptography for Ruby - Pathname ([GitHub][pathname]): Representation of the name of a file or directory on the filesystem @@ -153,6 +155,8 @@ of each. [forwardable]: https://github.com/ruby/forwardable [getoptlong]: https://github.com/ruby/getoptlong [io-console]: https://github.com/ruby/io-console +[io-nonblock]: https://github.com/ruby/io-nonblock +[io-wait]: https://github.com/ruby/io-wait [ipaddr]: https://github.com/ruby/ipaddr [irb]: https://github.com/ruby/irb [json]: https://github.com/ruby/json diff --git a/doc/string/byteslice.rdoc b/doc/string/byteslice.rdoc new file mode 100644 index 00000000000000..d70441fb2b6d6d --- /dev/null +++ b/doc/string/byteslice.rdoc @@ -0,0 +1,54 @@ +Returns a substring of +self+, or +nil+ if the substring cannot be constructed. + +With integer arguments +offset+ and +length+ given, +returns the substring beginning at the given +offset+ +and of the given +length+ (as available): + + s = '0123456789' # => "0123456789" + s.byteslice(2) # => "2" + s.byteslice(200) # => nil + s.byteslice(4, 3) # => "456" + s.byteslice(4, 30) # => "456789" + +Returns +nil+ if +length+ is negative or +offset+ falls outside of +self+: + + s.byteslice(4, -1) # => nil + s.byteslice(40, 2) # => nil + +Counts backwards from the end of +self+ +if +offset+ is negative: + + s = '0123456789' # => "0123456789" + s.byteslice(-4) # => "6" + s.byteslice(-4, 3) # => "678" + +With Range argument +range+ given, returns +byteslice(range.begin, range.size): + + s = '0123456789' # => "0123456789" + s.byteslice(4..6) # => "456" + s.byteslice(-6..-4) # => "456" + s.byteslice(5..2) # => "" # range.size is zero. + s.byteslice(40..42) # => nil + +The starting and ending offsets need not be on character boundaries: + + s = 'こんにちは' + s.byteslice(0, 3) # => "こ" + s.byteslice(1, 3) # => "\x81\x93\xE3" + +The encodings of +self+ and the returned substring +are always the same: + + s.encoding # => # + s.byteslice(0, 3).encoding # => # + s.byteslice(1, 3).encoding # => # + +But, depending on the character boundaries, +the encoding of the returned substring may not be valid: + + s.valid_encoding? # => true + s.byteslice(0, 3).valid_encoding? # => true + s.byteslice(1, 3).valid_encoding? # => false + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/bytesplice.rdoc b/doc/string/bytesplice.rdoc new file mode 100644 index 00000000000000..5689ef4a2ba79b --- /dev/null +++ b/doc/string/bytesplice.rdoc @@ -0,0 +1,66 @@ +Replaces target bytes in +self+ with source bytes from the given string +str+; +returns +self+. + +In the first form, arguments +offset+ and +length+ determine the target bytes, +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0, 3, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3, 3, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0, 50, 'abc') # => "abc" + '0123456789'.bytesplice(50, 3, 'abc') # Raises IndexError. + +The counts of the target bytes and source source bytes may be different: + + '0123456789'.bytesplice(0, 6, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc') # => "abc123456789" # Shorter target. + +And either count may be zero (i.e., specifying an empty string): + + '0123456789'.bytesplice(0, 3, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc') # => "abc0123456789" # Empty target. + +In the second form, just as in the first, +arugments +offset+ and +length+ determine the target bytes; +argument +str+ _contains_ the source bytes, +and the additional arguments +str_offset+ and +str_length+ +determine the actual source bytes: + + '0123456789'.bytesplice(0, 3, 'abc', 0, 3) # => "abc3456789" + '0123456789'.bytesplice(0, 3, 'abc', 1, 1) # => "b3456789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc', 0, 3) # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0, 3, 'abc', 1, 0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc', 0, 3) # => "abc0123456789" # Empty target. + +In the third form, argument +range+ determines the target bytes +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0..2, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0..5, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0..0, 'abc') # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0..2, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc') # => "abc0123456789" # Empty target. + +In the fourth form, just as in the third, +arugment +range+ determines the target bytes; +argument +str+ _contains_ the source bytes, +and the additional argument +str_range+ +determines the actual source bytes: + + '0123456789'.bytesplice(0..2, 'abc', 0..2) # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc', 0..2) # => "012abc6789" + '0123456789'.bytesplice(0..2, 'abc', 0..1) # => "ab3456789" # Shorter source. + '0123456789'.bytesplice(0..1, 'abc', 0..2) # => "abc23456789" # Shorter target. + '0123456789'.bytesplice(0..2, 'abc', 0...0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc', 0..2) # => "abc0123456789" # Empty target. + +In any of the forms, the beginnings and endings of both source and target +must be on character boundaries. + +In these examples, +self+ has five 3-byte characters, +and so has character boundaries at offsets 0, 3, 6, 9, 12, and 15. + + 'こんにちは'.bytesplice(0, 3, 'abc') # => "abcんにちは" + 'こんにちは'.bytesplice(1, 3, 'abc') # Raises IndexError. + 'こんにちは'.bytesplice(0, 2, 'abc') # Raises IndexError. + diff --git a/ext/etc/extconf.rb b/ext/etc/extconf.rb index 3d7cceae40344d..497303a4fa51f7 100644 --- a/ext/etc/extconf.rb +++ b/ext/etc/extconf.rb @@ -60,7 +60,7 @@ # TODO: remove when dropping 2.7 support, as exported since 3.0 have_func('rb_deprecate_constant(Qnil, "None")') -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") $distcleanfiles << "constdefs.h" diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 4ad7ed6996df5b..dd3d221ae51df3 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -10,11 +10,11 @@ abort have_func("rb_interned_str_cstr") -have_func("rb_io_path") -have_func("rb_io_descriptor") -have_func("rb_io_get_write_io") -have_func("rb_io_closed_p") -have_func("rb_io_open_descriptor") +have_func("rb_io_path", "ruby/io.h") +have_func("rb_io_descriptor", "ruby/io.h") +have_func("rb_io_get_write_io", "ruby/io.h") +have_func("rb_io_closed_p", "ruby/io.h") +have_func("rb_io_open_descriptor", "ruby/io.h") have_func("rb_ractor_local_storage_value_newkey") is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"] diff --git a/ext/io/nonblock/extconf.rb b/ext/io/nonblock/extconf.rb index a1e6075c9b5052..505c9e6252fb99 100644 --- a/ext/io/nonblock/extconf.rb +++ b/ext/io/nonblock/extconf.rb @@ -7,7 +7,7 @@ return end -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") hdr = %w"fcntl.h" if have_macro("O_NONBLOCK", hdr) and diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index e63c0461873187..ba223f0ac24ad6 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -5,8 +5,8 @@ File.write("Makefile", dummy_makefile($srcdir).join("")) else target = "io/wait" - have_func("rb_io_wait") - have_func("rb_io_descriptor") + have_func("rb_io_wait", "ruby/io.h") + have_func("rb_io_descriptor", "ruby/io.h") unless macro_defined?("DOSISH", "#include ") have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index aaf02c77d607ba..fb9afd07f7bfee 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -9,7 +9,7 @@ $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) - require_relative "../simd/conf.rb" + load __dir__ + "/../simd/conf.rb" end create_makefile 'json/ext/generator' diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 07426363acf78b..55757310250dae 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -44,8 +44,7 @@ spec = Gem::Specification.new do |s| "LEGAL", "README.md", "json.gemspec", - *Dir["lib/**/*.rb"], - ] + ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__)) if java_ext s.platform = 'java' diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index 0b62fd6135c852..de5d5758b46c42 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'mkmf' -have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 +have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby have_func("strnlen", "string.h") # Missing on Solaris 10 @@ -9,7 +9,7 @@ append_cflags("-std=c99") if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) - require_relative "../simd/conf.rb" + load __dir__ + "/../simd/conf.rb" end create_makefile 'json/ext/parser' diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index d7796948274281..9bf247039e3389 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -908,7 +908,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) { #ifdef HAVE_SIMD #if defined(HAVE_SIMD_NEON) - + uint64_t mask = 0; if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros64(mask) >> 2; diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index 6393cf7891ea50..8e7d8ee26133d1 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -1,25 +1,20 @@ -if RbConfig::CONFIG['host_cpu'] =~ /^(arm.*|aarch64.*)/ +case RbConfig::CONFIG['host_cpu'] +when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions - if have_header('arm_neon.h') - have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') - #include - int main() { - uint8x16_t test = vdupq_n_u8(32); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") - end + header, type, init = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)' +when /^(x86_64|x64)/ + header, type, init = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)' end - -if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') - #include - int main() { - __m128i test = _mm_set1_epi8(32); +if header + have_header(header) && try_compile(<<~SRC) + #{cpp_include(header)} + int main(int argc, char **argv) { + #{type} test = #{init}; + if (argc > 100000) printf("%p", &test); return 0; - } + } SRC - $defs.push("-DJSON_ENABLE_SIMD") + $defs.push("-DJSON_ENABLE_SIMD") end have_header('cpuid.h') diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index ed2a6d467b6ab5..e0cf4754a2abda 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -59,7 +59,7 @@ static inline int trailing_zeros(int input) { #include #define FIND_SIMD_IMPLEMENTATION_DEFINED 1 -static SIMD_Implementation find_simd_implementation(void) { +static inline SIMD_Implementation find_simd_implementation(void) { return SIMD_NEON; } @@ -161,18 +161,11 @@ static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **pt #include #endif /* HAVE_CPUID_H */ -static SIMD_Implementation find_simd_implementation(void) { - -#if defined(__GNUC__ ) || defined(__clang__) -#ifdef __GNUC__ - __builtin_cpu_init(); -#endif /* __GNUC__ */ - +static inline SIMD_Implementation find_simd_implementation(void) { // TODO Revisit. I think the SSE version now only uses SSE2 instructions. if (__builtin_cpu_supports("sse2")) { return SIMD_SSE2; } -#endif /* __GNUC__ || __clang__*/ return SIMD_NONE; } @@ -183,7 +176,7 @@ static SIMD_Implementation find_simd_implementation(void) { #endif /* JSON_ENABLE_SIMD */ #ifndef FIND_SIMD_IMPLEMENTATION_DEFINED -static SIMD_Implementation find_simd_implementation(void) { +static inline SIMD_Implementation find_simd_implementation(void) { return SIMD_NONE; } #endif diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 6eb401cf5597de..afbed10b54bd06 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -38,7 +38,7 @@ $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 have_func("rb_io_timeout", "ruby/io.h") diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 9f7c718592045e..2ec1551885b201 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -13,7 +13,8 @@ Gem::Specification.new do |spec| spec.files = [] spec.add_runtime_dependency('jruby-openssl', '~> 0.14') else - spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "COPYING"] + spec.files = Dir.glob(["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md"], base: File.expand_path("..", __FILE__)) + + ["BSDL", "COPYING"] spec.require_paths = ["lib"] spec.extensions = ["ext/openssl/extconf.rb"] end diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 30fbb3bbd14730..b5872f588125c2 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -1182,25 +1182,29 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) } #endif -#if !defined(OPENSSL_NO_EC) /* * call-seq: - * ctx.ecdh_curves = curve_list -> curve_list + * ctx.groups = groups_list + * ctx.ecdh_curves = groups_list * - * Sets the list of "supported elliptic curves" for this context. + * Sets the list of supported groups for key agreement for this context. * - * For a TLS client, the list is directly used in the Supported Elliptic Curves - * Extension. For a server, the list is used by OpenSSL to determine the set of - * shared curves. OpenSSL will pick the most appropriate one from it. + * For a TLS client, the list is directly used in the "supported_groups" + * extension. For a server, the list is used by OpenSSL to determine the set of + * shared supported groups. OpenSSL will pick the most appropriate one from it. + * + * #ecdh_curves= is a deprecated alias for #groups=. + * + * See also the man page SSL_CTX_set1_groups_list(3). * * === Example * ctx1 = OpenSSL::SSL::SSLContext.new - * ctx1.ecdh_curves = "X25519:P-256:P-224" + * ctx1.groups = "X25519:P-256:P-224" * svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1) * Thread.new { svr.accept } * * ctx2 = OpenSSL::SSL::SSLContext.new - * ctx2.ecdh_curves = "P-256" + * ctx2.groups = "P-256" * cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2) * cli.connect * @@ -1208,7 +1212,7 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) * # => "prime256v1" (is an alias for NIST P-256) */ static VALUE -ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) +ossl_sslctx_set_groups(VALUE self, VALUE arg) { SSL_CTX *ctx; @@ -1216,13 +1220,10 @@ ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) GetSSLCTX(self, ctx); StringValueCStr(arg); - if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg))) - ossl_raise(eSSLError, NULL); + if (!SSL_CTX_set1_groups_list(ctx, RSTRING_PTR(arg))) + ossl_raise(eSSLError, "SSL_CTX_set1_groups_list"); return arg; } -#else -#define ossl_sslctx_set_ecdh_curves rb_f_notimplement -#endif /* * call-seq: @@ -2958,7 +2959,8 @@ Init_ossl_ssl(void) #ifndef OPENSSL_NO_DH rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1); #endif - rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1); + rb_define_method(cSSLContext, "groups=", ossl_sslctx_set_groups, 1); + rb_define_alias(cSSLContext, "ecdh_curves=", "groups="); rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0); rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1); #ifdef SSL_MODE_SEND_FALLBACK_SCSV diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 54a5381da416e1..dcafbe24e3111e 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -292,8 +292,8 @@ extern VALUE rb_eResolution; #ifdef SOCKS extern VALUE rb_cSOCKSSocket; # ifndef SOCKS5 -void SOCKSinit(); -int Rconnect(); +void SOCKSinit(char *); +int Rconnect(int, const struct sockaddr *, socklen_t); # endif #endif diff --git a/ext/socket/sockssocket.c b/ext/socket/sockssocket.c index 1031812befd24d..f033f39b2e8888 100644 --- a/ext/socket/sockssocket.c +++ b/ext/socket/sockssocket.c @@ -30,7 +30,8 @@ socks_init(VALUE sock, VALUE host, VALUE port) static int init = 0; if (init == 0) { - SOCKSinit("ruby"); + char progname[] = "ruby"; + SOCKSinit(progname); init = 1; } diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index abcbdb3ad216cd..3c311d2364b7d7 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -2,7 +2,7 @@ require 'mkmf' if RUBY_ENGINE == 'ruby' $INCFLAGS << " -I$(top_srcdir)" if $extmk - have_func("onig_region_memsize") + have_func("onig_region_memsize(NULL)") have_func("rb_reg_onig_match", "ruby/re.h") create_makefile 'strscan' else diff --git a/gc.c b/gc.c index 047fcdb3c06772..63e86d5ca201ef 100644 --- a/gc.c +++ b/gc.c @@ -4940,6 +4940,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU #undef C +#ifdef RUBY_ASAN_ENABLED void rb_asan_poison_object(VALUE obj) { @@ -4960,6 +4961,7 @@ rb_asan_poisoned_object_p(VALUE obj) MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; return __asan_region_is_poisoned(ptr, rb_gc_obj_slot_size(obj)); } +#endif static void raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) diff --git a/gems/bundled_gems b/gems/bundled_gems index d1832335fd7d8f..7fcd0796aa4bec 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.25.5 https://github.com/minitest/minitest power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a rake 13.3.0 https://github.com/ruby/rake -test-unit 3.6.8 https://github.com/test-unit/test-unit +test-unit 3.6.9 https://github.com/test-unit/test-unit rexml 3.4.1 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp @@ -39,7 +39,7 @@ ostruct 0.6.2 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.4.1 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.14.1 https://github.com/ruby/rdoc +rdoc 6.14.2 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36 reline 0.6.1 https://github.com/ruby/reline diff --git a/internal/class.h b/internal/class.h index f71583d61aef87..f8cfba3fd963b4 100644 --- a/internal/class.h +++ b/internal/class.h @@ -566,9 +566,7 @@ RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - VALUE old_fields_obj = ext->fields_obj; - RUBY_ATOMIC_VALUE_SET(ext->fields_obj, fields_obj); - RB_OBJ_WRITTEN(obj, old_fields_obj, fields_obj); + RB_OBJ_ATOMIC_WRITE(obj, &ext->fields_obj, fields_obj); } static inline void diff --git a/internal/gc.h b/internal/gc.h index 06103ca25fd238..f0dc04fc58a954 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -264,6 +264,26 @@ int rb_gc_modular_gc_loaded_p(void); RUBY_SYMBOL_EXPORT_END +static inline VALUE +rb_obj_atomic_write( + VALUE a, VALUE *slot, VALUE b, + RBIMPL_ATTR_MAYBE_UNUSED() + const char *filename, + RBIMPL_ATTR_MAYBE_UNUSED() + int line) +{ +#ifdef RGENGC_LOGGING_WRITE + RGENGC_LOGGING_WRITE(a, slot, b, filename, line); +#endif + + RUBY_ATOMIC_VALUE_SET(*slot, b); + + rb_obj_written(a, RUBY_Qundef /* ignore `oldv' now */, b, filename, line); + return a; +} +#define RB_OBJ_ATOMIC_WRITE(old, slot, young) \ + RBIMPL_CAST(rb_obj_atomic_write((VALUE)(old), (VALUE *)(slot), (VALUE)(young), __FILE__, __LINE__)) + int rb_ec_stack_check(struct rb_execution_context_struct *ec); void rb_gc_writebarrier_remember(VALUE obj); const char *rb_obj_info(VALUE obj); diff --git a/internal/sanitizers.h b/internal/sanitizers.h index 279cbbe06941e7..8e6e87ddc8d749 100644 --- a/internal/sanitizers.h +++ b/internal/sanitizers.h @@ -127,6 +127,7 @@ asan_poison_memory_region(const volatile void *ptr, size_t size) #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #endif +#ifdef RUBY_ASAN_ENABLED RUBY_SYMBOL_EXPORT_BEGIN /** * This is a variant of asan_poison_memory_region that takes a VALUE. @@ -153,6 +154,11 @@ void *rb_asan_poisoned_object_p(VALUE obj); void rb_asan_unpoison_object(VALUE obj, bool newobj_p); RUBY_SYMBOL_EXPORT_END +#else +# define rb_asan_poison_object(obj) ((void)obj) +# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL) +# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p) +#endif /** * This function asserts that a (formally poisoned) memory region from ptr to diff --git a/io.c b/io.c index 327740aa2ec614..ba43bd80659a01 100644 --- a/io.c +++ b/io.c @@ -14918,7 +14918,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - \File +t.rb+: * * p "ARGV: #{ARGV}" - * p "Line: #{ARGF.read}" # Read everything from all specified streams. + * p "Read: #{ARGF.read}" # Read everything from all specified streams. * * - Command and output: * diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index e49d6fbdcf86ac..50fc31937c9440 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -99,11 +99,14 @@ def self.warning?(name, specs: nil) # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. feature.delete_prefix!(ARCHDIR) feature.delete_prefix!(LIBDIR) - segments = feature.split("/") + # 1. A segment for the EXACT mapping and SINCE check + # 2. A segment for the SINCE check for dashed names + # 3. A segment to check if there's a subfeature + segments = feature.split("/", 3) name = segments.shift name = EXACT[name] || name if !SINCE[name] - name = [name, segments.shift].join("-") + name = "#{name}-#{segments.shift}" return unless SINCE[name] end segments.any? diff --git a/lib/bundler.rb b/lib/bundler.rb index 904dcb84671ea0..d3219fe46e52ee 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -113,13 +113,13 @@ def create_bundle_path end def configured_bundle_path - @configured_bundle_path ||= settings.path.tap(&:validate!) + @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!) end # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin - path = settings[:bin] || "bin" + path = Bundler.settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path mkdir_p(path) path @@ -180,7 +180,7 @@ def auto_switch # should be called first, before you instantiate anything like an # `Installer` that'll keep a reference to the old one instead. def auto_install - return unless settings[:auto_install] + return unless Bundler.settings[:auto_install] begin definition.specs @@ -238,10 +238,10 @@ def definition(unlock = nil, lockfile = default_lockfile) end def frozen_bundle? - frozen = settings[:frozen] + frozen = Bundler.settings[:frozen] return frozen unless frozen.nil? - settings[:deployment] + Bundler.settings[:deployment] end def locked_gems @@ -342,7 +342,7 @@ def app_config_path def app_cache(custom_path = nil) path = custom_path || root - Pathname.new(path).join(settings.app_cache_path) + Pathname.new(path).join(Bundler.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -454,7 +454,7 @@ def unbundled_exec(*args) end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] + return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform] Gem::Platform.local end @@ -480,11 +480,11 @@ def system_bindir # install binstubs there instead. Unfortunately, RubyGems doesn't expose # that directory at all, so rather than parse .gemrc ourselves, we allow # the directory to be set as well, via `bundle config set --local bindir foo`. - settings[:system_bindir] || Bundler.rubygems.gem_bindir + Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir end def preferred_gemfile_name - settings[:init_gems_rb] ? "gems.rb" : "Gemfile" + Bundler.settings[:init_gems_rb] ? "gems.rb" : "Gemfile" end def use_system_gems? @@ -567,7 +567,7 @@ def git_present? end def feature_flag - @feature_flag ||= FeatureFlag.new(settings[:simulate_version] || VERSION) + @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION) end def reset! diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb index 5d2a8b53bb58a6..49d2518078b206 100644 --- a/lib/bundler/build_metadata.rb +++ b/lib/bundler/build_metadata.rb @@ -4,21 +4,26 @@ module Bundler # Represents metadata from when the Bundler gem was built. module BuildMetadata # begin ivars - @release = false + @built_at = nil # end ivars # A hash representation of the build metadata. def self.to_h { - "Built At" => built_at, + "Timestamp" => timestamp, "Git SHA" => git_commit_sha, - "Released Version" => release?, } end + # A timestamp representing the date the bundler gem was built, or the + # current time if never built + def self.timestamp + @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze + end + # A string representing the date the bundler gem was built. def self.built_at - @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + @built_at end # The SHA for the git commit the bundler gem was built from. @@ -34,10 +39,5 @@ def self.git_commit_sha @git_commit_sha ||= "unknown" end - - # Whether this is an official release build of Bundler. - def self.release? - @release - end end end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index f16c0a0386f2f5..bba60ddab4d3dc 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -77,7 +77,7 @@ def initialize(*args) self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options["verbose"] + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end @@ -486,13 +486,13 @@ def console(group = nil) def version cli_help = current_command.name == "cli_help" if cli_help || ARGV.include?("version") - build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.print_only_version_number? - Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + if !cli_help && Bundler.feature_flag.bundler_4_mode? + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else - Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" end end @@ -714,14 +714,9 @@ def print_command command_name = cmd.name return if PARSEABLE_COMMANDS.include?(command_name) command = ["bundle", command_name] + args - options_to_print = options.dup - options_to_print.delete_if do |k, v| - next unless o = cmd.options[k] - o.default == v - end - command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip + command << Thor::Options.to_switches(options.sort_by(&:first)).strip command.reject!(&:empty?) - Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" end def warn_on_outdated_bundler diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 2267dc3ee04601..b36d7e73e94d95 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -36,7 +36,6 @@ def self.settings_method(name, key, &default) settings_flag(:lockfile_checksums) { bundler_4_mode? } settings_flag(:path_relative_to_cwd) { bundler_4_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } - settings_flag(:print_only_version_number) { bundler_4_mode? } settings_flag(:setup_makes_kernel_gem_public) { !bundler_4_mode? } settings_flag(:update_requires_all_flag) { bundler_5_mode? } diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index c07e8ab350d51b..0b6ced6f395cda 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -72,24 +72,29 @@ def initialize(remote_uri) end end - HTTP_ERRORS = [ - Gem::Timeout::Error, - EOFError, - SocketError, - Errno::EADDRNOTAVAIL, - Errno::ENETDOWN, - Errno::ENETUNREACH, - Errno::EINVAL, - Errno::ECONNRESET, - Errno::ETIMEDOUT, - Errno::EAGAIN, - Gem::Net::HTTPBadResponse, - Gem::Net::HTTPHeaderSyntaxError, - Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, - Zlib::BufError, - Errno::EHOSTUNREACH, + HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze + deprecate_constant :HTTP_ERRORS + + NET_ERRORS = [ + :HTTPBadGateway, + :HTTPBadRequest, + :HTTPFailedDependency, + :HTTPForbidden, + :HTTPInsufficientStorage, + :HTTPMethodNotAllowed, + :HTTPMovedPermanently, + :HTTPNoContent, + :HTTPNotFound, + :HTTPNotImplemented, + :HTTPPreconditionFailed, + :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, + :HTTPUnauthorized, + :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, + :HTTPVersionNotSupported, ].freeze + deprecate_constant :NET_ERRORS # Exceptions classes that should bypass retry attempts. If your password didn't work the # first time, it's not going to the third time. diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 868b39b959c79c..2eac6e79754c5e 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,6 +3,28 @@ module Bundler class Fetcher class Downloader + HTTP_NON_RETRYABLE_ERRORS = [ + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, + Errno::ENETUNREACH, + Gem::Net::HTTP::Persistent::Error, + Errno::EHOSTUNREACH, + ].freeze + + HTTP_RETRYABLE_ERRORS = [ + Gem::Timeout::Error, + EOFError, + Errno::EINVAL, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Errno::EAGAIN, + Gem::Net::HTTPBadResponse, + Gem::Net::HTTPHeaderSyntaxError, + Gem::Net::ProtocolError, + Zlib::BufError, + ].freeze + attr_reader :connection attr_reader :redirect_limit @@ -67,15 +89,19 @@ def request(uri, headers) connection.request(uri, req) rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) - rescue *HTTP_ERRORS => e + rescue *HTTP_NON_RETRYABLE_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.message.to_s.include?("host down:") - raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ - "connection and try again." - else - raise HTTPError, "Network error while fetching #{filtered_uri}" \ + + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ + "connection and try again." + rescue *HTTP_RETRYABLE_ERRORS => e + Bundler.ui.trace e + + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" - end end private diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 80b1be904eb3cc..a77e4ac2644139 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -161,9 +161,6 @@ Enable Bundler's experimental plugin system\. \fBprefer_patch\fR (BUNDLE_PREFER_PATCH) Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. .TP -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR) -Print only version number from \fBbundler \-\-version\fR\. -.TP \fBredirect\fR (\fBBUNDLE_REDIRECT\fR) The number of redirects allowed for network requests\. Defaults to \fB5\fR\. .TP @@ -182,6 +179,9 @@ Whether Bundler should silence deprecation warnings for behavior that will be ch \fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR) Silence the warning Bundler prints when installing gems as root\. .TP +\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR) +The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\. +.TP \fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR) Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. .TP @@ -203,6 +203,9 @@ Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be u \fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR) The custom user agent fragment Bundler includes in API requests\. .TP +\fBverbose\fR (\fBBUNDLE_VERBOSE\fR) +Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\. +.TP \fBversion\fR (\fBBUNDLE_VERSION\fR) The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index b36288c23bf407..2386fe9dcdfb75 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -177,8 +177,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): - Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. * `retry` (`BUNDLE_RETRY`): @@ -194,6 +192,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). be changed in the next major version. * `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): Silence the warning Bundler prints when installing gems as root. +* `simulate_version` (`BUNDLE_SIMULATE_VERSION`): + The virtual version Bundler should use for activating feature flags. Can be + used to simulate all the new functionality that will be enabled in a future + major version. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. @@ -212,6 +214,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): The custom user agent fragment Bundler includes in API requests. +* `verbose` (`BUNDLE_VERBOSE`): + Whether Bundler should print verbose output. Defaults to `false`, unless the + `--verbose` CLI flag is used. * `version` (`BUNDLE_VERSION`): The version of Bundler to use when running under Bundler environment. Defaults to `lockfile`. You can also specify `system` or `x.y.z`. diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index 0cf01e02e93947..433f43b1009a30 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -4,11 +4,18 @@ .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" -\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl] +.br +\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=VERSION] [\-\-verify\-mode=MODE] +.br +\fBbundle doctor\fR help [COMMAND] .SH "DESCRIPTION" +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\. +.SH "SUB\-COMMANDS" +.SS "diagnose (default command)" Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. .P -Examples of common problems caught by bundle\-doctor include: +Examples of common problems caught include: .IP "\(bu" 4 Invalid Bundler settings .IP "\(bu" 4 @@ -20,11 +27,43 @@ Uninstalled gems .IP "\(bu" 4 Missing dependencies .IP "" 0 -.SH "OPTIONS" +.P +\fBOPTIONS\fR .TP \fB\-\-quiet\fR Only output warnings and errors\. .TP \fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +.TP +\fB\-\-ssl\fR +Diagnose common SSL problems when connecting to https://rubygems\.org\. +.IP +This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\. +.SS "ssl" +If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as: +.IP "\(bu" 4 +Verify the Ruby OpenSSL version installed on your system\. +.IP "\(bu" 4 +Check the OpenSSL library version used for compilation\. +.IP "\(bu" 4 +Ensure CA certificates are correctly setup on your machine\. +.IP "\(bu" 4 +Open a TLS connection and verify the outcome\. +.IP "" 0 +.P +\fBOPTIONS\fR +.TP +\fB\-\-host=HOST\fR +Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\. +.TP +\fB\-\-tls\-version=VERSION\fR +Specify the TLS version when opening the connection to HOST\. +.IP +Accepted values are: \fB1\.1\fR or \fB1\.2\fR\. +.TP +\fB\-\-verify\-mode=MODE\fR +Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\. +.IP +Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\. diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn index 5970f6188b1221..7e8a21b1c58968 100644 --- a/lib/bundler/man/bundle-doctor.1.ronn +++ b/lib/bundler/man/bundle-doctor.1.ronn @@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems ## SYNOPSIS -`bundle doctor` [--quiet] - [--gemfile=GEMFILE] +`bundle doctor [diagnose]` [--quiet] + [--gemfile=GEMFILE] + [--ssl]
+`bundle doctor ssl` [--host=HOST] + [--tls-version=VERSION] + [--verify-mode=MODE]
+`bundle doctor` help [COMMAND] ## DESCRIPTION +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue. + +## SUB-COMMANDS + +### diagnose (default command) + Checks your Gemfile and gem environment for common problems. If issues are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits status 0. -Examples of common problems caught by bundle-doctor include: +Examples of common problems caught include: * Invalid Bundler settings * Mismatched Ruby versions @@ -20,7 +31,7 @@ Examples of common problems caught by bundle-doctor include: * Uninstalled gems * Missing dependencies -## OPTIONS +**OPTIONS** * `--quiet`: Only output warnings and errors. @@ -31,3 +42,36 @@ Examples of common problems caught by bundle-doctor include: will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. + +* `--ssl`: + Diagnose common SSL problems when connecting to https://rubygems.org. + + This flag runs the `bundle doctor ssl` subcommand with default values + underneath. + +### ssl + +If you've experienced issues related to SSL certificates and/or TLS versions while connecting +to https://rubygems.org, this command can help troubleshoot common problems. +The diagnostic will perform a few checks such as: + +* Verify the Ruby OpenSSL version installed on your system. +* Check the OpenSSL library version used for compilation. +* Ensure CA certificates are correctly setup on your machine. +* Open a TLS connection and verify the outcome. + +**OPTIONS** + +* `--host=HOST`: + Perform the diagnostic on HOST. Defaults to `rubygems.org`. + +* `--tls-version=VERSION`: + Specify the TLS version when opening the connection to HOST. + + Accepted values are: `1.1` or `1.2`. + +* `--verify-mode=MODE`: + Specify the TLS verify mode when opening the connection to HOST. + Defaults to `SSL_VERIFY_PEER`. + + Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`. diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 444b085bed214b..9b2416402bb74c 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -71,7 +71,7 @@ def require(*groups) raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end - rescue RuntimeError => e + rescue StandardError => e raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 8e92ca88d3fd6f..6f6bb59747cfb0 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -40,11 +40,11 @@ class Settings path.system plugins prefer_patch - print_only_version_number setup_makes_kernel_gem_public silence_deprecations silence_root_warning update_requires_all_flag + verbose ].freeze REMEMBERED_KEYS = %w[ @@ -87,6 +87,7 @@ class Settings gemfile path shebang + simulate_version system_bindir trust-policy version diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 7a8fb214c75410..f2e654b08a5b7c 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -10,4 +10,12 @@ def self.bundler_major_version def self.gem_version @gem_version ||= Gem::Version.create(VERSION) end + + def self.verbose_version + @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}" + end + + def self.simulated_version + @simulated_version ||= Bundler.settings[:simulate_version] + end end diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec similarity index 97% rename from lib/erb.gemspec rename to lib/erb/erb.gemspec index 0a59abad5391f7..94edc686825419 100644 --- a/lib/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -2,7 +2,7 @@ begin require_relative 'lib/erb/version' rescue LoadError # for Ruby core repository - require_relative 'erb/version' + require_relative 'version' end Gem::Specification.new do |spec| diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 8589f1857cf030..6ea6b883956dc0 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -25,8 +25,9 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + - %w[README.md ChangeLog COPYING .document .rdoc_options] + dir, gemspec = File.split(__FILE__) + excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n} + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb index a94eea5046f123..d26568eefcd09c 100755 --- a/misc/expand_tabs.rb +++ b/misc/expand_tabs.rb @@ -59,53 +59,31 @@ def with_clean_env end DEFAULT_GEM_LIBS = %w[ - abbrev - base64 - benchmark bundler - cmath - csv - debug delegate did_you_mean - drb english erb + error_highlight fileutils find forwardable - getoptlong ipaddr - irb - logger - mutex_m net-http net-protocol - observer open3 open-uri optparse ostruct pp prettyprint - prime - pstore - rdoc - readline - reline + prism resolv - resolv-replace - rexml - rinda - rss rubygems - scanf securerandom - set shellwords singleton tempfile - thwait time timeout tmpdir @@ -117,27 +95,19 @@ def with_clean_env ] DEFAULT_GEM_EXTS = %w[ - bigdecimal - cgi date digest etc fcntl - fiddle io-console io-nonblock io-wait json - nkf openssl pathname psych - racc - readline-ext stringio strscan - syslog - win32ole zlib ] diff --git a/ractor_sync.c b/ractor_sync.c index 124ffc139cac09..eb967a73cbc324 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -729,20 +729,10 @@ static rb_ractor_t * ractor_set_successor_once(rb_ractor_t *r, rb_ractor_t *cr) { if (r->sync.successor == NULL) { - RACTOR_LOCK(r); - { - if (r->sync.successor != NULL) { - // already `value`ed - } - else { - r->sync.successor = cr; - } - } - RACTOR_UNLOCK(r); + rb_ractor_t *successor = ATOMIC_PTR_CAS(r->sync.successor, NULL, cr); + return successor == NULL ? cr : successor; } - VM_ASSERT(r->sync.successor != NULL); - return r->sync.successor; } diff --git a/sample/drb/README.ja.rdoc b/sample/drb/README.ja.rdoc deleted file mode 100644 index 1697b1b70416bc..00000000000000 --- a/sample/drb/README.ja.rdoc +++ /dev/null @@ -1,59 +0,0 @@ -= サンプルスクリプト - -* Arrayをリモートから利用してイテレータを試す。 - * darray.rb --- server - * darrayc.rb --- client - -* 簡易チャット - * dchats.rb --- server - * dchatc.rb --- client - -* 分散chasen - * dhasen.rb --- server - * dhasenc.rb --- client - -* 簡易ログサーバ - * dlogd.rb --- server - * dlogc.rb --- client - -* Queueサーバ。 - クライアントdqin.rbはQueueサーバの知らないオブジェクト(DQEntry)を - pushするがDRbUnknownによりクライアントdqout.rbがpopできる。 - * dqueue.rb --- server - * dqin.rb --- client。DQEntryオブジェクトをpushする - * dqout.rb --- client。DQEntryオブジェクトをpopする - * dqlib.rb --- DQEntryを定義したライブラリ - -* 名前による参照 - IdConvをカスタマイズしてidでなく名前で参照する例 - * name.rb --- server - * namec.rb --- client - -* extservのサンプル - * extserv_test.rb - -* TimerIdConvの使用例 - * holders.rb --- server。ruby -d hodlers.rbとするとTimerIdConvを使用する。 - * holderc.rb --- client - -* rinda.rbの使用例 - * rinda_ts.rb --- TupleSpaceサーバ。 - * rindac.rb --- TupleSpaceのclientでアプリケーションのclient - * rindas.rb --- TupleSpaceのclientでアプリケーションのserver - -* observerの使用例 - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbsslの使用例 - * drbssl_s.rb - * drbssl_c.rb - -* DRbProtocolの追加例 - * http0.rb - * http0serv.rb - -* ringの使用例 - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/README.rdoc b/sample/drb/README.rdoc deleted file mode 100644 index e6b457bc5c170c..00000000000000 --- a/sample/drb/README.rdoc +++ /dev/null @@ -1,56 +0,0 @@ -= Sample scripts - -* array and iterator - * darray.rb --- server - * darrayc.rb --- client - -* simple chat - * dchats.rb --- server - * dchatc.rb --- client - -* distributed chasen (for Japanese) - * dhasen.rb --- server - * dhasenc.rb --- client - -* simple log server - * dlogd.rb --- server - * dlogc.rb --- client - -* Queue server, and DRbUnknown demo - * dqueue.rb --- server - * dqin.rb --- client. push DQEntry objects. - * dqout.rb --- client. pop DQEntry objects. - * dqlib.rb --- define DQEntry - -* IdConv customize demo: reference by name - * name.rb --- server - * namec.rb --- client - -* extserv - * extserv_test.rb - -* IdConv customize demo 2: using TimerIdConv - * holders.rb --- server - * holderc.rb --- client - -* rinda, remote tuplespace - * rinda_ts.rb --- TupleSpace server. - * rindas.rb --- provide simple service via TupleSpace. - * rindac.rb --- service user - -* observer - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbssl - * drbssl_s.rb - * drbssl_c.rb - -* add DRbProtocol - * http0.rb - * http0serv.rb - -* Rinda::Ring - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/acl.rb b/sample/drb/acl.rb deleted file mode 100644 index d93eb9c1fcd119..00000000000000 --- a/sample/drb/acl.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'drb/acl' - -list = %w(deny all - allow 192.168.1.1 - allow ::ffff:192.168.1.2 - allow 192.168.1.3 -) - -addr = ["AF_INET", 10, "lc630", "192.168.1.3"] - -acl = ACL.new -p acl.allow_addr?(addr) - -acl = ACL.new(list, ACL::DENY_ALLOW) -p acl.allow_addr?(addr) diff --git a/sample/drb/darray.rb b/sample/drb/darray.rb deleted file mode 100644 index d2ac39513f4a6d..00000000000000 --- a/sample/drb/darray.rb +++ /dev/null @@ -1,12 +0,0 @@ -=begin - distributed Ruby --- Array - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -here = ARGV.shift -DRb.start_service(here, [1, 2, "III", 4, "five", 6]) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/darrayc.rb b/sample/drb/darrayc.rb deleted file mode 100644 index 579e11564e9f96..00000000000000 --- a/sample/drb/darrayc.rb +++ /dev/null @@ -1,47 +0,0 @@ -=begin - distributed Ruby --- Array client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service(nil, nil) -ro = DRbObject.new(nil, there) -p ro.size - -puts "# collect" -a = ro.collect { |x| - x + x -} -p a - -puts "# find" -p ro.find { |x| x.kind_of? String } - -puts "# each, break" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, break" -ro.each do |x| - break if x == "five" - puts x -end - -puts "# each, next" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, redo" -count = 0 -ro.each do |x| - count += 1 - puts count - redo if count == 3 -end diff --git a/sample/drb/dbiff.rb b/sample/drb/dbiff.rb deleted file mode 100644 index 290eb1d28b55fe..00000000000000 --- a/sample/drb/dbiff.rb +++ /dev/null @@ -1,51 +0,0 @@ -# -# dbiff.rb - distributed cdbiff (server) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' -require 'drb/observer' - -class Biff - include DRb::DRbObservable - - def initialize(filename, interval) - super() - @filename = filename - @interval = interval - end - - def run - last = Time.now - while true - begin - sleep(@interval) - current = File::mtime(@filename) - if current > last - changed - begin - notify_observers(@filename, current) - rescue Error - end - last = current - end - rescue - next - end - end - end -end - -def main - filename = "/var/mail/#{ENV['USER']}" - interval = 15 - uri = 'druby://:19903' - - biff = Biff.new(filename, interval) - - DRb.start_service(uri, biff) - biff.run -end - -main - diff --git a/sample/drb/dcdbiff.rb b/sample/drb/dcdbiff.rb deleted file mode 100644 index 6a24680c33fc56..00000000000000 --- a/sample/drb/dcdbiff.rb +++ /dev/null @@ -1,43 +0,0 @@ -# -# dcdbiff.rb - distributed cdbiff (client) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' - -class Notify - include DRbUndumped - - def initialize(biff, command) - @biff = biff - @command = command - - @biff.add_observer(self) - end - - def update(filename, time) - p [filename, time] if $DEBUG - system(@command) - end - - def done - begin - @biff.delete_observer(self) - rescue - end - end -end - -def main - command = 'eject' - uri = 'druby://localhost:19903' - - DRb.start_service - biff = DRbObject.new(nil, uri) - notify = Notify.new(biff, command) - - trap("INT"){ notify.done } - DRb.thread.join -end - -main diff --git a/sample/drb/dchatc.rb b/sample/drb/dchatc.rb deleted file mode 100644 index 2b8ddbf4cc649b..00000000000000 --- a/sample/drb/dchatc.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- chat client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class ChatClient - include DRbUndumped - - def initialize(name) - @name = name - @key = nil - end - attr_reader(:name) - attr_accessor(:key) - - def message(there, str) - raise 'invalid key' unless @key == there - puts str - end -end - -if __FILE__ == $0 - begin - there = ARGV.shift - name = ARGV.shift - raise "usage" unless (there and name) - rescue - $stderr.puts("usage: #{$0} ") - exit 1 - end - DRb.start_service - ro = DRbObject.new(nil, there) - - chat = ChatClient.new(name) - entry = ro.add_member(chat) - while gets - entry.say($_) - end -end diff --git a/sample/drb/dchats.rb b/sample/drb/dchats.rb deleted file mode 100644 index c96486a4527d97..00000000000000 --- a/sample/drb/dchats.rb +++ /dev/null @@ -1,69 +0,0 @@ -=begin - distributed Ruby --- chat server - Copyright (c) 1999-2000 Masatoshi SEKI -=end -require 'drb/drb' - -class ChatEntry - include DRbUndumped - - def initialize(server, there) - @server = server - @there = there - @name = there.name - @key = there.key = Time.now - end - attr :name, true - attr :there - - def say(str) - @server.distribute(@there, str) - end - - def listen(str) - @there.message(@key, str) - end -end - - -class ChatServer - def initialize - @mutex = Thread::Mutex.new - @members = {} - end - - def add_member(there) - client = ChatEntry.new(self, there) - @mutex.synchronize do - @members[there] = client - end - client - end - - def distribute(there, str) - name = @members[there].name - msg = "<#{name}> #{str}" - msg2 = ">#{name}< #{str}" - @mutex.synchronize do - for m in @members.keys - begin - if m == there - @members[m].listen(msg2) - else - @members[m].listen(msg) - end - rescue - p $! - @members.delete(m) - end - end - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, ChatServer.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/dhasen.rb b/sample/drb/dhasen.rb deleted file mode 100644 index 13ff38940e7687..00000000000000 --- a/sample/drb/dhasen.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server --- chasen server - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby dhasen.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby dhasenc.rb druby://yourhost:7640 - -=end - -require 'drb/drb' -require 'chasen' - -class Dhasen - include DRbUndumped - - def initialize - @mutex = Thread::Mutex.new - end - - def sparse(str, *arg) - @mutex.synchronize do - Chasen.getopt(*arg) - Chasen.sparse(str) - end - end -end - -if __FILE__ == $0 - DRb.start_service(nil, Dhasen.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dhasenc.rb b/sample/drb/dhasenc.rb deleted file mode 100644 index dddac9882cc970..00000000000000 --- a/sample/drb/dhasenc.rb +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -=begin - distributed Ruby --- dRuby Sample Client -- chasen client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") -DRb.start_service -dhasen = DRbObject.new(nil, there) - -print dhasen.sparse("本日は、晴天なり。", "-F", '(%BB %m %M)\n', "-j") -print dhasen.sparse("本日は、晴天なり。", "-F", '(%m %M)\n') diff --git a/sample/drb/dlogc.rb b/sample/drb/dlogc.rb deleted file mode 100644 index 3939a718271c74..00000000000000 --- a/sample/drb/dlogc.rb +++ /dev/null @@ -1,16 +0,0 @@ -=begin - distributed Ruby --- Log test - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ro = DRbObject.new(nil, there) -ro.log(123) -ro.log("hello") -sleep 2 -ro.log("wakeup") - diff --git a/sample/drb/dlogd.rb b/sample/drb/dlogd.rb deleted file mode 100644 index a87e6603464a73..00000000000000 --- a/sample/drb/dlogd.rb +++ /dev/null @@ -1,38 +0,0 @@ -=begin - distributed Ruby --- Log server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class Logger - def initialize(fname) - @fname = fname.to_s - @fp = File.open(@fname, "a+") - @queue = Thread::Queue.new - @th = Thread.new { self.flush } - end - - def log(str) - @queue.push("#{Time.now}\t" + str.to_s) - end - - def flush - begin - while(1) - @fp.puts(@queue.pop) - @fp.flush - end - ensure - @fp.close - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, Logger.new('/usr/tmp/dlogd.log')) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dqin.rb b/sample/drb/dqin.rb deleted file mode 100644 index 4751335fff7c3e..00000000000000 --- a/sample/drb/dqin.rb +++ /dev/null @@ -1,13 +0,0 @@ -=begin - distributed Ruby --- store - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -queue.push(DQEntry.new(DRb.uri)) diff --git a/sample/drb/dqlib.rb b/sample/drb/dqlib.rb deleted file mode 100644 index 75f2e6115b7829..00000000000000 --- a/sample/drb/dqlib.rb +++ /dev/null @@ -1,14 +0,0 @@ -class DQEntry - def initialize(name) - @name = name - end - - def greeting - "Hello, This is #{@name}." - end - alias to_s greeting -end - -if __FILE__ == $0 - puts DQEntry.new('DQEntry') -end diff --git a/sample/drb/dqout.rb b/sample/drb/dqout.rb deleted file mode 100644 index f2b0b4ac95a952..00000000000000 --- a/sample/drb/dqout.rb +++ /dev/null @@ -1,14 +0,0 @@ -=begin - distributed Ruby --- fetch - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -entry = queue.pop -puts entry.greeting diff --git a/sample/drb/dqueue.rb b/sample/drb/dqueue.rb deleted file mode 100644 index a9afa8c8585ec8..00000000000000 --- a/sample/drb/dqueue.rb +++ /dev/null @@ -1,11 +0,0 @@ -=begin - distributed Ruby --- Queue - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -DRb.start_service(nil, Thread::Queue.new) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/drbc.rb b/sample/drb/drbc.rb deleted file mode 100644 index 50a86c39e8a111..00000000000000 --- a/sample/drb/drbc.rb +++ /dev/null @@ -1,45 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - ro = DRbObject.new_with_uri(there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbch.rb b/sample/drb/drbch.rb deleted file mode 100644 index 07fdcd5fae1e67..00000000000000 --- a/sample/drb/drbch.rb +++ /dev/null @@ -1,48 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'drb/http' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb::DRbConn.proxy_map['x68k'] = 'http://x68k/~mas/http_cgi.rb' - - DRb.start_service() - ro = DRbObject.new(nil, there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbm.rb b/sample/drb/drbm.rb deleted file mode 100644 index 3390608cd1889f..00000000000000 --- a/sample/drb/drbm.rb +++ /dev/null @@ -1,60 +0,0 @@ -=begin - multiple DRbServer - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbm.rb - | druby://yourhost:7640 druby://yourhost:7641 - - Terminal 2 - | % ruby drbmc.rb druby://yourhost:7640 druby://yourhost:7641 - | [#, "FOO"] - | [#, "FOO"] - -=end - -require 'drb/drb' - -class Hoge - include DRbUndumped - def initialize(s) - @str = s - end - - def to_s - @str - end -end - -class Foo - def initialize(s='FOO') - @hoge = Hoge.new(s) - end - - def hello - @hoge - end -end - -class Bar < Foo - def initialize(foo) - @hoge = foo.hello - end -end - - -if __FILE__ == $0 - foo = Foo.new - s1 = DRb::DRbServer.new('druby://:7640', foo) - s2 = DRb::DRbServer.new('druby://:7641', Bar.new(foo)) - - puts "#{s1.uri} #{s2.uri}" - - s1.thread.join - s2.thread.join -end - diff --git a/sample/drb/drbmc.rb b/sample/drb/drbmc.rb deleted file mode 100644 index fd191401e6d40b..00000000000000 --- a/sample/drb/drbmc.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin - multiple DRbServer client - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -require 'drb/drb' - -if __FILE__ == $0 - s1 = ARGV.shift - s2 = ARGV.shift - unless s1 && s2 - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - r1 = DRbObject.new(nil, s1) - r2 = DRbObject.new(nil, s2) - - p [r1.hello, r1.hello.to_s] - p [r2.hello, r2.hello.to_s] -end diff --git a/sample/drb/drbs-acl.rb b/sample/drb/drbs-acl.rb deleted file mode 100644 index 71c4f7bf428364..00000000000000 --- a/sample/drb/drbs-acl.rb +++ /dev/null @@ -1,51 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | 6 - | 10 - -=end - -require 'drb/drb' -require 'acl' - -class DRbEx - def initialize - @hello = 'hello' - end - - def hello - info = Thread.current['DRb'] - p info['socket'].peeraddr if info - @hello - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -if __FILE__ == $0 - acl = ACL.new(%w(deny all - allow 192.168.1.* - allow localhost)) - - DRb.install_acl(acl) - - DRb.start_service(nil, DRbEx.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/drbs.rb b/sample/drb/drbs.rb deleted file mode 100644 index 5a913d9918bd4b..00000000000000 --- a/sample/drb/drbs.rb +++ /dev/null @@ -1,64 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000,2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | .... - -=end - -require 'drb/drb' - -class DRbEx - include DRbUndumped - - def initialize - @hello = 'hello' - end - - def hello - cntxt = Thread.current['DRb'] - if cntxt - p cntxt['server'].uri - p cntxt['client'].peeraddr - end - Foo::Unknown.new - end - - def err - raise FooError - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -class Foo - class Unknown - end -end - -class FooError < RuntimeError -end - -if __FILE__ == $0 - DRb.start_service(ARGV.shift || 'druby://:7640', DRbEx.new) - puts DRb.uri - Thread.new do - sleep 10 - DRb.stop_service - end - DRb.thread.join -end - diff --git a/sample/drb/drbssl_c.rb b/sample/drb/drbssl_c.rb deleted file mode 100644 index 65112f6e7872a8..00000000000000 --- a/sample/drb/drbssl_c.rb +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -there = ARGV.shift || "drbssl://localhost:3456" - -config = Hash.new -config[:SSLVerifyMode] = OpenSSL::SSL::VERIFY_PEER -config[:SSLVerifyCallback] = lambda{|ok,x509_store| - p [ok, x509_store.error_string] - true -} - -DRb.start_service(nil,nil,config) -h = DRbObject.new(nil, there) -while line = gets - p h.hello(line.chomp) -end diff --git a/sample/drb/drbssl_s.rb b/sample/drb/drbssl_s.rb deleted file mode 100644 index 4d96f591d4edf4..00000000000000 --- a/sample/drb/drbssl_s.rb +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -here = ARGV.shift || "drbssl://localhost:3456" - -class HelloWorld - include DRbUndumped - - def hello(name) - "Hello, #{name}." - end -end - -config = Hash.new -config[:verbose] = true -begin - data = open("sample.key"){|io| io.read } - config[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(data) - data = open("sample.crt"){|io| io.read } - config[:SSLCertificate] = OpenSSL::X509::Certificate.new(data) -rescue - $stderr.puts "Switching to use self-signed certificate" - config[:SSLCertName] = - [ ["C","JP"], ["O","Foo.DRuby.Org"], ["CN", "Sample"] ] -end - -DRb.start_service(here, HelloWorld.new, config) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/extserv_test.rb b/sample/drb/extserv_test.rb deleted file mode 100644 index 2c4f485dc67b45..00000000000000 --- a/sample/drb/extserv_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -=begin - dRuby sample - Copyright (c) 2000 Masatoshi SEKI - -= How to play - -* Terminal 1 - - % ruby -I. extserv_test.rb server - druby://yourhost:12345 - -* Terminal 2 - - % ruby -I. extserv_test.rb druby://yourhost:12345 - ... - -=end - -require 'drb/drb' - -def ARGV.shift - it = super() - raise "usage:\nserver: #{$0} server []\nclient: #{$0} [quit] " unless it - it -end - -class Foo - include DRbUndumped - - def initialize(str) - @str = str - end - - def hello(it) - "#{it}: #{self}" - end - - def to_s - @str - end -end - -cmd = ARGV.shift -case cmd -when 'itest1', 'itest2' - require 'drb/extserv' - - front = Foo.new(cmd) - server = DRb::DRbServer.new(nil, front) - es = DRb::ExtServ.new(ARGV.shift, ARGV.shift, server) - server.thread.join - -when 'server' - require 'drb/extservm' - - DRb::ExtServManager.command['itest1'] = "ruby -I. #{$0} itest1" - DRb::ExtServManager.command['itest2'] = "ruby -I. #{$0} itest2" - - s = DRb::ExtServManager.new - DRb.start_service(ARGV.shift, s) - puts DRb.uri - DRb.thread.join - - -else - uri = (cmd == 'quit') ? ARGV.shift : cmd - - DRb.start_service - s = DRbObject.new(nil, uri) - t1 = s.service('itest1').front - puts t1 - t2 = s.service('itest2').front - puts t2 - puts t1.hello(t2) - if (cmd == 'quit') - s.service('itest1').stop_service - s.service('itest2').stop_service - end -end - diff --git a/sample/drb/gw_ct.rb b/sample/drb/gw_ct.rb deleted file mode 100644 index 062278401839c3..00000000000000 --- a/sample/drb/gw_ct.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service(nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:tcp] = Foo.new -gets - -it = ro[:unix] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets - - diff --git a/sample/drb/gw_cu.rb b/sample/drb/gw_cu.rb deleted file mode 100644 index 8079cbdc4fd6fb..00000000000000 --- a/sample/drb/gw_cu.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'drb/drb' -require 'drb/unix' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service('drbunix:', nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:unix] = Foo.new -gets - -it = ro[:tcp] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets diff --git a/sample/drb/gw_s.rb b/sample/drb/gw_s.rb deleted file mode 100644 index c2bea0baadf06a..00000000000000 --- a/sample/drb/gw_s.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'drb/drb' -require 'drb/unix' -require 'drb/gw' - -DRb.install_id_conv(DRb::GWIdConv.new) -gw = DRb::GW.new -s1 = DRb::DRbServer.new(ARGV.shift, gw) -s2 = DRb::DRbServer.new(ARGV.shift, gw) -s1.thread.join -s2.thread.join diff --git a/sample/drb/holderc.rb b/sample/drb/holderc.rb deleted file mode 100644 index e627916d76a844..00000000000000 --- a/sample/drb/holderc.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - $stderr.puts("usage: #{$0} ") - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -ary = [] -10.times do - ary.push(ro.gen) -end - -sleep 5 if $DEBUG - -ary.each do |e| - p e.sample([1]) -end diff --git a/sample/drb/holders.rb b/sample/drb/holders.rb deleted file mode 100644 index 293426faa5e37b..00000000000000 --- a/sample/drb/holders.rb +++ /dev/null @@ -1,63 +0,0 @@ -=begin -= How to play. - -== with timeridconv: - % ruby -d holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 - - -== without timeridconv: - % ruby holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 -=end - - -require 'drb/drb' - -class DRbEx3 - include DRbUndumped - - def initialize(n) - @v = n - end - - def sample(list) - sum = 0 - list.each do |e| - sum += e.to_i - end - @v * sum - end -end - -class DRbEx4 - include DRbUndumped - - def initialize - @curr = 1 - end - - def gen - begin - @curr += 1 - DRbEx3.new(@curr) - ensure - GC.start - end - end -end - -if __FILE__ == $0 - if $DEBUG - require 'drb/timeridconv' - DRb.install_id_conv(DRb::TimerIdConv.new(2)) - end - - DRb.start_service(nil, DRbEx4.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/http0.rb b/sample/drb/http0.rb deleted file mode 100644 index e40d81031169a4..00000000000000 --- a/sample/drb/http0.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'drb/drb' -require 'net/http' -require 'uri' - -module DRb - module HTTP0 - class StrStream - def initialize(str='') - @buf = str - end - attr_reader :buf - - def read(n) - begin - return @buf[0,n] - ensure - @buf[0,n] = '' - end - end - - def write(s) - @buf.concat s - end - end - - def self.uri_option(uri, config) - return uri, nil - end - - def self.open(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - ClientSide.new(uri, config) - end - - class ClientSide - def initialize(uri, config) - @uri = uri - @res = nil - @config = config - @msg = DRbMessage.new(config) - @proxy = ENV['HTTP_PROXY'] - end - - def close; end - def alive?; false; end - - def send_request(ref, msg_id, *arg, &b) - stream = StrStream.new - @msg.send_request(stream, ref, msg_id, *arg, &b) - @reply_stream = StrStream.new - post(@uri, stream.buf) - end - - def recv_reply - @msg.recv_reply(@reply_stream) - end - - def post(url, data) - it = URI.parse(url) - path = [(it.path=='' ? '/' : it.path), it.query].compact.join('?') - http = Net::HTTP.new(it.host, it.port) - sio = StrStream.new - http.post(path, data, {'Content-Type'=>'application/octetstream;'}) do |str| - sio.write(str) - if @config[:load_limit] < sio.buf.size - raise TypeError, 'too large packet' - end - end - @reply_stream = sio - end - end - end - DRbProtocol.add_protocol(HTTP0) -end diff --git a/sample/drb/http0serv.rb b/sample/drb/http0serv.rb deleted file mode 100644 index 2e853312e12a6c..00000000000000 --- a/sample/drb/http0serv.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'webrick' -require 'drb/drb' -require_relative 'http0' - -module DRb - module HTTP0 - - def self.open_server(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - Server.new(uri, config) - end - - class Callback < WEBrick::HTTPServlet::AbstractServlet - def initialize(config, drb) - @config = config - @drb = drb - @queue = Thread::Queue.new - end - - def do_POST(req, res) - @req = req - @res = res - @drb.push(self) - @res.body = @queue.pop - @res['content-type'] = 'application/octet-stream;' - end - - def req_body - @req.body - end - - def reply(body) - @queue.push(body) - end - - def close - @queue.push('') - end - end - - class Server - def initialize(uri, config) - @uri = uri - @config = config - @queue = Thread::Queue.new - setup_webrick(uri) - end - attr_reader :uri - - def close - @server.shutdown if @server - @server = nil - end - - def push(callback) - @queue.push(callback) - end - - def accept - client = @queue.pop - ServerSide.new(uri, client, @config) - end - - def setup_webrick(uri) - logger = WEBrick::Log::new($stderr, WEBrick::Log::FATAL) - u = URI.parse(uri) - s = WEBrick::HTTPServer.new(:Port => u.port, - :AddressFamily => Socket::AF_INET, - :BindAddress => u.host, - :Logger => logger, - :ServerType => Thread) - s.mount(u.path, Callback, self) - @server = s - s.start - end - end - - class ServerSide - def initialize(uri, callback, config) - @uri = uri - @callback = callback - @config = config - @msg = DRbMessage.new(@config) - @req_stream = StrStream.new(@callback.req_body) - end - attr_reader :uri - - def close - @callback.close if @callback - @callback = nil - end - - def alive?; false; end - - def recv_request - begin - @msg.recv_request(@req_stream) - rescue - close - raise $! - end - end - - def send_reply(succ, result) - begin - return unless @callback - stream = StrStream.new - @msg.send_reply(stream, succ, result) - @callback.reply(stream.buf) - rescue - close - raise $! - end - end - end - end -end diff --git a/sample/drb/name.rb b/sample/drb/name.rb deleted file mode 100644 index 6d88186dabf1d3..00000000000000 --- a/sample/drb/name.rb +++ /dev/null @@ -1,117 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -=begin -How to play. - -* start server - Terminal 1 - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* start client - Terminal 2 - | % ruby namec.rb druby://yourhost:7640 - | # - | # - | 1 - | 2 - | [return] to continue - -* restart server - Terminal 1 - type [return] - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* continue client - Terminal 2 - type [return] - | 1 - | 2 -=end - -require 'drb/drb' - -module DRbNamedObject - DRbNAMEDICT = {} - DRBNAMEMUTEX = Thread::Mutex.new - attr_reader(:drb_name) - - def drb_name=(name) - @drb_name = name - DRBNAMEMUTEX.synchronize do - raise(IndexError, name) if DRbNAMEDICT[name] - DRbNAMEDICT[name] = self - end - end -end - -class DRbNamedIdConv < DRb::DRbIdConv - def initialize - @dict = DRbNamedObject::DRbNAMEDICT - end - - def to_obj(ref) - @dict.fetch(ref) do super end - end - - def to_id(obj) - if obj.kind_of? DRbNamedObject - return obj.drb_name - else - return super - end - end -end - -class Seq - include DRbUndumped - include DRbNamedObject - - def initialize(v, name) - @counter = v - @mutex = Thread::Mutex.new - self.drb_name = name - end - - def next_value - @mutex.synchronize do - @counter += 1 - return @counter - end - end -end - -class Front - def initialize - seq = Seq.new(0, 'seq') - mutex = Thread::Mutex.new - mutex.extend(DRbUndumped) - mutex.extend(DRbNamedObject) - mutex.drb_name = 'mutex' - @name = {} - @name['seq'] = seq - @name['mutex'] = mutex - end - - def [](k) - @name[k] - end -end - -if __FILE__ == $0 - uri = ARGV.shift - - name_conv = DRbNamedIdConv.new - - DRb.install_id_conv(name_conv) - DRb.start_service(uri, Front.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/namec.rb b/sample/drb/namec.rb deleted file mode 100644 index 98b9d0e532afbf..00000000000000 --- a/sample/drb/namec.rb +++ /dev/null @@ -1,36 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample Client - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - puts "usage: #{$0} " - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -seq = ro["seq"] -mutex = ro["mutex"] - -p seq -p mutex - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - -puts '[return] to continue' -gets - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - diff --git a/sample/drb/old_tuplespace.rb b/sample/drb/old_tuplespace.rb deleted file mode 100644 index 2d5310086e9362..00000000000000 --- a/sample/drb/old_tuplespace.rb +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/local/bin/ruby -# TupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class TupleSpace - class Template - def initialize(list) - @list = list - @check_idx = [] - @list.each_with_index do |x, i| - @check_idx.push i if x - end - @size = @list.size - end - - attr :size - alias length size - - def match(tuple) - return nil if tuple.size != self.size - @check_idx.each do |i| - unless @list[i] === tuple[i] - return false - end - end - return true - end - end - - def initialize - @que = {} - @waiting = {} - @que.taint # enable tainted communication - @waiting.taint - self.taint - end - - def wakeup_waiting(tuple) - sz = tuple.length - return nil unless @waiting[sz] - - x = nil - i = -1 - found = false - @waiting[sz] = @waiting[sz].find_all { |x| - if x[0].match(tuple) - begin - x[1].wakeup - rescue ThreadError - end - false - else - true - end - } - end - - def put_waiting(template, thread) - sz = template.length - @waiting[sz] = [] unless @waiting[sz] - @waiting[sz].push([Template.new(template), thread]) - end - private :wakeup_waiting - private :put_waiting - - def get_que(template) - sz = template.length - return nil unless @que[sz] - - template = Template.new(template) - - x = nil - i = -1 - found = false - @que[sz].each_with_index do |x, i| - if template.match(x) - found = true - break - end - end - return nil unless found - - @que[sz].delete_at(i) - - return x - end - - def put_que(tuple) - sz = tuple.length - @que[sz] = [] unless @que[sz] - @que[sz].push tuple - end - private :get_que - private :put_que - - def out(*tuples) - tuples.each do |tuple| - Thread.critical = true - put_que(tuple) - wakeup_waiting(tuple) - Thread.critical = false - end - end - alias put out - alias write out - - def in(template, non_block=false) - begin - loop do - Thread.critical = true - tuple = get_que(template) - unless tuple - if non_block - raise ThreadError, "queue empty" - end - put_waiting(template, Thread.current) - Thread.stop - else - return tuple - end - end - ensure - Thread.critical = false - end - end - alias get in - alias take in - - def rd(template, non_block=false) - tuple = self.in(template, non_block) - out(tuple) - tuple - end - alias read rd - - def mv(dest, template, non_block=false) - tuple = self.in(template, non_block) - begin - dest.out(tuple) - rescue - self.out(tuple) - end - end - alias move mv -end - -if __FILE__ == $0 - ts = TupleSpace.new - clients = [] - servers = [] - - def server(ts, id) - Thread.start { - loop do - req = ts.in(['req', nil, nil]) - ac = req[1] - num = req[2] - sleep id - ts.out([ac, id, num, num * num]) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - tuples = (1..10).collect { |i| - ['req', ac, i * 10 + n] - } - ts.out(*tuples) - ts.out(tuples[0]) - puts "out: #{n}" - 11.times do |i| - ans = ts.in([ac, nil, nil, nil]) - puts "client(#{n}) server(#{ans[1]}) #{ans[2]} #{ans[3]}" - end - } - end - - def watcher(ts) - Thread.start { - loop do - begin - sleep 1 - p ts.rd(['req', nil, nil], true) - rescue ThreadError - puts "'req' not found." - end - end - } - end - - (0..3).each do |n| - servers.push(server(ts, n)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - (1..3).each do - watcher(ts) - end - - clients.each do |t| - t.join - end -end - - - diff --git a/sample/drb/rinda_ts.rb b/sample/drb/rinda_ts.rb deleted file mode 100644 index 6f2fae5c0f20fe..00000000000000 --- a/sample/drb/rinda_ts.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'drb/drb' -require 'rinda/tuplespace' - -uri = ARGV.shift -DRb.start_service(uri, Rinda::TupleSpace.new) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/rindac.rb b/sample/drb/rindac.rb deleted file mode 100644 index 72be09deafda56..00000000000000 --- a/sample/drb/rindac.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -(1..10).each do |n| - ts.write(['sum', DRb.uri, n]) -end - -(1..10).each do |n| - ans = ts.take(['ans', DRb.uri, n, nil]) - p [ans[2], ans[3]] -end - diff --git a/sample/drb/rindas.rb b/sample/drb/rindas.rb deleted file mode 100644 index 9fd9ada2d10106..00000000000000 --- a/sample/drb/rindas.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -def do_it(v) - puts "do_it(#{v})" - v + v -end - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -while true - r = ts.take(['sum', nil, nil]) - v = do_it(r[2]) - ts.write(['ans', r[1], r[2], v]) -end diff --git a/sample/drb/ring_echo.rb b/sample/drb/ring_echo.rb deleted file mode 100644 index c54628b54c2064..00000000000000 --- a/sample/drb/ring_echo.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' -require 'drb/eq' -require 'rinda/ring' - -class RingEcho - include DRbUndumped - def initialize(name) - @name = name - end - - def echo(str) - "#{@name}: #{str}" - end -end - -DRb.start_service - -renewer = Rinda::SimpleRenewer.new - -finder = Rinda::RingFinger.new -ts = finder.lookup_ring_any -ts.read_all([:name, :RingEcho, nil, nil]).each do |tuple| - p tuple[2] - puts tuple[2].echo('Hello, World') rescue nil -end -ts.write([:name, :RingEcho, RingEcho.new(DRb.uri), ''], renewer) - -DRb.thread.join - diff --git a/sample/drb/ring_inspect.rb b/sample/drb/ring_inspect.rb deleted file mode 100644 index c096cd7034932b..00000000000000 --- a/sample/drb/ring_inspect.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rinda/ring' -require 'drb/drb' - -class Inspector - def initialize - end - - def primary - Rinda::RingFinger.primary - end - - def list_place - Rinda::RingFinger.to_a - end - - def list(idx = -1) - if idx < 0 - ts = primary - else - ts = list_place[idx] - raise "RingNotFound" unless ts - end - ts.read_all([:name, nil, nil, nil]) - end -end - -def main - DRb.start_service - r = Inspector.new -end diff --git a/sample/drb/ring_place.rb b/sample/drb/ring_place.rb deleted file mode 100644 index 11c6c2fe80287f..00000000000000 --- a/sample/drb/ring_place.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'drb/drb' -require 'rinda/ring' -require 'rinda/tuplespace' - -unless $DEBUG - # Run as a daemon... - exit!( 0 ) if fork - Process.setsid - exit!( 0 ) if fork -end - -DRb.start_service(ARGV.shift) - -ts = Rinda::TupleSpace.new -place = Rinda::RingServer.new(ts) - -if $DEBUG - puts DRb.uri - DRb.thread.join -else - STDIN.reopen(IO::NULL) - STDOUT.reopen(IO::NULL, 'w') - STDERR.reopen(IO::NULL, 'w') - DRb.thread.join -end diff --git a/sample/drb/simpletuple.rb b/sample/drb/simpletuple.rb deleted file mode 100644 index 4bb4b1cff91475..00000000000000 --- a/sample/drb/simpletuple.rb +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/local/bin/ruby -# SimpleTupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class SimpleTupleSpace - def initialize - @hash = {} - @waiting = {} - @hash.taint - @waiting.taint - self.taint - end - - def out(key, obj) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - @hash[key].push obj - begin - t = @waiting[key].shift - @waiting.delete(key) if @waiting[key].length == 0 - t.wakeup if t - rescue ThreadError - retry - ensure - Thread.critical = false - end - end - - def in(key) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - begin - loop do - if @hash[key].length == 0 - @waiting[key].push Thread.current - Thread.stop - else - return @hash[key].shift - end - end - ensure - @hash.delete(key) if @hash[key].length == 0 - Thread.critical = false - end - end -end - -if __FILE__ == $0 - ts = SimpleTupleSpace.new - clients = [] - servers = [] - - def server(ts) - Thread.start { - loop do - req = ts.in('req') - ac = req[0] - num = req[1] - ts.out(ac, num * num) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - ts.out('req', [ac, n]) - ans = ts.in(ac) - puts "#{n}: #{ans}" - } - end - - 3.times do - servers.push(server(ts)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - clients.each do |t| - t.join - end -end - - diff --git a/sample/drb/speedc.rb b/sample/drb/speedc.rb deleted file mode 100644 index 64b8a650216760..00000000000000 --- a/sample/drb/speedc.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/local/bin/ruby - -uri = ARGV.shift || raise("usage: #{$0} URI") -N = (ARGV.shift || 100).to_i - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - client = ROMP::Client.new(uri, false) - foo = client.resolve("foo") -when /^druby:/ - require 'drb/drb' - - DRb.start_service - foo = DRbObject.new(nil, uri) -end - -N.times do |n| - foo.foo(n) -end diff --git a/sample/drb/speeds.rb b/sample/drb/speeds.rb deleted file mode 100644 index 79840594231e94..00000000000000 --- a/sample/drb/speeds.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Foo - attr_reader :i - def initialize - @i = 0 - end - - def foo(i) - @i = i - i + i - end -end - -# server = ROMP::Server.new('tcpromp://localhost:4242', nil, true) - -uri = ARGV.shift || raise("usage: #{$0} URI") -foo = Foo.new - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - server = ROMP::Server.new(uri, nil, true) - server.bind(foo, "foo") - -when /^druby:/ - require 'drb/drb' - - DRb.start_service(uri, Foo.new) -end - -DRb.thread.join diff --git a/spec/bin/bundle b/spec/bin/bundle new file mode 100755 index 00000000000000..8f8b5352951537 --- /dev/null +++ b/spec/bin/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb index afa2d1716fb390..2e69821f68fc7a 100644 --- a/spec/bundler/bundler/build_metadata_spec.rb +++ b/spec/bundler/bundler/build_metadata_spec.rb @@ -6,18 +6,20 @@ RSpec.describe Bundler::BuildMetadata do before do allow(Time).to receive(:now).and_return(Time.at(0)) - Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + Bundler::BuildMetadata.instance_variable_set(:@timestamp, nil) end - describe "#built_at" do - it "returns %Y-%m-%d formatted time" do - expect(Bundler::BuildMetadata.built_at).to eq "1970-01-01" + describe "#timestamp" do + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + expect(Bundler::BuildMetadata.timestamp).to eq "1970-01-01" end - end - describe "#release?" do - it "returns false as default" do - expect(Bundler::BuildMetadata.release?).to be_falsey + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, "2025-01-01") + expect(Bundler::BuildMetadata.timestamp).to eq "2025-01-01" + ensure + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) end end @@ -40,10 +42,9 @@ describe "#to_h" do subject { Bundler::BuildMetadata.to_h } - it "returns a hash includes Built At, Git SHA and Released Version" do - expect(subject["Built At"]).to eq "1970-01-01" + it "returns a hash includes Timestamp, and Git SHA" do + expect(subject["Timestamp"]).to eq "1970-01-01" expect(subject["Git SHA"]).to be_instance_of(String) - expect(subject["Released Version"]).to be_falsey end end end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 03edc1c81ede34..41cd8c636d4041 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -112,20 +112,34 @@ def out_with_macos_man_workaround end context "with --verbose" do - it "prints the running command" do + before do gemfile "source 'https://gem.repo1'" + end + + it "prints the running command" do bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") - end - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true + bundle "install", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true - expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") + it "prints the simulated version too when setting is enabled" do + bundle "config simulate_version 4", verbose: true + bundle "info bundler", verbose: true + expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION} (simulating Bundler 4)") + end + end + + context "with verbose configuration" do + before do + bundle "config verbose true" + end + + it "prints the running command" do + gemfile "source 'https://gem.repo1'" + bundle "info bundler" + expect(out).to start_with("Running `bundle info bundler` with bundler #{Bundler::VERSION}") end end @@ -253,10 +267,9 @@ def out_with_macos_man_workaround it "shows the bundler version just as the `bundle` executable does" do bundler "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "shows the bundler version just as the `bundle` executable does", bundler: "4" do + bundle "config simulate_version 4" bundler "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 6164025ac61c16..36b9b949908caf 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -88,7 +88,7 @@ /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m) end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } it "should raise a Bundler::Fetcher::BadAuthenticationError that doesn't contain the password" do @@ -116,7 +116,7 @@ to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } it "should raise a Bundler::Fetcher::FallbackError that doesn't contain the password" do @@ -201,39 +201,29 @@ end end - context "when the request response causes an error included in HTTP_ERRORS" do - let(:message) { nil } - let(:error) { RuntimeError.new(message) } + context "when the request response causes an HTTP error" do + let(:message) { "error about network" } + let(:error) { error_class.new(message) } before do - stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end - it "should trace log the error" do - allow(Bundler).to receive_message_chain(:ui, :debug) - expect(Bundler).to receive_message_chain(:ui, :trace).with(error) - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) - end - - context "when error message is about the host being down" do - let(:message) { "host down: http://www.uri-to-fetch.com" } + context "that it's retryable" do + let(:error_class) { Gem::Timeout::Error } - it "should raise a Bundler::Fetcher::NetworkDownError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, - /Could not reach host www.uri-to-fetch.com/) + it "should trace log the error" do + allow(Bundler).to receive_message_chain(:ui, :debug) + expect(Bundler).to receive_message_chain(:ui, :trace).with(error) + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) end - end - - context "when error message is not about host down" do - let(:message) { "other error about network" } it "should raise a Bundler::HTTPError" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)") + "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } before do allow(net_http_get).to receive(:basic_auth).with("username", "password") @@ -241,17 +231,38 @@ it "should raise a Bundler::HTTPError that doesn't contain the password" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)") + "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") end end end - context "when error message is about no route to host" do + context "when error is about the host being down" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "host down: http://www.uri-to-fetch.com" } + + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) + end + end + + context "when error is about connection refused" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "connection refused down: http://www.uri-to-fetch.com" } + + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) + end + end + + context "when error is about no route to host" do + let(:error_class) { SocketError } let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " } - it "should raise a Bundler::Fetcher::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})") + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 42271167d66b19..356858070105d1 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -423,7 +423,7 @@ it "sets BUNDLE_BIN_PATH to the bundle executable file" do subject.set_bundle_environment bin_path = ENV["BUNDLE_BIN_PATH"] - expect(bin_path).to eq(bindir.join("bundle").to_s) + expect(bin_path).to eq(exedir.join("bundle").to_s) expect(File.exist?(bin_path)).to be true end end diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 2676b06c781677..00aa6415e11c97 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -161,26 +161,47 @@ end describe "with --github" do + before do + build_git "rake", "13.0" + git("config --global url.file://#{lib_path("rake-13.0")}.insteadOf https://github.com/ruby/rake.git") + end + it "adds dependency with specified github source" do bundle "add rake --github=ruby/rake" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake"}) end - end - describe "with --github and --branch" do it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master" + bundle "add rake --github=ruby/rake --branch=main" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main"}) end - end - describe "with --github and --ref" do it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8" + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref}" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}"}) + end + + it "adds dependency with specified github source and glob" do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, branch and glob" do + bundle "add rake --github=ruby/rake --branch=main --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, ref and glob" do + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}", glob: "\.\/\*\.gemspec"}) end end @@ -215,30 +236,6 @@ end end - describe "with --github and --glob" do - it "adds dependency with specified github source" do - bundle "add rake --github=ruby/rake --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --branch --and glob" do - it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --ref and --glob" do - it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8", glob: "\.\/\*\.gemspec"}) - end - end - describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 51a89842620b11..99a8f9c1416e5f 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -625,7 +625,7 @@ def should_not_have_gems(*gems) expect(out).to eq("1.0") end - it "when using --force, it doesn't remove default gem binaries", :realworld do + it "when using --force, it doesn't remove default gem binaries" do default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false skip "irb isn't a default gem" if default_irb_version.empty? @@ -634,8 +634,6 @@ def should_not_have_gems(*gems) s.executables = "irb" end - realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" - install_gemfile <<-G source "https://gem.repo2" G diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb index dbfbec874f408d..ec44fe59f3a3f2 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -40,7 +40,7 @@ def __pry__ end end - context "when the library has an unrelated error" do + context "when the library requires a non-existent file" do before do build_lib "loadfuuu", "1.0.0" do |s| s.write "lib/loadfuuu.rb", "require_relative 'loadfuuu/bar'" @@ -65,6 +65,30 @@ def __pry__ end end + context "when the library references a non-existent constant" do + before do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "Some::NonExistent::Constant" + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + path "#{lib_path}" do + gem "loadfuuu", require: true + end + G + end + + it "does not show the bug report template" do + bundle("console", raise_on_error: false) do |input, _, _| + input.puts("exit") + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + end + end + context "when the library does not have any errors" do before do install_gemfile <<-G diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 6b3f2b4c7ec893..4a581b3058e76e 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -697,7 +697,7 @@ end G - expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/") + expect(err).to eq("Could not reach host 0.0.0.0:9384. Check your network connection and try again.") expect(err).not_to include("file://") end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index da21e44c9cf501..c47cc9727191ea 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -101,9 +101,8 @@ let(:gemfile_with_rails_weakling_and_foo_from_repo4) do build_repo4 do - FileUtils.cp rake_path, "#{gem_repo4}/gems/" - build_gem "rake", "10.0.1" + build_gem "rake", rake_version %w[2.3.1 2.3.2].each do |version| build_gem "rails", version do |s| diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 5fc3c7109b6218..ba579ffe5762b2 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -15,13 +15,13 @@ def gem_skeleton_assertions def bundle_exec_rubocop prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{rubocop_gems}", dir: bundled_app(gem_name) + bundle "config set path #{rubocop_gem_path}", dir: bundled_app(gem_name) bundle "exec rubocop --debug --config .rubocop.yml", dir: bundled_app(gem_name) end def bundle_exec_standardrb prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{standard_gems}", dir: bundled_app(gem_name) + bundle "config set path #{standard_gem_path}", dir: bundled_app(gem_name) bundle "exec standardrb --debug", dir: bundled_app(gem_name) end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 556e77e01f7cf6..1019803c8705d4 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -10,38 +10,56 @@ end context "with -v" do - it "outputs the version" do + it "outputs the version and virtual version if set" do bundle "-v" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "4" do + bundle "config simulate_version 4" bundle "-v" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with --version" do - it "outputs the version" do + it "outputs the version and virtual version if set" do bundle "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "4" do + bundle "config simulate_version 4" bundle "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with version" do - it "outputs the version with build metadata" do - bundle "version" - expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + context "when released", :ruby_repo do + before do + system_gems "bundler-2.9.9", released: true + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + end end - it "outputs the version with build metadata", bundler: "4" do - bundle "version" - expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + context "when not released" do + before do + system_gems "bundler-2.9.9", released: false + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + end end end end diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb index 446b416c102a28..0ad285104c4e38 100644 --- a/spec/bundler/commands/viz_spec.rb +++ b/spec/bundler/commands/viz_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle viz", if: Bundler.which("dot") do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" + base_system_gems "rexml", "ruby-graphviz" end it "graphs gems from the Gemfile" do @@ -71,7 +71,7 @@ context "with another gem that has a graphviz file" do before do - build_repo4 do + update_repo4 do build_gem "graphviz", "999" do |s| s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index a8cb8cb3f99d16..e0f87572dade48 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -140,14 +140,8 @@ end describe "with default gems and a lockfile", :ruby_repo do - before do - necessary_system_gems = ["tsort --version 0.1.0"] - realworld_system_gems(*necessary_system_gems) - end - it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0"] - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" @@ -184,8 +178,7 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0", "shellwords --version 0.2.0", "open3 --version 0.2.1"] - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index 670bd1fb7275a3..f9d96e488f9dcd 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -28,14 +28,14 @@ end it "displays the correct default branch", git: ">= 2.28.0" do - build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "non-standard" install_gemfile <<-G, verbose: true source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{revision_for(lib_path("foo"))[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index a0ecba21327515..a0a19582449473 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -574,9 +574,8 @@ end end - context "bundle viz", :realworld do + context "bundle viz" do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" create_file "gems.rb", "source 'https://gem.repo1'" bundle "viz" end diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index 046300391b26cc..6a8de2b949253a 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -66,7 +66,7 @@ it "includes the relevant tasks" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -85,7 +85,7 @@ it "defines a working `rake install` task", :ruby_repo do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -155,7 +155,7 @@ it "adds 'pkg' to rake/clean's CLOBBER" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), env: { "GEM_HOME" => system_gem_path.to_s } end expect(out).to eq '["pkg"]' diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index 48385b452d8537..0467d8b14abdcb 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -590,29 +590,6 @@ def confirm(msg, newline = nil) expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings" do - Dir.mkdir tmp("path_without_gemfile") - - default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", raise_on_error: false - skip "fileutils isn't a default gem" if default_fileutils_version.empty? - - realworld_system_gems "fileutils --version 1.4.1" - - realworld_system_gems "pathname --version 0.2.0" - - script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s, "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - require "bundler/inline" - - gemfile(true) do - source "https://gem.repo2" - end - - require "fileutils" - RUBY - - expect(err).to eq("The Gemfile specifies no dependencies") - end - it "does not load default timeout" do default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false skip "timeout isn't a default gem" if default_timeout_version.empty? diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index ba331e483f1d81..e61fe921ec3b05 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Dir[Spec::Path.scoped_base_system_gem_path.join("gems/compact_index*/lib")].first.to_s require "compact_index" require "digest" diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 189100edb7748c..2eade4137bd68f 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,11 +8,10 @@ module BuildMetadata include Spec::Path include Spec::Helpers - def write_build_metadata(dir: source_root) + def write_build_metadata(dir: source_root, version: Bundler::VERSION) build_metadata = { git_commit_sha: git_commit_sha, - built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - release: true, + built_at: release_date_for(version, dir: dir), } replace_build_metadata(build_metadata, dir: dir) @@ -20,7 +19,7 @@ def write_build_metadata(dir: source_root) def reset_build_metadata(dir: source_root) build_metadata = { - release: false, + built_at: nil, } replace_build_metadata(build_metadata, dir: dir) @@ -44,6 +43,11 @@ def git_commit_sha ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip end + def release_date_for(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog) + end + extend self end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index e94ca5bfc5c6ca..5cfbed3864c39c 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -277,9 +277,9 @@ def update_repo(path, build_compact_index: true) @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as base_system_gem_path do - Dir[base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{base_system_gem_path}") + with_gem_path_as scoped_base_system_gem_path do + Dir[scoped_base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || + raise("Could not find rubygems-generate_index lib directory in #{scoped_base_system_gem_path}") command = "generate_index" command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") @@ -450,9 +450,10 @@ def _build(options = {}) end @context.replace_version_file(@version, dir: build_path) + @context.replace_changelog(@version, dir: build_path) if options[:released] @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version - Spec::BuildMetadata.write_build_metadata(dir: build_path) + Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @version) @context.gem_command "build #{@context.relative_gemspec}", dir: build_path diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle new file mode 100755 index 00000000000000..8f8b5352951537 --- /dev/null +++ b/spec/bundler/support/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index 5d6d6580405c0f..aa7b12170694e3 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -require_relative "activate" +require_relative "path" -load File.expand_path("bundle", Spec::Path.bindir) +warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead" + +load Spec::Path.dev_binstub diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 83f24214f325ef..26571126637b49 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -303,6 +303,10 @@ def lock_gemfile(*args) bundle :lock, opts end + def base_system_gems(*names, **options) + system_gems names.map {|name| find_base_path(name) }, **options + end + def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} @@ -312,7 +316,7 @@ def system_gems(*gems) gem_name = g.to_s if gem_name.start_with?("bundler") version = gem_name.match(/\Abundler-(?.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } + with_built_bundler(version, released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) } elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) install_gem(gem_name, install_dir, default) else @@ -337,10 +341,10 @@ def install_gem(path, install_dir, default = false) gem_command "install #{args} '#{path}'" end - def with_built_bundler(version = nil, &block) + def with_built_bundler(version = nil, opts = {}, &block) require_relative "builders" - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block) end def with_gem_path_as(path) @@ -398,16 +402,6 @@ def pristine_system_gems(*gems) system_gems(*gems) end - def realworld_system_gems(*gems) - gems = gems.flatten - opts = gems.last.is_a?(Hash) ? gems.pop : {} - path = opts.fetch(:path, system_gem_path) - - gems.each do |gem| - gem_command "install --no-document --verbose --install-dir #{path} #{gem}" - end - end - def cache_gems(*gems, gem_repo: gem_repo1) gems = gems.flatten @@ -509,7 +503,7 @@ def with_env_vars(env_hash, &block) def require_rack_test # need to hack, so we can require rack for testing old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s + ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s require "rack/test" ENV["GEM_HOME"] = old_gem_home end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index d0542669d0b1f9..c4d2f06cbfe29d 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -45,8 +45,16 @@ def dev_gemfile @dev_gemfile ||= tool_dir.join("dev_gems.rb") end + def dev_binstub + @dev_binstub ||= bindir.join("bundle") + end + def bindir - @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe") + @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "bin") + end + + def exedir + @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe") end def installed_bindir @@ -63,7 +71,7 @@ def gem_bin def path env_path = ENV["PATH"] - env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? + env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? env_path end @@ -175,19 +183,19 @@ def bundled_app_lock bundled_app("Gemfile.lock") end - def base_system_gem_path - scoped_gem_path(base_system_gems) + def scoped_base_system_gem_path + scoped_gem_path(base_system_gem_path) end - def base_system_gems + def base_system_gem_path tmp_root.join("gems/base") end - def rubocop_gems + def rubocop_gem_path tmp_root.join("gems/rubocop") end - def standard_gems + def standard_gem_path tmp_root.join("gems/standard") end @@ -280,12 +288,19 @@ def replace_required_ruby_version(version, dir:) File.open(gemspec_file, "w") {|f| f << contents } end + def replace_changelog(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + contents = File.readlines(changelog) + contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join + File.open(changelog, "w") {|f| f << contents } + end + def git_root ruby_core? ? source_root : source_root.parent end def rake_path - Dir["#{base_system_gems}/*/*/**/rake*.gem"].first + find_base_path("rake") end def rake_version @@ -303,11 +318,15 @@ def sinatra_dependency_paths logger cgi ] - Dir[base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) + Dir[scoped_base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) end private + def find_base_path(name) + Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first + end + def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index e10400e040925c..2d681529aac2ef 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -52,13 +52,13 @@ def test_setup def setup_test_paths ENV["BUNDLE_PATH"] = nil ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gems.to_s) - dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gems.to_s) - dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gems.to_s) + dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s) + dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s) + dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s) require_relative "helpers" Helpers.install_dev_bundler @@ -100,7 +100,7 @@ def dev_bundle(*args, gemfile: dev_gemfile, path: nil) require "shellwords" # We don't use `Open3` here because it does not work on JRuby + Windows - output = `ruby #{File.expand_path("support/bundle.rb", Path.spec_dir)} #{args.shelljoin}` + output = `ruby #{Path.dev_binstub} #{args.shelljoin}` raise output unless $?.success? output ensure diff --git a/string.c b/string.c index 0425388f375afe..589946c9bc6094 100644 --- a/string.c +++ b/string.c @@ -6870,45 +6870,10 @@ str_byte_aref(VALUE str, VALUE indx) /* * call-seq: - * byteslice(index, length = 1) -> string or nil - * byteslice(range) -> string or nil - * - * Returns a substring of +self+, or +nil+ if the substring cannot be constructed. - * - * With integer arguments +index+ and +length+ given, - * returns the substring beginning at the given +index+ - * of the given +length+ (if possible), - * or +nil+ if +length+ is negative or +index+ falls outside of +self+: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(2) # => "2" - * s.byteslice(200) # => nil - * s.byteslice(4, 3) # => "456" - * s.byteslice(4, 30) # => "456789" - * s.byteslice(4, -1) # => nil - * s.byteslice(40, 2) # => nil - * - * In either case above, counts backwards from the end of +self+ - * if +index+ is negative: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(-4) # => "6" - * s.byteslice(-4, 3) # => "678" - * - * With Range argument +range+ given, returns - * byteslice(range.begin, range.size): - * - * s = '0123456789' # => "0123456789" - * s.byteslice(4..6) # => "456" - * s.byteslice(-6..-4) # => "456" - * s.byteslice(5..2) # => "" # range.size is zero. - * s.byteslice(40..42) # => nil - * - * In all cases, a returned string has the same encoding as +self+: - * - * s.encoding # => # - * s.byteslice(4).encoding # => # + * byteslice(offset, length = 1) -> string or nil + * byteslice(range) -> string or nil * + * :include: doc/string/byteslice.rdoc */ static VALUE @@ -6948,23 +6913,12 @@ str_check_beg_len(VALUE str, long *beg, long *len) /* * call-seq: - * bytesplice(index, length, str) -> string - * bytesplice(index, length, str, str_index, str_length) -> string - * bytesplice(range, str) -> string - * bytesplice(range, str, str_range) -> string - * - * Replaces some or all of the content of +self+ with +str+, and returns +self+. - * The portion of the string affected is determined using - * the same criteria as String#byteslice, except that +length+ cannot be omitted. - * If the replacement string is not the same length as the text it is replacing, - * the string will be adjusted accordingly. - * - * If +str_index+ and +str_length+, or +str_range+ are given, the content of +self+ is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range); however the substring of +str+ is not allocated as a new string. + * bytesplice(offset, length, str) -> self + * bytesplice(offset, length, str, str_offset, str_length) -> self + * bytesplice(range, str) -> self + * bytesplice(range, str, str_range) -> self * - * The form that take an Integer will raise an IndexError if the value is out - * of range; the Range form will raise a RangeError. - * If the beginning or ending offset does not land on character (codepoint) - * boundary, an IndexError will be raised. + * :include: doc/string/bytesplice.rdoc */ static VALUE @@ -8102,7 +8056,7 @@ upcase_single(VALUE str) /* * call-seq: - * upcase!(*options) -> self or nil + * upcase!(mapping) -> self or nil * * Upcases the characters in +self+; * returns +self+ if any changes were made, +nil+ otherwise: @@ -8112,7 +8066,7 @@ upcase_single(VALUE str) * s # => "HELLO WORLD!" * s.upcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase, String#downcase, String#downcase!. @@ -8144,14 +8098,14 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * upcase(*options) -> string + * upcase(mapping) -> string * * Returns a string containing the upcased characters in +self+: * * s = 'Hello World!' # => "Hello World!" * s.upcase # => "HELLO WORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase!, String#downcase, String#downcase!. @@ -8204,7 +8158,7 @@ downcase_single(VALUE str) /* * call-seq: - * downcase!(*options) -> self or nil + * downcase!(mapping) -> self or nil * * Downcases the characters in +self+; * returns +self+ if any changes were made, +nil+ otherwise: @@ -8214,7 +8168,7 @@ downcase_single(VALUE str) * s # => "hello world!" * s.downcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#downcase, String#upcase, String#upcase!. @@ -8246,14 +8200,14 @@ rb_str_downcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * downcase(*options) -> string + * downcase(mapping) -> string * * Returns a string containing the downcased characters in +self+: * * s = 'Hello World!' # => "Hello World!" * s.downcase # => "hello world!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#downcase!, String#upcase, String#upcase!. @@ -8288,7 +8242,7 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize!(*options) -> self or nil + * capitalize!(mapping) -> self or nil * * Upcases the first character in +self+; * downcases the remaining characters; @@ -8299,7 +8253,7 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) * s # => "Hello world!" * s.capitalize! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#capitalize. @@ -8328,7 +8282,7 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize(*options) -> string + * capitalize(mapping) -> string * * Returns a string containing the characters in +self+; * the first character is upcased; @@ -8337,7 +8291,7 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) * s = 'hello World!' # => "hello World!" * s.capitalize # => "Hello world!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#capitalize!. @@ -8367,7 +8321,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase!(*options) -> self or nil + * swapcase!(mapping) -> self or nil * * Upcases each lowercase character in +self+; * downcases uppercase character; @@ -8378,7 +8332,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) * s # => "hELLO wORLD!" * ''.swapcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase. @@ -8406,7 +8360,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(*options) -> string + * swapcase(mapping) -> string * * Returns a string containing the characters in +self+, with cases reversed; * each uppercase character is downcased; @@ -8415,7 +8369,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) * s = 'Hello World!' # => "Hello World!" * s.swapcase # => "hELLO wORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase!. @@ -12528,7 +12482,7 @@ sym_empty(VALUE sym) /* * call-seq: - * upcase(*options) -> symbol + * upcase(mapping) -> symbol * * Equivalent to sym.to_s.upcase.to_sym. * @@ -12544,7 +12498,7 @@ sym_upcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * downcase(*options) -> symbol + * downcase(mapping) -> symbol * * Equivalent to sym.to_s.downcase.to_sym. * @@ -12562,7 +12516,7 @@ sym_downcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * capitalize(*options) -> symbol + * capitalize(mapping) -> symbol * * Equivalent to sym.to_s.capitalize.to_sym. * @@ -12578,7 +12532,7 @@ sym_capitalize(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * swapcase(*options) -> symbol + * swapcase(mapping) -> symbol * * Equivalent to sym.to_s.swapcase.to_sym. * diff --git a/string.rb b/string.rb index a5ff79a62c04dc..a14c81ba2d82b1 100644 --- a/string.rb +++ b/string.rb @@ -391,6 +391,7 @@ # # _Substitution_ # +# - #bytesplice: Replaces bytes of +self+ with bytes from a given string; returns +self+. # - #sub!: Replaces the first substring that matches a given pattern with a given replacement string; # returns +self+ if any changes, +nil+ otherwise. # - #gsub!: Replaces each substring that matches a given pattern with a given replacement string; diff --git a/symbol.c b/symbol.c index 0bd60aec342854..e4f18197c92614 100644 --- a/symbol.c +++ b/symbol.c @@ -173,8 +173,21 @@ rb_id_attrset(ID id) } } - /* make new symbol and ID */ - if (!(str = lookup_id_str(id))) { + bool error = false; + GLOBAL_SYMBOLS_LOCKING(symbols) { + /* make new symbol and ID */ + if ((str = lookup_id_str(id))) { + str = rb_str_dup(str); + rb_str_cat(str, "=", 1); + sym = lookup_str_sym(str); + id = sym ? rb_sym2id(sym) : intern_str(str, 1); + } + else { + error = true; + } + } + + if (error) { RBIMPL_ATTR_NONSTRING_ARRAY() static const char id_types[][8] = { "local", "instance", @@ -188,10 +201,7 @@ rb_id_attrset(ID id) rb_name_error(id, "cannot make anonymous %.*s ID %"PRIxVALUE" attrset", (int)sizeof(id_types[0]), id_types[scope], (VALUE)id); } - str = rb_str_dup(str); - rb_str_cat(str, "=", 1); - sym = lookup_str_sym(str); - id = sym ? rb_sym2id(sym) : intern_str(str, 1); + return id; } @@ -765,10 +775,20 @@ rb_intern3(const char *name, long len, rb_encoding *enc) struct RString fake_str; VALUE str = rb_setup_fake_str(&fake_str, name, len, enc); OBJ_FREEZE(str); - sym = lookup_str_sym(str); - if (sym) return rb_sym2id(sym); - str = rb_enc_str_new(name, len, enc); /* make true string */ - return intern_str(str, 1); + ID id; + + GLOBAL_SYMBOLS_LOCKING(symbols) { + sym = lookup_str_sym(str); + if (sym) { + id = rb_sym2id(sym); + } + else { + str = rb_enc_str_new(name, len, enc); /* make true string */ + id = intern_str(str, 1); + } + } + + return id; } static ID @@ -801,6 +821,8 @@ next_id_base(void) static ID intern_str(VALUE str, int mutable) { + ASSERT_vm_locking(); + ID id; ID nid; @@ -836,13 +858,18 @@ rb_intern(const char *name) ID rb_intern_str(VALUE str) { - VALUE sym = lookup_str_sym(str); - - if (sym) { - return SYM2ID(sym); + ID id; + GLOBAL_SYMBOLS_LOCKING(symbols) { + VALUE sym = lookup_str_sym(str); + if (sym) { + id = SYM2ID(sym); + } + else { + id = intern_str(str, 0); + } } - return intern_str(str, 0); + return id; } void @@ -926,7 +953,7 @@ rb_sym2id(VALUE sym) } else if (DYNAMIC_SYM_P(sym)) { GLOBAL_SYMBOLS_LOCKING(symbols) { - sym = dsymbol_check(symbols, sym); + RUBY_ASSERT(!rb_objspace_garbage_object_p(sym)); id = RSYMBOL(sym)->id; if (UNLIKELY(!(id & ~ID_SCOPE_MASK))) { @@ -1181,13 +1208,7 @@ rb_check_symbol(volatile VALUE *namep) return name; } else if (DYNAMIC_SYM_P(name)) { - if (!SYMBOL_PINNED_P(name)) { - GLOBAL_SYMBOLS_LOCKING(symbols) { - name = dsymbol_check(symbols, name); - } - - *namep = name; - } + RUBY_ASSERT(!rb_objspace_garbage_object_p(name)); return name; } else if (!RB_TYPE_P(name, T_STRING)) { diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index d43095bc4ca580..c3f9c91c7dedad 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -7,6 +7,11 @@ end class TestIO_Console < Test::Unit::TestCase + HOST_OS = RbConfig::CONFIG['host_os'] + private def host_os?(os) + HOST_OS =~ os + end + begin PATHS = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`} rescue Encoding::CompatibilityError @@ -26,7 +31,7 @@ class TestIO_Console < Test::Unit::TestCase # But it does not occur in `make test-all > /dev/null`, so # there should be an additional factor, I guess. def set_winsize_setup - @old_ttou = trap(:TTOU, 'IGNORE') if RUBY_PLATFORM =~ /freebsd/i + @old_ttou = trap(:TTOU, 'IGNORE') if host_os?(/freebsd/) end def set_winsize_teardown @@ -368,10 +373,10 @@ def assert_ctrl(expect, cc, r, w) w.flush result = EnvUtil.timeout(3) {r.gets} if result - case cc - when 0..31 + case cc.chr + when "\C-A".."\C-_" cc = "^" + (cc.ord | 0x40).chr - when 127 + when "\C-?" cc = "^?" end result.sub!(cc, "") @@ -387,7 +392,7 @@ def test_intr # TestIO_Console#test_intr [/usr/home/chkbuild/chkbuild/tmp/build/20220304T163001Z/ruby/test/io/console/test_io_console.rb:387]: # <"25"> expected but was # <"-e:12:in `p': \e[1mexecution expired (\e[1;4mTimeout::Error\e[m\e[1m)\e[m">. - omit if /freebsd/ =~ RUBY_PLATFORM + omit if host_os?(/freebsd/) run_pty("#{<<~"begin;"}\n#{<<~'end;'}") do |r, w, _| begin; @@ -413,7 +418,7 @@ def test_intr if cc = ctrl["intr"] assert_ctrl("#{cc.ord}", cc, r, w) assert_ctrl("#{cc.ord}", cc, r, w) - assert_ctrl("Interrupt", cc, r, w) unless /linux/ =~ RUBY_PLATFORM + assert_ctrl("Interrupt", cc, r, w) unless host_os?(/linux/) end if cc = ctrl["dsusp"] assert_ctrl("#{cc.ord}", cc, r, w) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 61c26b5dd5f504..febac061564dfd 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1764,33 +1764,28 @@ def test_get_ephemeral_key end end - if !aws_lc? # AWS-LC does not support DHE ciphersuites. - # DHE - # TODO: SSL_CTX_set1_groups() is required for testing this with TLS 1.3 - ctx_proc2 = proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" - ctx.tmp_dh = Fixtures.pkey("dh-1") - } - start_server(ctx_proc: ctx_proc2) do |port| + # DHE + # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 + # LibreSSL does not support named FFDHE groups currently + # AWS-LC does not support DHE ciphersuites + if openssl?(3, 0, 0) + start_server do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" + ctx.groups = "ffdhe3072" server_connect(port, ctx) { |ssl| assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + assert_equal 3072, ssl.tmp_key.p.num_bits + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end # ECDHE ctx_proc3 = proc { |ctx| - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - server_connect(port, ctx) { |ssl| + server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -2079,17 +2074,17 @@ def test_tmp_dh end end - def test_ecdh_curves_tls12 + def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" + ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs @@ -2099,29 +2094,36 @@ def test_ecdh_curves_tls12 # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-521:P-384" + ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } + + # Test 4: #ecdh_curves= alias + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" + server_connect(port, ctx) { |ssl| + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + } end end - def test_ecdh_curves_tls13 + def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" # disable P-521 + ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index d9130c3116c813..58c9cd0970e139 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -118,6 +118,7 @@ def test3 = baz(4, 1) end def test_invokebuiltin + omit 'Test fails at the moment due to not handling optional parameters' assert_compiles '["."]', %q{ def test = Dir.glob(".") test @@ -692,6 +693,39 @@ def fib(n) }, call_threshold: 5, num_profiles: 3 end + def test_spilled_basic_block_args + assert_compiles '55', %q{ + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + } + end + + def test_spilled_method_args + assert_runs '55', %q{ + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + + test + } + end + def test_opt_aref_with assert_compiles ':ok', %q{ def aref_with(hash) = hash["key"] @@ -781,6 +815,21 @@ def entry = jit_frame1 # 3 }, call_threshold: 2 end + def test_bop_invalidation + omit 'Invalidation on BOP redefinition is not implemented yet' + assert_compiles '', %q{ + def test + eval(<<~RUBY) + class Integer + def +(_) = 100 + end + RUBY + 1 + 2 + end + test + } + end + def test_defined_yield assert_compiles "nil", "defined?(yield)" assert_compiles '[nil, nil, "yield"]', %q{ @@ -830,6 +879,29 @@ def test = Foo }, call_threshold: 2 end + def test_branchnil + assert_compiles '[2, nil]', %q{ + def test(x) + x&.succ + end + [test(1), test(nil)] + }, call_threshold: 1, insns: [:branchnil] + end + + def test_nil_nil + assert_compiles 'true', %q{ + def test = nil.nil? + test + }, insns: [:opt_nil_p] + end + + def test_non_nil_nil + assert_compiles 'false', %q{ + def test = 1.nil? + test + }, insns: [:opt_nil_p] + end + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and # b) being reliably ordered after all the other instructions. def test_instruction_order @@ -848,10 +920,17 @@ def test_instruction_order # Assert that every method call in `test_script` can be compiled by ZJIT # at a given call_threshold def assert_compiles(expected, test_script, insns: [], **opts) + assert_runs(expected, test_script, insns:, assert_compiles: true, **opts) + end + + # Assert that `test_script` runs successfully with ZJIT enabled. + # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)` + # allows ZJIT to skip compiling methods. + def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts) pipe_fd = 3 script = <<~RUBY - ret_val = (_test_proc = -> { RubyVM::ZJIT.assert_compiles; #{test_script.lstrip} }).call + ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call result = { ret_val:, #{ unless insns.empty? diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index 84c35ff07485bb..4851de09d075b2 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ca6726be0eca74687047fed7dcbc2d509571f3962e190c343ac1eb40e482b3" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2390cfc87b7513656656faad6567291e581542d3ec41dd0a2bf381896e0880" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 876dbfb23d8df7..7cb12fa8a6c16c 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.115" +rb-sys = "0.9.116" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 767c24a1bf300a..9740b435e7bb8f 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ca6726be0eca74687047fed7dcbc2d509571f3962e190c343ac1eb40e482b3" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2390cfc87b7513656656faad6567291e581542d3ec41dd0a2bf381896e0880" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index 4ed446c4efacd7..b389cff5425e5e 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.115" +rb-sys = "0.9.116" diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index a8fd9dc6f8daf5..28113cd299d4f0 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -11,3 +11,8 @@ gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" +gem "ruby-graphviz" +gem "psych" +gem "etc", platforms: [:ruby, :windows] +gem "open3" +gem "shellwords" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 028de749f47f7b..cbc682cd5c45e0 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -4,10 +4,21 @@ GEM base64 (0.2.0) builder (3.3.0) compact_index (0.15.0) + date (3.4.1) + date (3.4.1-java) + etc (1.4.5) fiddle (1.1.6) + jar-dependencies (0.5.5) logger (1.7.0) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) + open3 (0.2.1) + psych (5.2.3) + date + stringio + psych (5.2.3-java) + date + jar-dependencies (>= 0.1.7) rack (3.1.15) rack-protection (4.1.1) base64 (>= 0.1.0) @@ -22,9 +33,13 @@ GEM rake-compiler-dock (1.9.1) rb_sys (0.9.111) rake-compiler-dock (= 1.9.1) + rexml (3.4.1) + ruby-graphviz (1.2.5) + rexml ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) + shellwords (0.2.2) sinatra (4.1.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -32,6 +47,7 @@ GEM rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + stringio (3.1.6) tilt (2.6.0) PLATFORMS @@ -45,21 +61,33 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) compact_index (~> 0.15.0) + etc fiddle + open3 + psych rack (~> 3.1) rack-test (~> 2.1) rake (~> 13.1) rb_sys + ruby-graphviz rubygems-generate_index (~> 1.1) + shellwords sinatra (~> 4.1) CHECKSUMS base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f + date (3.4.1-java) sha256=74740d914c65a922a15657c25ff0e203c16f1d0f7aa910a9ebed712afe9819c4 + etc (1.4.5) sha256=0d854e7b97a40390b048ba51230c30886931931b9dba955e85985d7d3bccf26c fiddle (1.1.6) sha256=79e8d909e602d979434cf9fccfa6e729cb16432bb00e39c7596abe6bee1249ab + jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 mustermann (3.0.3) sha256=d1f8e9ba2ddaed47150ddf81f6a7ea046826b64c672fbc92d83bce6b70657e88 + open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952 + psych (5.2.3) sha256=84a54bb952d14604fea22d99938348814678782f58b12648fcdfa4d2fce859ee + psych (5.2.3-java) sha256=3e5425b9e8a2f41cc2707d5ef14fdc1ae908abbafb12fe45727bd63900056585 rack (3.1.15) sha256=d12b3e9960d18a26ded961250f2c0e3b375b49ff40dbe6786e9c3b160cbffca4 rack-protection (4.1.1) sha256=51a254a5d574a7f0ca4f0672025ce2a5ef7c8c3bd09c431349d683e825d7d16a rack-session (2.1.0) sha256=437c3916535b58ef71c816ce4a2dee0a01c8a52ae6077dc2b6cd19085760a290 @@ -67,9 +95,13 @@ CHECKSUMS rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 + rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca + ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c + shellwords (0.2.2) sha256=b8695a791de2f71472de5abdc3f4332f6535a4177f55d8f99e7e44266cd32f94 sinatra (4.1.1) sha256=4e997b859aa1b5d2e624f85d5b0fd0f0b3abc0da44daa6cbdf10f7c0da9f4d00 + stringio (3.1.6) sha256=292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5 tilt (2.6.0) sha256=263d748466e0d83e510aa1a2e2281eff547937f0ef06be33d3632721e255f76b BUNDLED WITH diff --git a/tool/downloader.rb b/tool/downloader.rb index a1520eb6a95c56..14f18747f3849e 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -1,41 +1,12 @@ # Used by configure and make to download or update mirrored Ruby and GCC -# files. This will use HTTPS if possible, falling back to HTTP. +# files. # -*- frozen-string-literal: true -*- require 'fileutils' require 'open-uri' require 'pathname' -begin - require 'net/https' -rescue LoadError - https = 'http' -else - https = 'https' - - # open-uri of ruby 2.2.0 accepts an array of PEMs as ssl_ca_cert, but old - # versions do not. so, patching OpenSSL::X509::Store#add_file instead. - class OpenSSL::X509::Store - alias orig_add_file add_file - def add_file(pems) - Array(pems).each do |pem| - if File.directory?(pem) - add_path pem - else - orig_add_file pem - end - end - end - end - # since open-uri internally checks ssl_ca_cert using File.directory?, - # allow to accept an array. - class < e - m1, m2 = e.message.split("\n", 2) - STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" - super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest, **options) - end + Mirrors.each_with_index do |url, i| + super("#{url}/#{name}", name, *rest, **options) + rescue => e + raise if i + 1 == Mirrors.size # no more URLs + m1, m2 = e.message.split("\n", 2) + STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" else - super("https://repo.or.cz/official-gcc.git/blob_plain/HEAD:/#{name}", name, *rest, **options) + return end end end @@ -222,11 +184,6 @@ def self.download(url, name, dir = nil, since = true, if link_cache(cache, file, name, verbose: verbose) return file.to_path end - if !https? and URI::HTTPS === url - warn "*** using http instead of https ***" - url.scheme = 'http' - url = URI(url.to_s) - end if verbose $stdout.print "downloading #{name} ... " $stdout.flush @@ -234,13 +191,7 @@ def self.download(url, name, dir = nil, since = true, mtime = nil options = options.merge(http_options(file, since.nil? ? true : since)) begin - data = with_retry(10) do - data = url.read(options) - if mtime = data.meta["last-modified"] - mtime = Time.httpdate(mtime) - end - data - end + data = with_retry(10) {url.read(options)} rescue OpenURI::HTTPError => http_error case http_error.message when /^304 / # 304 Not Modified @@ -268,6 +219,10 @@ def self.download(url, name, dir = nil, since = true, return file.to_path end raise + else + if mtime = data.meta["last-modified"] + mtime = Time.httpdate(mtime) + end end dest = (cache_save && cache && !cache.exist? ? cache : file) dest.parent.mkpath @@ -386,8 +341,6 @@ def self.with_retry(max_times, &block) private_class_method :with_retry end -Downloader.https = https.freeze - if $0 == __FILE__ since = true options = {} diff --git a/tool/lib/bundle_env.rb b/tool/lib/bundle_env.rb new file mode 100644 index 00000000000000..9ad5ea220b466b --- /dev/null +++ b/tool/lib/bundle_env.rb @@ -0,0 +1,4 @@ +ENV["GEM_HOME"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_APP_CONFIG"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_PATH__SYSTEM"] = "true" +ENV["BUNDLE_WITHOUT"] = "lint doc" diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index ca0b15dd194134..7a231772b5cbb4 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -136,7 +136,7 @@ def sync_default_gems(gem) cp_r("#{upstream}/bundler/spec", "spec/bundler") rm_rf("spec/bundler/bin") - ["parallel_rspec", "rspec"].each do |binstub| + ["bundle", "parallel_rspec", "rspec"].each do |binstub| content = File.read("#{upstream}/bundler/bin/#{binstub}").gsub("../spec", "../bundler") File.write("spec/bin/#{binstub}", content) chmod("+x", "spec/bin/#{binstub}") @@ -276,7 +276,7 @@ def sync_default_gems(gem) rm_rf(%w[lib/erb* test/erb libexec/erb]) cp_r("#{upstream}/lib/erb.rb", "lib") cp_r("#{upstream}/test/erb", "test") - cp_r("#{upstream}/erb.gemspec", "lib") + cp_r("#{upstream}/erb.gemspec", "lib/erb") cp_r("#{upstream}/libexec/erb", "libexec") when "pathname" rm_rf(%w[ext/pathname test/pathname]) diff --git a/transcode.c b/transcode.c index bff9268e1cda9a..8e36fa13eb88cd 100644 --- a/transcode.c +++ b/transcode.c @@ -20,6 +20,7 @@ #include "internal/string.h" #include "internal/transcode.h" #include "ruby/encoding.h" +#include "vm_sync.h" #include "transcode_data.h" #include "id.h" @@ -209,19 +210,21 @@ make_transcoder_entry(const char *sname, const char *dname) st_data_t val; st_table *table2; - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - val = (st_data_t)st_init_strcasetable(); - st_add_direct(transcoder_table, (st_data_t)sname, val); - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - transcoder_entry_t *entry = ALLOC(transcoder_entry_t); - entry->sname = sname; - entry->dname = dname; - entry->lib = NULL; - entry->transcoder = NULL; - val = (st_data_t)entry; - st_add_direct(table2, (st_data_t)dname, val); + RB_VM_LOCKING() { + if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { + val = (st_data_t)st_init_strcasetable(); + st_add_direct(transcoder_table, (st_data_t)sname, val); + } + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + transcoder_entry_t *entry = ALLOC(transcoder_entry_t); + entry->sname = sname; + entry->dname = dname; + entry->lib = NULL; + entry->transcoder = NULL; + val = (st_data_t)entry; + st_add_direct(table2, (st_data_t)dname, val); + } } return (transcoder_entry_t *)val; } @@ -229,15 +232,15 @@ make_transcoder_entry(const char *sname, const char *dname) static transcoder_entry_t * get_transcoder_entry(const char *sname, const char *dname) { - st_data_t val; + st_data_t val = 0; st_table *table2; - - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - return NULL; - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - return NULL; + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)sname, &val)) { + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + val = 0; + } + } } return (transcoder_entry_t *)val; } @@ -250,13 +253,14 @@ rb_register_transcoder(const rb_transcoder *tr) transcoder_entry_t *entry; - entry = make_transcoder_entry(sname, dname); - if (entry->transcoder) { - rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", - sname, dname); + RB_VM_LOCKING() { + entry = make_transcoder_entry(sname, dname); + if (entry->transcoder) { + rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", + sname, dname); + } + entry->transcoder = tr; } - - entry->transcoder = tr; } static void @@ -323,8 +327,9 @@ transcode_search_path(const char *sname, const char *dname, search_path_queue_t *q; st_data_t val; st_table *table2; - int found; int pathlen = -1; + bool found = false; + bool lookup_res; if (encoding_equal(sname, dname)) return -1; @@ -338,34 +343,36 @@ transcode_search_path(const char *sname, const char *dname, bfs.visited = st_init_strcasetable(); st_add_direct(bfs.visited, (st_data_t)sname, (st_data_t)NULL); - while (bfs.queue) { - q = bfs.queue; - bfs.queue = q->next; - if (!bfs.queue) - bfs.queue_last_ptr = &bfs.queue; + RB_VM_LOCKING() { + while (bfs.queue) { + q = bfs.queue; + bfs.queue = q->next; + if (!bfs.queue) { + bfs.queue_last_ptr = &bfs.queue; + } - if (!st_lookup(transcoder_table, (st_data_t)q->enc, &val)) { - xfree(q); - continue; - } - table2 = (st_table *)val; + lookup_res = st_lookup(transcoder_table, (st_data_t)q->enc, &val); + if (!lookup_res) { + xfree(q); + continue; + } + table2 = (st_table *)val; - if (st_lookup(table2, (st_data_t)dname, &val)) { - st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); - xfree(q); - found = 1; - goto cleanup; - } + if (st_lookup(table2, (st_data_t)dname, &val)) { + st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); + xfree(q); + found = true; + break; + } - bfs.base_enc = q->enc; - st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); - bfs.base_enc = NULL; + bfs.base_enc = q->enc; + st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); - xfree(q); + bfs.base_enc = NULL; + xfree(q); + } } - found = 0; - cleanup: while (bfs.queue) { q = bfs.queue; bfs.queue = q->next; @@ -404,6 +411,8 @@ int rb_require_internal_silent(VALUE fname); static const rb_transcoder * load_transcoder_entry(transcoder_entry_t *entry) { + // changes result of entry->transcoder depending on if it's required or not, so needs lock + ASSERT_vm_locking(); if (entry->transcoder) return entry->transcoder; @@ -972,6 +981,7 @@ rb_econv_open_by_transcoder_entries(int n, transcoder_entry_t **entries) { rb_econv_t *ec; int i, ret; + ASSERT_vm_locking(); for (i = 0; i < n; i++) { const rb_transcoder *tr; @@ -1016,6 +1026,7 @@ rb_econv_open0(const char *sname, const char *dname, int ecflags) transcoder_entry_t **entries = NULL; int num_trans; rb_econv_t *ec; + ASSERT_vm_locking(); /* Just check if sname and dname are defined */ /* (This check is needed?) */ @@ -1106,19 +1117,23 @@ rb_econv_open(const char *sname, const char *dname, int ecflags) if (num_decorators == -1) return NULL; - ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); - if (!ec) - return NULL; - - for (i = 0; i < num_decorators; i++) - if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { - rb_econv_close(ec); - return NULL; + RB_VM_LOCKING() { + ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); + if (ec) { + for (i = 0; i < num_decorators; i++) { + if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { + rb_econv_close(ec); + ec = NULL; + break; + } + } } + } - ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; - - return ec; + if (ec) { + ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; + } + return ec; // can be NULL } static int @@ -1815,26 +1830,29 @@ rb_econv_asciicompat_encoding(const char *ascii_incompat_name) { st_data_t v; st_table *table2; - struct asciicompat_encoding_t data; + struct asciicompat_encoding_t data = {0}; + + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) { + table2 = (st_table *)v; + /* + * Assumption: + * There is at most one transcoder for + * converting from ASCII incompatible encoding. + * + * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. + */ + if (table2->num_entries == 1) { + data.ascii_incompat_name = ascii_incompat_name; + data.ascii_compat_name = NULL; + st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); + } - if (!st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) - return NULL; - table2 = (st_table *)v; + } - /* - * Assumption: - * There is at most one transcoder for - * converting from ASCII incompatible encoding. - * - * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. - */ - if (table2->num_entries != 1) - return NULL; + } - data.ascii_incompat_name = ascii_incompat_name; - data.ascii_compat_name = NULL; - st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); - return data.ascii_compat_name; + return data.ascii_compat_name; // can be NULL } /* @@ -1937,19 +1955,20 @@ static int rb_econv_add_converter(rb_econv_t *ec, const char *sname, const char *dname, int n) { transcoder_entry_t *entry; - const rb_transcoder *tr; + const rb_transcoder *tr = NULL; if (ec->started != 0) return -1; - entry = get_transcoder_entry(sname, dname); - if (!entry) - return -1; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + if (entry) { + tr = load_transcoder_entry(entry); + } - tr = load_transcoder_entry(entry); - if (!tr) return -1; + } - return rb_econv_add_transcoder_at(ec, tr, n); + return tr ? rb_econv_add_transcoder_at(ec, tr, n) : -1; } static int @@ -2662,24 +2681,25 @@ rb_econv_open_opts(const char *source_encoding, const char *destination_encoding replacement = rb_hash_aref(opthash, sym_replace); } - ec = rb_econv_open(source_encoding, destination_encoding, ecflags); - if (!ec) - return ec; - - if (!NIL_P(replacement)) { - int ret; - rb_encoding *enc = rb_enc_get(replacement); - - ret = rb_econv_set_replacement(ec, - (const unsigned char *)RSTRING_PTR(replacement), - RSTRING_LEN(replacement), - rb_enc_name(enc)); - if (ret == -1) { - rb_econv_close(ec); - return NULL; + RB_VM_LOCKING() { + ec = rb_econv_open(source_encoding, destination_encoding, ecflags); + if (ec) { + if (!NIL_P(replacement)) { + int ret; + rb_encoding *enc = rb_enc_get(replacement); + + ret = rb_econv_set_replacement(ec, + (const unsigned char *)RSTRING_PTR(replacement), + RSTRING_LEN(replacement), + rb_enc_name(enc)); + if (ret == -1) { + rb_econv_close(ec); + ec = NULL; + } + } } } - return ec; + return ec; // can be NULL } static int @@ -2979,9 +2999,11 @@ static rb_encoding * make_encoding(const char *name) { rb_encoding *enc; - enc = rb_enc_find(name); - if (!enc) - enc = make_dummy_encoding(name); + RB_VM_LOCKING() { + enc = rb_enc_find(name); + if (!enc) + enc = make_dummy_encoding(name); + } return enc; } @@ -3014,17 +3036,19 @@ econv_s_asciicompat_encoding(VALUE klass, VALUE arg) { const char *arg_name, *result_name; rb_encoding *arg_enc, *result_enc; + VALUE enc = Qnil; enc_arg(&arg, &arg_name, &arg_enc); - result_name = rb_econv_asciicompat_encoding(arg_name); - - if (result_name == NULL) - return Qnil; - - result_enc = make_encoding(result_name); + RB_VM_LOCKING() { + result_name = rb_econv_asciicompat_encoding(arg_name); - return rb_enc_from_encoding(result_enc); + if (result_name) { + result_enc = make_encoding(result_name); + enc = rb_enc_from_encoding(result_enc); + } + } + return enc; } static void @@ -3105,8 +3129,12 @@ decorate_convpath(VALUE convpath, int ecflags) if (RB_TYPE_P(pair, T_ARRAY)) { const char *sname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 0))); const char *dname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 1))); - transcoder_entry_t *entry = get_transcoder_entry(sname, dname); - const rb_transcoder *tr = load_transcoder_entry(entry); + transcoder_entry_t *entry; + const rb_transcoder *tr; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + tr = load_transcoder_entry(entry); + } if (!tr) return -1; if (!DECORATOR_P(tr->src_encoding, tr->dst_encoding) && diff --git a/variable.c b/variable.c index 3e17efd72e7fbe..66e17c43ad38ca 100644 --- a/variable.c +++ b/variable.c @@ -1844,6 +1844,9 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { if (rb_shape_too_complex_p(current_shape_id)) { if (concurrent) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. fields_obj = rb_imemo_fields_clone(fields_obj); } } @@ -4680,9 +4683,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id); attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id); - if (concurrent || next_capacity != current_capacity) { - RUBY_ASSERT(concurrent || next_capacity > current_capacity); - + if (next_capacity > current_capacity) { // We allocate a new fields_obj even when concurrency isn't a concern // so that we're embedded as long as possible. fields_obj = imemo_fields_copy_capa(rb_singleton_class(klass), fields_obj, next_capacity); @@ -4693,7 +4694,18 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } VALUE *fields = rb_imemo_fields_ptr(fields_obj); - RB_OBJ_WRITE(fields_obj, &fields[index], val); + + if (concurrent && original_fields_obj == fields_obj) { + // In the concurrent case, if we're mutating the existing + // fields_obj, we must use an atomic write, because if we're + // adding a new field, the shape_id must be written after the field + // and if we're updating an existing field, we at least need a relaxed + // write to avoid reaping. + RB_OBJ_ATOMIC_WRITE(fields_obj, &fields[index], val); + } + else { + RB_OBJ_WRITE(fields_obj, &fields[index], val); + } if (!existing) { RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); @@ -4705,9 +4717,12 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc too_complex: { if (concurrent && fields_obj == original_fields_obj) { - // If we're in the multi-ractor mode, we can't directly insert in the table. + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. fields_obj = rb_imemo_fields_clone(fields_obj); } + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); existing = st_insert(table, (st_data_t)id, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); @@ -4747,14 +4762,6 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) return !existing; } -static int -tbl_copy_i(ID key, VALUE val, st_data_t dest) -{ - rb_class_ivar_set((VALUE)dest, key, val); - - return ST_CONTINUE; -} - void rb_fields_tbl_copy(VALUE dst, VALUE src) { @@ -4762,7 +4769,11 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE)); RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(dst), SHAPE_ROOT)); - rb_ivar_foreach(src, tbl_copy_i, dst); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); + if (fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); + RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + } } static rb_const_entry_t * diff --git a/yjit.h b/yjit.h index 9360e7fe3c8c9e..468965500218e0 100644 --- a/yjit.h +++ b/yjit.h @@ -37,7 +37,7 @@ void rb_yjit_collect_binding_alloc(void); void rb_yjit_collect_binding_set(void); void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); void rb_yjit_init(bool yjit_enabled); -void rb_yjit_free_at_exit(); +void rb_yjit_free_at_exit(void); void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_yjit_constant_state_changed(ID id); void rb_yjit_iseq_mark(void *payload); diff --git a/zjit.c b/zjit.c index 9218395582416c..560e115f3c4851 100644 --- a/zjit.c +++ b/zjit.c @@ -32,6 +32,8 @@ #include +RUBY_EXTERN VALUE rb_cSet; // defined in set.c and it's not exposed yet + uint32_t rb_zjit_get_page_size(void) { diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index cf328fc68cdfc8..1e4c711e05fcd8 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -186,7 +186,9 @@ fn main() { .allowlist_var("rb_cThread") .allowlist_var("rb_cArray") .allowlist_var("rb_cHash") + .allowlist_var("rb_cSet") .allowlist_var("rb_cClass") + .allowlist_var("rb_cRegexp") .allowlist_var("rb_cISeq") // From include/ruby/internal/fl_type.h @@ -227,6 +229,7 @@ fn main() { .allowlist_function("rb_sym2id") .allowlist_function("rb_str_intern") .allowlist_function("rb_id2str") + .allowlist_function("rb_sym2str") // From internal/numeric.h .allowlist_function("rb_fix_aref") diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index 1e1b125eaaaf37..ef477821aa38a5 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -936,7 +936,7 @@ pub fn stur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rn.num_bits == 32 || rn.num_bits == 64); - assert!(mem_disp_fits_bits(rn.disp), "Expected displacement to be 9 bits or less"); + assert!(mem_disp_fits_bits(rn.disp), "Expected displacement {} to be 9 bits or less", rn.disp); LoadStore::stur(rt.reg_no, rn.base_reg_no, rn.disp as i16, rn.num_bits).into() }, diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index dd1eb52d34778e..5cac6740e366e3 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = X0_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(X0_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(XZR_REG); // These constants define the way we work with Arm64's stack pointer. The stack // pointer always needs to be aligned to a 16-byte boundary. @@ -891,7 +892,6 @@ impl Assembler let mut pos_markers: Vec<(usize, CodePtr)> = vec![]; // For each instruction - //let start_write_pos = cb.get_write_pos(); let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { //let src_ptr = cb.get_write_ptr(); @@ -1255,9 +1255,6 @@ impl Assembler csel(cb, out.into(), truthy.into(), falsy.into(), Condition::GE); } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code - Insn::PadInvalPatch => { - unimplemented!("we haven't needed padding in ZJIT yet"); - } }; // On failure, jump to the next page and retry the current insn @@ -1296,7 +1293,7 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { let asm = self.arm64_split(); - let mut asm = asm.alloc_regs(regs); + let mut asm = asm.alloc_regs(regs)?; asm.compile_side_exits()?; // Create label instances in the code block diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f46b35ded55fb1..f914870c84fed9 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -3,12 +3,11 @@ use std::fmt; use std::mem::take; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32}; +use crate::options::{debug, get_option}; use crate::{cruby::VALUE}; use crate::backend::current::*; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; -#[cfg(feature = "disasm")] -use crate::options::*; pub const EC: Opnd = _EC; pub const CFP: Opnd = _CFP; @@ -16,6 +15,7 @@ pub const SP: Opnd = _SP; pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS; pub const C_RET_OPND: Opnd = _C_RET_OPND; +pub const NATIVE_STACK_PTR: Opnd = _NATIVE_STACK_PTR; pub use crate::backend::current::{Reg, C_RET_REG}; // Memory operand base @@ -77,6 +77,7 @@ impl fmt::Debug for Opnd { match self { Self::None => write!(fmt, "None"), Value(val) => write!(fmt, "Value({val:?})"), + VReg { idx, num_bits } if *num_bits == 64 => write!(fmt, "VReg({idx})"), VReg { idx, num_bits } => write!(fmt, "VReg{num_bits}({idx})"), Imm(signed) => write!(fmt, "{signed:x}_i64"), UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"), @@ -276,7 +277,7 @@ pub enum Target /// Pointer to a piece of ZJIT-generated code CodePtr(CodePtr), // Side exit with a counter - SideExit { pc: *const VALUE, stack: Vec, locals: Vec }, + SideExit { pc: *const VALUE, stack: Vec, locals: Vec, c_stack_bytes: usize }, /// A label within the generated code Label(Label), } @@ -483,10 +484,6 @@ pub enum Insn { // binary OR operation. Or { left: Opnd, right: Opnd, out: Opnd }, - /// Pad nop instructions to accommodate Op::Jmp in case the block or the insn - /// is invalidated. - PadInvalPatch, - // Mark a position in the generated code PosMarker(PosMarkerFn), @@ -606,7 +603,6 @@ impl Insn { Insn::Mov { .. } => "Mov", Insn::Not { .. } => "Not", Insn::Or { .. } => "Or", - Insn::PadInvalPatch => "PadEntryExit", Insn::PosMarker(_) => "PosMarker", Insn::RShift { .. } => "RShift", Insn::Store { .. } => "Store", @@ -800,7 +796,6 @@ impl<'a> Iterator for InsnOpndIterator<'a> { Insn::CPushAll | Insn::FrameSetup | Insn::FrameTeardown | - Insn::PadInvalPatch | Insn::PosMarker(_) => None, Insn::CPopInto(opnd) | @@ -955,7 +950,6 @@ impl<'a> InsnOpndMutIterator<'a> { Insn::CPushAll | Insn::FrameSetup | Insn::FrameTeardown | - Insn::PadInvalPatch | Insn::PosMarker(_) => None, Insn::CPopInto(opnd) | @@ -1517,13 +1511,18 @@ impl Assembler /// Sets the out field on the various instructions that require allocated /// registers because their output is used as the operand on a subsequent /// instruction. This is our implementation of the linear scan algorithm. - pub(super) fn alloc_regs(mut self, regs: Vec) -> Assembler { + pub(super) fn alloc_regs(mut self, regs: Vec) -> Option { // Dump live registers for register spill debugging. fn dump_live_regs(insns: Vec, live_ranges: Vec, num_regs: usize, spill_index: usize) { // Convert live_ranges to live_regs: the number of live registers at each index let mut live_regs: Vec = vec![]; for insn_idx in 0..insns.len() { - let live_count = live_ranges.iter().filter(|range| range.start() <= insn_idx && insn_idx <= range.end()).count(); + let live_count = live_ranges.iter().filter(|range| + match (range.start, range.end) { + (Some(start), Some(end)) => start <= insn_idx && insn_idx <= end, + _ => false, + } + ).count(); live_regs.push(live_count); } @@ -1559,7 +1558,12 @@ impl Assembler // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { - let new_reg = pool.alloc_reg(vreg_idx).unwrap(); // TODO: support spill + let new_reg = if let Some(new_reg) = pool.alloc_reg(vreg_idx) { + new_reg + } else { + debug!("spilling VReg is not implemented yet, can't evacuate C_RET_REG on CCall"); + return None; + }; asm.mov(Opnd::Reg(new_reg), C_RET_OPND); pool.dealloc_reg(&C_RET_REG); reg_mapping[vreg_idx] = Some(new_reg); @@ -1652,13 +1656,16 @@ impl Assembler _ => match pool.alloc_reg(vreg_idx.unwrap()) { Some(reg) => Some(reg), None => { - let mut insns = asm.insns; - insns.push(insn); - while let Some((_, insn)) = iterator.next() { + if get_option!(debug) { + let mut insns = asm.insns; insns.push(insn); + while let Some((_, insn)) = iterator.next() { + insns.push(insn); + } + dump_live_regs(insns, live_ranges, regs.len(), index); } - dump_live_regs(insns, live_ranges, regs.len(), index); - unreachable!("Register spill not supported"); + debug!("Register spill not supported"); + return None; } } }; @@ -1729,7 +1736,7 @@ impl Assembler } assert!(pool.is_empty(), "Expected all registers to be returned to the pool"); - asm + Some(asm) } /// Compile the instructions down to machine code. @@ -1773,7 +1780,7 @@ impl Assembler for (idx, target) in targets { // Compile a side exit. Note that this is past the split pass and alloc_regs(), // so you can't use a VReg or an instruction that needs to be split. - if let Target::SideExit { pc, stack, locals } = target { + if let Target::SideExit { pc, stack, locals, c_stack_bytes } = target { let side_exit_label = self.new_label("side_exit".into()); self.write_label(side_exit_label.clone()); @@ -1809,6 +1816,11 @@ impl Assembler let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP); self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG)); + if c_stack_bytes > 0 { + asm_comment!(self, "restore C stack pointer"); + self.add_into(NATIVE_STACK_PTR, c_stack_bytes.into()); + } + asm_comment!(self, "exit to the interpreter"); self.frame_teardown(); self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64())); @@ -1841,6 +1853,11 @@ impl Assembler { out } + pub fn add_into(&mut self, left: Opnd, right: Opnd) -> Opnd { + self.push_insn(Insn::Add { left, right, out: left }); + left + } + #[must_use] pub fn and(&mut self, left: Opnd, right: Opnd) -> Opnd { let out = self.new_vreg(Opnd::match_num_bits(&[left, right])); @@ -2147,10 +2164,6 @@ impl Assembler { out } - pub fn pad_inval_patch(&mut self) { - self.push_insn(Insn::PadInvalPatch); - } - //pub fn pos_marker(&mut self, marker_fn: F) pub fn pos_marker(&mut self, marker_fn: impl Fn(CodePtr, &CodeBlock) + 'static) { self.push_insn(Insn::PosMarker(Box::new(marker_fn))); diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index d83fc184f91a6a..4dd9877ea7125a 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = RAX_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(RAX_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(RSP_REG); impl CodeBlock { // The number of bytes that are generated by jmp_ptr @@ -443,7 +444,6 @@ impl Assembler let mut pos_markers: Vec<(usize, CodePtr)> = vec![]; // For each instruction - //let start_write_pos = cb.get_write_pos(); let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { //let src_ptr = cb.get_write_ptr(); @@ -794,15 +794,6 @@ impl Assembler emit_csel(cb, *truthy, *falsy, *out, cmovge, cmovl); } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code - Insn::PadInvalPatch => { - unimplemented!("we don't need padding yet"); - /* - let code_size = cb.get_write_pos().saturating_sub(std::cmp::max(start_write_pos, cb.page_start_pos())); - if code_size < cb.jmp_ptr_bytes() { - nop(cb, (cb.jmp_ptr_bytes() - code_size) as u32); - } - */ - } }; // On failure, jump to the next page and retry the current insn @@ -835,7 +826,7 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { let asm = self.x86_split(); - let mut asm = asm.alloc_regs(regs); + let mut asm = asm.alloc_regs(regs)?; asm.compile_side_exits()?; // Create label instances in the code block diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f805b8b8d748a2..306ba31aba117b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -3,12 +3,12 @@ use std::rc::Rc; use std::num::NonZeroU32; use crate::backend::current::{Reg, ALLOC_REGS}; +use crate::invariants::track_bop_assumption; use crate::profile::get_or_create_iseq_payload; use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption}; -use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; +use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, SP}; +use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, Invariant, RangeType, SpecialObjectType, SELF_PARAM_IDX}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; use crate::options::get_option; @@ -26,16 +26,20 @@ struct JITState { /// Branches to an ISEQ that need to be compiled later branch_iseqs: Vec<(Rc, IseqPtr)>, + + /// The number of bytes allocated for basic block arguments spilled onto the C stack + c_stack_bytes: usize, } impl JITState { /// Create a new JITState instance - fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize) -> Self { + fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize, c_stack_bytes: usize) -> Self { JITState { iseq, opnds: vec![None; num_insns], labels: vec![None; num_blocks], branch_iseqs: Vec::default(), + c_stack_bytes, } } @@ -59,15 +63,6 @@ impl JITState { } } } - - /// Assume that this ISEQ doesn't escape EP. Return false if it's known to escape EP. - fn assume_no_ep_escape(iseq: IseqPtr) -> bool { - if iseq_escapes_ep(iseq) { - return false; - } - track_no_ep_escape_assumption(iseq); - true - } } /// CRuby API to compile a given ISEQ @@ -78,6 +73,14 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *co return std::ptr::null(); } + // Reject ISEQs with very large temp stacks. + // We cannot encode too large offsets to access locals in arm64. + let stack_max = unsafe { rb_get_iseq_body_stack_max(iseq) }; + if stack_max >= i8::MAX as u32 { + debug!("ISEQ stack too large: {stack_max}"); + return std::ptr::null(); + } + // Take a lock to avoid writing to ISEQ in parallel with Ractors. // with_vm_lock() does nothing if the program doesn't use Ractors. let code_ptr = with_vm_lock(src_loc!(), || { @@ -145,7 +148,7 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt // Set up registers for CFP, EC, SP, and basic block arguments let mut asm = Assembler::new(); gen_entry_prologue(&mut asm, iseq); - gen_method_params(&mut asm, iseq, function.block(BlockId(0))); + gen_entry_params(&mut asm, iseq, function.block(BlockId(0))); // Jump to the first block using a call instruction asm.ccall(function_ptr.raw_ptr(cb) as *const u8, vec![]); @@ -189,7 +192,8 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<(CodePtr, Vec<(Rc Option<(CodePtr, Vec<(Rc, IseqPtr)>)> { - let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks()); + let c_stack_bytes = aligned_stack_bytes(max_num_params(function).saturating_sub(ALLOC_REGS.len())); + let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_bytes); let mut asm = Assembler::new(); // Compile each basic block @@ -205,6 +209,13 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio // Set up the frame at the first block if block_id == BlockId(0) { asm.frame_setup(); + + // Bump the C stack pointer for basic block arguments + if jit.c_stack_bytes > 0 { + asm_comment!(asm, "bump C stack pointer"); + let new_sp = asm.sub(NATIVE_STACK_PTR, jit.c_stack_bytes.into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + } } // Compile all parameters @@ -262,7 +273,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?, Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), args, &function.frame_state(*state))?, Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, args)?, - Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?), + Insn::Return { val } => return Some(gen_return(jit, asm, opnd!(val))?), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumMult { left, right, state } => gen_fixnum_mult(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, @@ -272,19 +283,20 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumLe { left, right } => gen_fixnum_le(asm, opnd!(left), opnd!(right))?, Insn::FixnumGt { left, right } => gen_fixnum_gt(asm, opnd!(left), opnd!(right))?, Insn::FixnumGe { left, right } => gen_fixnum_ge(asm, opnd!(left), opnd!(right))?, + Insn::IsNil { val } => gen_isnil(asm, opnd!(val))?, Insn::Test { val } => gen_test(asm, opnd!(val))?, Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state))?, Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state))?, - Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined() + Insn::PatchPoint(invariant) => return gen_patch_point(asm, invariant), Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(jit, asm, *cfun, args)?, Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), - Insn::SetGlobal { id, val, state: _ } => gen_setglobal(asm, *id, opnd!(val)), + Insn::SetGlobal { id, val, state: _ } => return Some(gen_setglobal(asm, *id, opnd!(val))), Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), &Insn::GetLocal { ep_offset, level } => gen_nested_getlocal(asm, ep_offset, level)?, Insn::SetLocal { val, ep_offset, level } => return gen_nested_setlocal(asm, opnd!(val), *ep_offset, *level), Insn::GetConstantPath { ic, state } => gen_get_constant_path(asm, *ic, &function.frame_state(*state)), Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), - Insn::SideExit { state } => return gen_side_exit(jit, asm, &function.frame_state(*state)), + Insn::SideExit { state, reason: _ } => return gen_side_exit(jit, asm, &function.frame_state(*state)), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?, Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?, @@ -294,7 +306,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio } }; - assert!(insn.has_output(), "Cannot write LIR output of HIR instruction with no output"); + assert!(insn.has_output(), "Cannot write LIR output of HIR instruction with no output: {insn}"); // If the instruction has an output, remember it in jit.opnds jit.opnds[insn_id.0] = Some(out_opnd); @@ -411,6 +423,24 @@ fn gen_invokebuiltin(jit: &mut JITState, asm: &mut Assembler, state: &FrameState Some(val) } +/// Record a patch point that should be invalidated on a given invariant +fn gen_patch_point(asm: &mut Assembler, invariant: &Invariant) -> Option<()> { + let invariant = invariant.clone(); + asm.pos_marker(move |code_ptr, _cb| { + match invariant { + Invariant::BOPRedefined { klass, bop } => { + track_bop_assumption(klass, bop, code_ptr); + } + _ => { + debug!("ZJIT: gen_patch_point: unimplemented invariant {invariant:?}"); + return; + } + } + }); + // TODO: Make sure patch points do not overlap with each other. + Some(()) +} + /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. fn gen_ccall(jit: &mut JITState, asm: &mut Assembler, cfun: *const u8, args: &[InsnId]) -> Option { @@ -451,12 +481,9 @@ fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd { } /// Set global variables -fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) -> Opnd { +fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) { asm_comment!(asm, "call rb_gvar_set"); - asm.ccall( - rb_gvar_set as *const u8, - vec![Opnd::UImm(id.0), val], - ) + asm.ccall(rb_gvar_set as *const u8, vec![Opnd::UImm(id.0), val]); } /// Side-exit into the interpreter @@ -503,7 +530,7 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { } /// Assign method arguments to basic block arguments at JIT entry -fn gen_method_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { +fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { let self_param = gen_param(asm, SELF_PARAM_IDX); asm.mov(self_param, Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF)); @@ -518,7 +545,7 @@ fn gen_method_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { // Assign local variables to the basic block arguments for (idx, ¶m) in params.iter().enumerate() { - let local = gen_getlocal(asm, iseq, idx); + let local = gen_entry_param(asm, iseq, idx); asm.load_into(param, local); } } @@ -530,18 +557,29 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg asm_comment!(asm, "set branch params: {}", branch.args.len()); let mut moves: Vec<(Reg, Opnd)> = vec![]; for (idx, &arg) in branch.args.iter().enumerate() { - moves.push((param_reg(idx), jit.get_opnd(arg)?)); + match param_opnd(idx) { + Opnd::Reg(reg) => { + // If a parameter is a register, we need to parallel-move it + moves.push((reg, jit.get_opnd(arg)?)); + }, + param => { + // If a parameter is memory, we set it beforehand + asm.mov(param, jit.get_opnd(arg)?); + } + } } asm.parallel_mov(moves); } Some(()) } -/// Get the local variable at the given index -fn gen_getlocal(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir::Opnd { +/// Get a method parameter on JIT entry. As of entry, whether EP is escaped or not solely +/// depends on the ISEQ type. +fn gen_entry_param(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir::Opnd { let ep_offset = local_idx_to_ep_offset(iseq, local_idx); - if JITState::assume_no_ep_escape(iseq) { + // If the ISEQ does not escape EP, we can optimize the local variable access using the SP register. + if !iseq_entry_escapes_ep(iseq) { // Create a reference to the local variable using the SP register. We assume EP == BP. // TODO: Implement the invalidation in rb_zjit_invalidate_ep_is_bp() let offs = -(SIZEOF_VALUE_I32 * (ep_offset + 1)); @@ -565,7 +603,13 @@ fn gen_const(val: VALUE) -> lir::Opnd { /// Compile a basic block argument fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd { - asm.live_reg_opnd(Opnd::Reg(param_reg(idx))) + // Allocate a register or a stack slot + match param_opnd(idx) { + // If it's a register, insert LiveReg instruction to reserve the register + // in the register pool for register allocation. + param @ Opnd::Reg(_) => asm.live_reg_opnd(param), + param => param, + } } /// Compile a jump to a basic block @@ -807,7 +851,7 @@ fn gen_new_range( } /// Compile code that exits from JIT code with a return value -fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { +fn gen_return(jit: &JITState, asm: &mut Assembler, val: lir::Opnd) -> Option<()> { // Pop the current frame (ec->cfp++) // Note: the return PC is already in the previous CFP asm_comment!(asm, "pop stack frame"); @@ -815,6 +859,13 @@ fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { asm.mov(CFP, incr_cfp); asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + // Restore the C stack pointer bumped for basic block arguments + if jit.c_stack_bytes > 0 { + asm_comment!(asm, "restore C stack pointer"); + let new_sp = asm.add(NATIVE_STACK_PTR, jit.c_stack_bytes.into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + } + asm.frame_teardown(); // Return from the function @@ -893,6 +944,13 @@ fn gen_fixnum_ge(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> Opti Some(asm.csel_ge(Qtrue.into(), Qfalse.into())) } +// Compile val == nil +fn gen_isnil(asm: &mut Assembler, val: lir::Opnd) -> Option { + asm.cmp(val, Qnil.into()); + // TODO: Implement and use setcc + Some(asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))) +} + fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> Option { // Save PC gen_save_pc(asm, state); @@ -995,17 +1053,15 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C asm.mov(cfp_opnd(RUBY_OFFSET_CFP_BLOCK_CODE), 0.into()); } -/// Return a register we use for the basic block argument at a given index -fn param_reg(idx: usize) -> Reg { - // To simplify the implementation, allocate a fixed register for each basic block argument for now. +/// Return an operand we use for the basic block argument at a given index +fn param_opnd(idx: usize) -> Opnd { + // To simplify the implementation, allocate a fixed register or a stack slot for each basic block argument for now. // TODO: Allow allocating arbitrary registers for basic block arguments - if idx >= ALLOC_REGS.len() { - unimplemented!( - "register spilling not yet implemented, too many basic block arguments ({}/{})", - idx + 1, ALLOC_REGS.len() - ); + if idx < ALLOC_REGS.len() { + Opnd::Reg(ALLOC_REGS[idx]) + } else { + Opnd::mem(64, NATIVE_STACK_PTR, -((idx - ALLOC_REGS.len() + 1) as i32) * SIZEOF_VALUE_I32) } - ALLOC_REGS[idx] } /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. @@ -1048,10 +1104,47 @@ fn side_exit(jit: &mut JITState, state: &FrameState) -> Option { pc: state.pc, stack, locals, + c_stack_bytes: jit.c_stack_bytes, }; Some(target) } +/// Return true if a given ISEQ is known to escape EP to the heap on entry. +/// +/// As of vm_push_frame(), EP is always equal to BP. However, after pushing +/// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP. +fn iseq_entry_escapes_ep(iseq: IseqPtr) -> bool { + match unsafe { get_iseq_body_type(iseq) } { + //
frame is always associated to TOPLEVEL_BINDING. + ISEQ_TYPE_MAIN | + // Kernel#eval uses a heap EP when a Binding argument is not nil. + ISEQ_TYPE_EVAL => true, + _ => false, + } +} + +/// Returne the maximum number of arguments for a block in a given function +fn max_num_params(function: &Function) -> usize { + let reverse_post_order = function.rpo(); + reverse_post_order.iter().map(|&block_id| { + let block = function.block(block_id); + block.params().len() + }).max().unwrap_or(0) +} + +/// Given the number of spill slots needed for a function, return the number of bytes +/// the function needs to allocate on the stack for the stack frame. +fn aligned_stack_bytes(num_slots: usize) -> usize { + // Both x86_64 and arm64 require the stack to be aligned to 16 bytes. + // Since SIZEOF_VALUE is 8 bytes, we need to round up the size to the nearest even number. + let num_slots = if num_slots % 2 == 0 { + num_slots + } else { + num_slots + 1 + }; + num_slots * SIZEOF_VALUE +} + impl Assembler { /// Make a C call while marking the start and end positions of it fn ccall_with_branch(&mut self, fptr: *const u8, opnds: Vec, branch: &Rc) -> Opnd { diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 3a1c45ffd3b537..82f0e3980452fb 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -218,6 +218,9 @@ pub use rb_vm_get_special_object as vm_get_special_object; /// Helper so we can get a Rust string for insn_name() pub fn insn_name(opcode: usize) -> String { + if opcode >= VM_INSTRUCTION_SIZE.try_into().unwrap() { + return "".into(); + } unsafe { // Look up Ruby's NULL-terminated insn name string let op_name = raw_insn_name(VALUE(opcode)); @@ -701,7 +704,7 @@ pub fn iseq_name(iseq: IseqPtr) -> String { if iseq_label == Qnil { "None".to_string() } else { - ruby_str_to_rust(iseq_label) + ruby_str_to_rust_string(iseq_label) } } @@ -717,7 +720,7 @@ pub fn iseq_get_location(iseq: IseqPtr, pos: u16) -> String { if iseq_path == Qnil { s.push_str("None"); } else { - s.push_str(&ruby_str_to_rust(iseq_path)); + s.push_str(&ruby_str_to_rust_string(iseq_path)); } s.push_str(":"); s.push_str(&iseq_lineno.to_string()); @@ -728,7 +731,7 @@ pub fn iseq_get_location(iseq: IseqPtr, pos: u16) -> String { // Convert a CRuby UTF-8-encoded RSTRING into a Rust string. // This should work fine on ASCII strings and anything else // that is considered legal UTF-8, including embedded nulls. -fn ruby_str_to_rust(v: VALUE) -> String { +fn ruby_str_to_rust_string(v: VALUE) -> String { let str_ptr = unsafe { rb_RSTRING_PTR(v) } as *mut u8; let str_len: usize = unsafe { rb_RSTRING_LEN(v) }.try_into().unwrap(); let str_slice: &[u8] = unsafe { std::slice::from_raw_parts(str_ptr, str_len) }; @@ -738,6 +741,11 @@ fn ruby_str_to_rust(v: VALUE) -> String { } } +pub fn ruby_sym_to_rust_string(v: VALUE) -> String { + let ruby_str = unsafe { rb_sym2str(v) }; + ruby_str_to_rust_string(ruby_str) +} + /// A location in Rust code for integrating with debugging facilities defined in C. /// Use the [src_loc!] macro to crate an instance. pub struct SourceLocation { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 1367c9381b7c1e..10c5c7c903df47 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -752,6 +752,7 @@ unsafe extern "C" { pub static mut rb_cNilClass: VALUE; pub static mut rb_cNumeric: VALUE; pub static mut rb_cRange: VALUE; + pub static mut rb_cRegexp: VALUE; pub static mut rb_cString: VALUE; pub static mut rb_cSymbol: VALUE; pub static mut rb_cThread: VALUE; @@ -790,6 +791,7 @@ unsafe extern "C" { pub fn rb_intern(name: *const ::std::os::raw::c_char) -> ID; pub fn rb_intern2(name: *const ::std::os::raw::c_char, len: ::std::os::raw::c_long) -> ID; pub fn rb_id2str(id: ID) -> VALUE; + pub fn rb_sym2str(symbol: VALUE) -> VALUE; pub fn rb_class2name(klass: VALUE) -> *const ::std::os::raw::c_char; pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE; pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE; @@ -900,6 +902,7 @@ unsafe extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); + pub static mut rb_cSet: VALUE; pub fn rb_zjit_get_page_size() -> u32; pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index edaaba1516f2a3..51ecb1c787f9ec 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -80,6 +80,8 @@ pub fn init() -> Annotations { annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cNilClass, "nil?", types::TrueClassExact, no_gc, leaf, elidable); + annotate!(rb_mKernel, "nil?", types::FalseClassExact, no_gc, leaf, elidable); Annotations { cfuncs: std::mem::take(cfuncs) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ad32d06f3e955c..32afebce13e17a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -393,6 +393,28 @@ impl PtrPrintMap { } } +#[derive(Debug, Clone)] +pub enum SideExitReason { + UnknownNewarraySend(vm_opt_newarray_send_type), + UnknownCallType, + UnknownOpcode(u32), +} + +impl std::fmt::Display for SideExitReason { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + SideExitReason::UnknownOpcode(opcode) => write!(f, "UnknownOpcode({})", insn_name(*opcode as usize)), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_MAX) => write!(f, "UnknownNewarraySend(MAX)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_MIN) => write!(f, "UnknownNewarraySend(MIN)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_HASH) => write!(f, "UnknownNewarraySend(HASH)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK) => write!(f, "UnknownNewarraySend(PACK)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK_BUFFER) => write!(f, "UnknownNewarraySend(PACK_BUFFER)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_INCLUDE_P) => write!(f, "UnknownNewarraySend(INCLUDE_P)"), + _ => write!(f, "{self:?}"), + } + } +} + /// An instruction in the SSA IR. The output of an instruction is referred to by the index of /// the instruction ([`InsnId`]). SSA form enables this, and [`UnionFind`] ([`Function::find`]) /// helps with editing. @@ -488,6 +510,8 @@ pub enum Insn { /// Control flow instructions Return { val: InsnId }, + /// Non-local control flow. See the throw YARV instruction + Throw { throw_state: u32, val: InsnId }, /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >= FixnumAdd { left: InsnId, right: InsnId, state: InsnId }, @@ -516,7 +540,7 @@ pub enum Insn { PatchPoint(Invariant), /// Side-exit into the interpreter. - SideExit { state: InsnId }, + SideExit { state: InsnId, reason: SideExitReason }, } impl Insn { @@ -527,7 +551,7 @@ impl Insn { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } - | Insn::SetLocal { .. } => false, + | Insn::SetLocal { .. } | Insn::Throw { .. } => false, _ => true, } } @@ -535,7 +559,7 @@ impl Insn { /// Return true if the instruction ends a basic block and false otherwise. pub fn is_terminator(&self) -> bool { match self { - Insn::Jump(_) | Insn::Return { .. } | Insn::SideExit { .. } => true, + Insn::Jump(_) | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true, _ => false, } } @@ -570,6 +594,8 @@ impl Insn { Insn::FixnumLe { .. } => false, Insn::FixnumGt { .. } => false, Insn::FixnumGe { .. } => false, + Insn::GetLocal { .. } => false, + Insn::IsNil { .. } => false, Insn::CCall { elidable, .. } => !elidable, _ => true, } @@ -710,9 +736,26 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"), Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") }, Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") }, - Insn::SideExit { .. } => write!(f, "SideExit"), - Insn::PutSpecialObject { value_type } => { - write!(f, "PutSpecialObject {}", value_type) + Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"), + Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"), + Insn::Throw { throw_state, val } => { + let mut state_string = match throw_state & VM_THROW_STATE_MASK { + RUBY_TAG_NONE => "TAG_NONE".to_string(), + RUBY_TAG_RETURN => "TAG_RETURN".to_string(), + RUBY_TAG_BREAK => "TAG_BREAK".to_string(), + RUBY_TAG_NEXT => "TAG_NEXT".to_string(), + RUBY_TAG_RETRY => "TAG_RETRY".to_string(), + RUBY_TAG_REDO => "TAG_REDO".to_string(), + RUBY_TAG_RAISE => "TAG_RAISE".to_string(), + RUBY_TAG_THROW => "TAG_THROW".to_string(), + RUBY_TAG_FATAL => "TAG_FATAL".to_string(), + tag => format!("{tag}") + }; + if throw_state & VM_THROW_NO_ESCAPE_FLAG != 0 { + use std::fmt::Write; + write!(state_string, "|NO_ESCAPE")?; + } + write!(f, "Throw {state_string}, {val}") } insn => { write!(f, "{insn:?}") } } @@ -1013,6 +1056,7 @@ impl Function { } }, Return { val } => Return { val: find!(*val) }, + &Throw { throw_state, val } => Throw { throw_state, val: find!(val) }, StringCopy { val, chilled } => StringCopy { val: find!(*val), chilled: *chilled }, StringIntern { val } => StringIntern { val: find!(*val) }, Test { val } => Test { val: find!(*val) }, @@ -1072,7 +1116,7 @@ impl Function { ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) }, ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state }, &HashDup { val , state } => HashDup { val: find!(val), state }, - &CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun: cfun, args: find_vec!(args), name: name, return_type: return_type, elidable }, + &CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun, args: find_vec!(args), name, return_type, elidable }, &Defined { op_type, obj, pushval, v } => Defined { op_type, obj, pushval, v: find!(v) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, NewArray { elements, state } => NewArray { elements: find_vec!(*elements), state: find!(*state) }, @@ -1117,10 +1161,10 @@ impl Function { match &self.insns[insn.0] { Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), Insn::SetGlobal { .. } | Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_) - | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } + | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } => - panic!("Cannot infer type of instruction with no output"), + panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), Insn::Const { val: Const::CInt8(val) } => Type::from_cint(types::CInt8, *val as i64), @@ -1753,6 +1797,7 @@ impl Function { Insn::StringCopy { val, .. } | Insn::StringIntern { val } | Insn::Return { val } + | Insn::Throw { val, .. } | Insn::Defined { v: val, .. } | Insn::Test { val } | Insn::SetLocal { val, .. } @@ -1840,7 +1885,7 @@ impl Function { worklist.push_back(state); } Insn::GetGlobal { state, .. } | - Insn::SideExit { state } => worklist.push_back(state), + Insn::SideExit { state, .. } => worklist.push_back(state), } } // Now remove all unnecessary instructions @@ -2186,9 +2231,15 @@ pub enum CallType { Forwarding, } +#[derive(Debug, PartialEq)] +pub enum ParameterType { + Optional, +} + #[derive(Debug, PartialEq)] pub enum ParseError { StackUnderflow(FrameState), + UnknownParameterType(ParameterType), MalformedIseq(u32), // insn_idx into iseq_encoded } @@ -2248,8 +2299,14 @@ impl ProfileOracle { /// The index of the self parameter in the HIR function pub const SELF_PARAM_IDX: usize = 0; +fn filter_unknown_parameter_type(iseq: *const rb_iseq_t) -> Result<(), ParseError> { + if unsafe { rb_get_iseq_body_param_opt_num(iseq) } != 0 { return Err(ParseError::UnknownParameterType(ParameterType::Optional)); } + Ok(()) +} + /// Compile ISEQ into High-level IR pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { + filter_unknown_parameter_type(iseq)?; let payload = get_or_create_iseq_payload(iseq); let mut profiles = ProfileOracle::new(payload); let mut fun = Function::new(iseq); @@ -2390,7 +2447,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), _ => { // Unknown opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownNewarraySend(method) }); break; // End the block }, }; @@ -2616,7 +2673,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2642,7 +2699,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2673,7 +2730,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2696,6 +2753,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::Return { val: state.stack_pop()? }); break; // Don't enqueue the next block as a successor } + YARVINSN_throw => { + fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()? }); + break; // Don't enqueue the next block as a successor + } // These are opt_send_without_block and all the opt_* instructions // specialized to a certain method that could also be serviced @@ -2729,7 +2790,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2757,7 +2818,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2881,7 +2942,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { _ => { // Unknown opcode; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownOpcode(opcode) }); break; // End the block } } @@ -3227,6 +3288,11 @@ mod tests { assert_eq!(result.unwrap_err(), reason); } + #[test] + fn test_cant_compile_optional() { + eval("def test(x=1) = 123"); + assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Optional)); + } #[test] fn test_putobject() { @@ -3359,8 +3425,8 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_newhash, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v4:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v5:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v4:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v5:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v7:HashExact = NewHash v4: v1, v5: v2 Return v7 "#]]); @@ -3417,7 +3483,7 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_putobject, expect![[r#" fn test: bb0(v0:BasicObject): - v2:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v2:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) Return v2 "#]]); } @@ -3918,7 +3984,7 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v4:ArrayExact = ToArray v1 - SideExit + SideExit UnknownCallType "#]]); } @@ -3930,7 +3996,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownCallType "#]]); } @@ -3943,7 +4009,7 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) - SideExit + SideExit UnknownCallType "#]]); } @@ -3955,7 +4021,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownCallType "#]]); } @@ -3969,7 +4035,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuper) "#]]); } @@ -3981,7 +4047,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuper) "#]]); } @@ -3993,7 +4059,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuperforward) "#]]); } @@ -4007,14 +4073,14 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v3:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) + v3:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) v5:HashExact = NewHash v7:BasicObject = SendWithoutBlock v3, :core#hash_merge_kwd, v5, v1 - v8:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) - v9:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v8:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) + v9:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v10:Fixnum[1] = Const Value(1) v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10 - SideExit + SideExit UnknownCallType "#]]); } @@ -4029,7 +4095,7 @@ mod tests { v4:ArrayExact = ToNewArray v1 v5:Fixnum[1] = Const Value(1) ArrayPush v4, v5 - SideExit + SideExit UnknownCallType "#]]); } @@ -4041,7 +4107,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownOpcode(sendforward) "#]]); } @@ -4110,7 +4176,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(MIN) "#]]); } @@ -4130,7 +4196,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(HASH) "#]]); } @@ -4152,7 +4218,7 @@ mod tests { v7:BasicObject = SendWithoutBlock v1, :+, v2 v8:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v9:StringExact = StringCopy v8 - SideExit + SideExit UnknownNewarraySend(PACK) "#]]); } @@ -4174,7 +4240,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(INCLUDE_P) "#]]); } @@ -4459,10 +4525,10 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_putspecialobject, expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) + v2:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) v3:BasicObject = PutSpecialObject CBase - v4:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v5:StaticSymbol[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v4:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) + v5:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) v7:BasicObject = SendWithoutBlock v2, :core#set_method_alias, v3, v4, v5 Return v7 "#]]); @@ -4598,7 +4664,27 @@ mod tests { v3:Fixnum[1] = Const Value(1) v5:BasicObject = ObjToString v3 v7:String = AnyToString v3, str: v5 - SideExit + SideExit UnknownOpcode(concatstrings) + "#]]); + } + + #[test] + fn throw() { + eval(" + define_method(:throw_return) { return 1 } + define_method(:throw_break) { break 2 } + "); + assert_method_hir_with_opcode("throw_return", YARVINSN_throw, expect![[r#" + fn block in : + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + Throw TAG_RETURN, v2 + "#]]); + assert_method_hir_with_opcode("throw_break", YARVINSN_throw, expect![[r#" + fn block in : + bb0(v0:BasicObject): + v2:Fixnum[2] = Const Value(2) + Throw TAG_BREAK, v2 "#]]); } } @@ -5667,6 +5753,69 @@ mod opt_tests { "#]]); } + #[test] + fn normal_class_type_inference() { + eval(" + class C; end + def test = C + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v7:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 + "#]]); + } + + #[test] + fn core_classes_type_inference() { + eval(" + def test = [String, Class, Module, BasicObject] + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v15:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Class) + v18:ClassExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1020, Module) + v21:ClassExact[VALUE(0x1028)] = Const Value(VALUE(0x1028)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1030, BasicObject) + v24:ClassExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + v11:ArrayExact = NewArray v15, v18, v21, v24 + Return v11 + "#]]); + } + + #[test] + fn module_instances_not_class_exact() { + eval(" + def test = [Enumerable, Kernel] + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Enumerable) + v11:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Kernel) + v14:BasicObject[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v7:ArrayExact = NewArray v11, v14 + Return v7 + "#]]); + } + #[test] fn eliminate_array_size() { eval(" @@ -5816,7 +5965,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v7:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) Return v7 "#]]); } @@ -5833,7 +5982,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v20:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:NilClassExact = Const Value(nil) v11:BasicObject = SendWithoutBlock v20, :new Return v11 @@ -5856,7 +6005,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v22:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v22:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:NilClassExact = Const Value(nil) v5:Fixnum[1] = Const Value(1) v13:BasicObject = SendWithoutBlock v22, :new, v5 @@ -6156,7 +6305,7 @@ mod opt_tests { v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:StringExact = StringCopy v3 - SideExit + SideExit UnknownOpcode(concatstrings) "#]]); } @@ -6172,7 +6321,43 @@ mod opt_tests { v3:Fixnum[1] = Const Value(1) v10:BasicObject = SendWithoutBlock v3, :to_s v7:String = AnyToString v3, str: v10 - SideExit + SideExit UnknownOpcode(concatstrings) + "#]]); + } + + #[test] + fn test_branchnil_nil() { + eval(" + def test + x = nil + x&.itself + end + "); + + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:NilClassExact = Const Value(nil) + Return v3 + "#]]); + } + + #[test] + fn test_branchnil_truthy() { + eval(" + def test + x = 1 + x&.itself + end + "); + + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008) + v15:BasicObject = CCall itself@0x1010, v3 + Return v15 "#]]); } @@ -6235,4 +6420,101 @@ mod opt_tests { Return v11 "#]]); } + + #[test] + fn test_set_type_from_constant() { + eval(" + MY_SET = Set.new + + def test = MY_SET + + test + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_SET) + v7:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 + "#]]); + } + + #[test] + fn test_regexp_type() { + eval(" + def test = /a/ + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + Return v2 + "#]]); + } + + #[test] + fn test_nil_nil_specialized_to_ccall() { + eval(" + def test = nil.nil? + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:NilClassExact = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008) + v7:TrueClassExact = CCall nil?@0x1010, v2 + Return v7 + "#]]); + } + + #[test] + fn test_eliminate_nil_nil_specialized_to_ccall() { + eval(" + def test + nil.nil? + 1 + end + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008) + v5:Fixnum[1] = Const Value(1) + Return v5 + "#]]); + } + + #[test] + fn test_non_nil_nil_specialized_to_ccall() { + eval(" + def test = 1.nil? + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008) + v7:FalseClassExact = CCall nil?@0x1010, v2 + Return v7 + "#]]); + } + + #[test] + fn test_eliminate_non_nil_nil_specialized_to_ccall() { + eval(" + def test + 1.nil? + 2 + end + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008) + v5:Fixnum[2] = Const Value(2) + Return v5 + "#]]); + } } diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 92351aafa2b74f..50259a9816e500 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -72,6 +72,9 @@ def base_type name base_type "Array" base_type "Hash" base_type "Range" +base_type "Set" +base_type "Regexp" +base_type "Class" (integer, integer_exact) = base_type "Integer" # CRuby partitions Integer into immediate and non-immediate variants. diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index 7d6f92a1808b18..57349aba147f22 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -9,7 +9,7 @@ mod bits { pub const BasicObjectSubclass: u64 = 1u64 << 3; pub const Bignum: u64 = 1u64 << 4; pub const BoolExact: u64 = FalseClassExact | TrueClassExact; - pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | StringExact | SymbolExact | TrueClassExact; + pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | ClassExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | RegexpExact | SetExact | StringExact | SymbolExact | TrueClassExact; pub const CBool: u64 = 1u64 << 5; pub const CDouble: u64 = 1u64 << 6; pub const CInt: u64 = CSigned | CUnsigned; @@ -27,47 +27,56 @@ mod bits { pub const CUnsigned: u64 = CUInt16 | CUInt32 | CUInt64 | CUInt8; pub const CValue: u64 = CBool | CDouble | CInt | CNull | CPtr; pub const CallableMethodEntry: u64 = 1u64 << 17; - pub const DynamicSymbol: u64 = 1u64 << 18; + pub const Class: u64 = ClassExact | ClassSubclass; + pub const ClassExact: u64 = 1u64 << 18; + pub const ClassSubclass: u64 = 1u64 << 19; + pub const DynamicSymbol: u64 = 1u64 << 20; pub const Empty: u64 = 0u64; pub const FalseClass: u64 = FalseClassExact | FalseClassSubclass; - pub const FalseClassExact: u64 = 1u64 << 19; - pub const FalseClassSubclass: u64 = 1u64 << 20; - pub const Fixnum: u64 = 1u64 << 21; + pub const FalseClassExact: u64 = 1u64 << 21; + pub const FalseClassSubclass: u64 = 1u64 << 22; + pub const Fixnum: u64 = 1u64 << 23; pub const Float: u64 = FloatExact | FloatSubclass; pub const FloatExact: u64 = Flonum | HeapFloat; - pub const FloatSubclass: u64 = 1u64 << 22; - pub const Flonum: u64 = 1u64 << 23; + pub const FloatSubclass: u64 = 1u64 << 24; + pub const Flonum: u64 = 1u64 << 25; pub const Hash: u64 = HashExact | HashSubclass; - pub const HashExact: u64 = 1u64 << 24; - pub const HashSubclass: u64 = 1u64 << 25; - pub const HeapFloat: u64 = 1u64 << 26; + pub const HashExact: u64 = 1u64 << 26; + pub const HashSubclass: u64 = 1u64 << 27; + pub const HeapFloat: u64 = 1u64 << 28; pub const Immediate: u64 = FalseClassExact | Fixnum | Flonum | NilClassExact | StaticSymbol | TrueClassExact | Undef; pub const Integer: u64 = IntegerExact | IntegerSubclass; pub const IntegerExact: u64 = Bignum | Fixnum; - pub const IntegerSubclass: u64 = 1u64 << 27; + pub const IntegerSubclass: u64 = 1u64 << 29; pub const NilClass: u64 = NilClassExact | NilClassSubclass; - pub const NilClassExact: u64 = 1u64 << 28; - pub const NilClassSubclass: u64 = 1u64 << 29; - pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | String | Symbol | TrueClass; - pub const ObjectExact: u64 = 1u64 << 30; - pub const ObjectSubclass: u64 = 1u64 << 31; + pub const NilClassExact: u64 = 1u64 << 30; + pub const NilClassSubclass: u64 = 1u64 << 31; + pub const Object: u64 = Array | Class | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Regexp | Set | String | Symbol | TrueClass; + pub const ObjectExact: u64 = 1u64 << 32; + pub const ObjectSubclass: u64 = 1u64 << 33; pub const Range: u64 = RangeExact | RangeSubclass; - pub const RangeExact: u64 = 1u64 << 32; - pub const RangeSubclass: u64 = 1u64 << 33; + pub const RangeExact: u64 = 1u64 << 34; + pub const RangeSubclass: u64 = 1u64 << 35; + pub const Regexp: u64 = RegexpExact | RegexpSubclass; + pub const RegexpExact: u64 = 1u64 << 36; + pub const RegexpSubclass: u64 = 1u64 << 37; pub const RubyValue: u64 = BasicObject | CallableMethodEntry | Undef; - pub const StaticSymbol: u64 = 1u64 << 34; + pub const Set: u64 = SetExact | SetSubclass; + pub const SetExact: u64 = 1u64 << 38; + pub const SetSubclass: u64 = 1u64 << 39; + pub const StaticSymbol: u64 = 1u64 << 40; pub const String: u64 = StringExact | StringSubclass; - pub const StringExact: u64 = 1u64 << 35; - pub const StringSubclass: u64 = 1u64 << 36; - pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; + pub const StringExact: u64 = 1u64 << 41; + pub const StringSubclass: u64 = 1u64 << 42; + pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | ClassSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | RegexpSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; pub const Symbol: u64 = SymbolExact | SymbolSubclass; pub const SymbolExact: u64 = DynamicSymbol | StaticSymbol; - pub const SymbolSubclass: u64 = 1u64 << 37; + pub const SymbolSubclass: u64 = 1u64 << 43; pub const TrueClass: u64 = TrueClassExact | TrueClassSubclass; - pub const TrueClassExact: u64 = 1u64 << 38; - pub const TrueClassSubclass: u64 = 1u64 << 39; - pub const Undef: u64 = 1u64 << 40; - pub const AllBitPatterns: [(&'static str, u64); 67] = [ + pub const TrueClassExact: u64 = 1u64 << 44; + pub const TrueClassSubclass: u64 = 1u64 << 45; + pub const Undef: u64 = 1u64 << 46; + pub const AllBitPatterns: [(&'static str, u64); 76] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -87,6 +96,12 @@ mod bits { ("StringExact", StringExact), ("SymbolExact", SymbolExact), ("StaticSymbol", StaticSymbol), + ("Set", Set), + ("SetSubclass", SetSubclass), + ("SetExact", SetExact), + ("Regexp", Regexp), + ("RegexpSubclass", RegexpSubclass), + ("RegexpExact", RegexpExact), ("Range", Range), ("RangeSubclass", RangeSubclass), ("RangeExact", RangeExact), @@ -111,6 +126,9 @@ mod bits { ("FalseClassSubclass", FalseClassSubclass), ("FalseClassExact", FalseClassExact), ("DynamicSymbol", DynamicSymbol), + ("Class", Class), + ("ClassSubclass", ClassSubclass), + ("ClassExact", ClassExact), ("CallableMethodEntry", CallableMethodEntry), ("CValue", CValue), ("CInt", CInt), @@ -136,7 +154,7 @@ mod bits { ("ArrayExact", ArrayExact), ("Empty", Empty), ]; - pub const NumTypeBits: u64 = 41; + pub const NumTypeBits: u64 = 47; } pub mod types { use super::*; @@ -167,6 +185,9 @@ pub mod types { pub const CUnsigned: Type = Type::from_bits(bits::CUnsigned); pub const CValue: Type = Type::from_bits(bits::CValue); pub const CallableMethodEntry: Type = Type::from_bits(bits::CallableMethodEntry); + pub const Class: Type = Type::from_bits(bits::Class); + pub const ClassExact: Type = Type::from_bits(bits::ClassExact); + pub const ClassSubclass: Type = Type::from_bits(bits::ClassSubclass); pub const DynamicSymbol: Type = Type::from_bits(bits::DynamicSymbol); pub const Empty: Type = Type::from_bits(bits::Empty); pub const FalseClass: Type = Type::from_bits(bits::FalseClass); @@ -194,7 +215,13 @@ pub mod types { pub const Range: Type = Type::from_bits(bits::Range); pub const RangeExact: Type = Type::from_bits(bits::RangeExact); pub const RangeSubclass: Type = Type::from_bits(bits::RangeSubclass); + pub const Regexp: Type = Type::from_bits(bits::Regexp); + pub const RegexpExact: Type = Type::from_bits(bits::RegexpExact); + pub const RegexpSubclass: Type = Type::from_bits(bits::RegexpSubclass); pub const RubyValue: Type = Type::from_bits(bits::RubyValue); + pub const Set: Type = Type::from_bits(bits::Set); + pub const SetExact: Type = Type::from_bits(bits::SetExact); + pub const SetSubclass: Type = Type::from_bits(bits::SetSubclass); pub const StaticSymbol: Type = Type::from_bits(bits::StaticSymbol); pub const String: Type = Type::from_bits(bits::String); pub const StringExact: Type = Type::from_bits(bits::StringExact); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 784c2f324ebbaf..422055e6d02aa2 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -1,8 +1,9 @@ #![allow(non_upper_case_globals)] -use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH}; -use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange}; +use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH, RUBY_T_CLASS}; +use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet, rb_cRegexp, rb_cClass, rb_cModule}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; +use crate::cruby::ruby_sym_to_rust_string; use crate::cruby::rb_mRubyVMFrozenCore; use crate::hir::PtrPrintMap; @@ -70,6 +71,7 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R match ty.spec { Specialization::Any | Specialization::Empty => { Ok(()) }, Specialization::Object(val) if val == unsafe { rb_mRubyVMFrozenCore } => write!(f, "[VMFrozenCore]"), + Specialization::Object(val) if ty.is_subtype(types::SymbolExact) => write!(f, "[:{}]", ruby_sym_to_rust_string(val)), Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)), Specialization::Type(val) => write!(f, "[class:{}]", get_class_name(val)), Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(val)), @@ -143,6 +145,11 @@ fn is_range_exact(val: VALUE) -> bool { val.class_of() == unsafe { rb_cRange } } +fn is_class_exact(val: VALUE) -> bool { + // Objects with RUBY_T_CLASS type and not instances of Module + val.builtin_type() == RUBY_T_CLASS && val.class_of() != unsafe { rb_cModule } +} + impl Type { /// Create a `Type` from the given integer. pub const fn fixnum(val: i64) -> Type { @@ -195,6 +202,15 @@ impl Type { else if is_string_exact(val) { Type { bits: bits::StringExact, spec: Specialization::Object(val) } } + else if is_class_exact(val) { + Type { bits: bits::ClassExact, spec: Specialization::Object(val) } + } + else if val.class_of() == unsafe { rb_cRegexp } { + Type { bits: bits::RegexpExact, spec: Specialization::Object(val) } + } + else if val.class_of() == unsafe { rb_cSet } { + Type { bits: bits::SetExact, spec: Specialization::Object(val) } + } else if val.class_of() == unsafe { rb_cObject } { Type { bits: bits::ObjectExact, spec: Specialization::Object(val) } } @@ -280,6 +296,7 @@ impl Type { fn is_builtin(class: VALUE) -> bool { if class == unsafe { rb_cArray } { return true; } + if class == unsafe { rb_cClass } { return true; } if class == unsafe { rb_cFalseClass } { return true; } if class == unsafe { rb_cFloat } { return true; } if class == unsafe { rb_cHash } { return true; } @@ -287,6 +304,7 @@ impl Type { if class == unsafe { rb_cNilClass } { return true; } if class == unsafe { rb_cObject } { return true; } if class == unsafe { rb_cRange } { return true; } + if class == unsafe { rb_cRegexp } { return true; } if class == unsafe { rb_cString } { return true; } if class == unsafe { rb_cSymbol } { return true; } if class == unsafe { rb_cTrueClass } { return true; } @@ -387,6 +405,7 @@ impl Type { return Some(val); } if self.is_subtype(types::ArrayExact) { return Some(unsafe { rb_cArray }); } + if self.is_subtype(types::ClassExact) { return Some(unsafe { rb_cClass }); } if self.is_subtype(types::FalseClassExact) { return Some(unsafe { rb_cFalseClass }); } if self.is_subtype(types::FloatExact) { return Some(unsafe { rb_cFloat }); } if self.is_subtype(types::HashExact) { return Some(unsafe { rb_cHash }); } @@ -394,6 +413,8 @@ impl Type { if self.is_subtype(types::NilClassExact) { return Some(unsafe { rb_cNilClass }); } if self.is_subtype(types::ObjectExact) { return Some(unsafe { rb_cObject }); } if self.is_subtype(types::RangeExact) { return Some(unsafe { rb_cRange }); } + if self.is_subtype(types::RegexpExact) { return Some(unsafe { rb_cRegexp }); } + if self.is_subtype(types::SetExact) { return Some(unsafe { rb_cSet }); } if self.is_subtype(types::StringExact) { return Some(unsafe { rb_cString }); } if self.is_subtype(types::SymbolExact) { return Some(unsafe { rb_cSymbol }); } if self.is_subtype(types::TrueClassExact) { return Some(unsafe { rb_cTrueClass }); } @@ -585,6 +606,21 @@ mod tests { assert_eq!(types::Integer.inexact_ruby_class(), None); } + #[test] + fn set() { + assert_subtype(types::SetExact, types::Set); + assert_subtype(types::SetSubclass, types::Set); + } + + #[test] + fn set_has_ruby_class() { + crate::cruby::with_rubyvm(|| { + assert_eq!(types::SetExact.runtime_exact_ruby_class(), Some(unsafe { rb_cSet })); + assert_eq!(types::Set.runtime_exact_ruby_class(), None); + assert_eq!(types::SetSubclass.runtime_exact_ruby_class(), None); + }); + } + #[test] fn display_exact_bits_match() { assert_eq!(format!("{}", Type::fixnum(4)), "Fixnum[4]"); diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 77fd78d95e9ed9..9703656e709d98 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -1,6 +1,6 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; -use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::ZJITState, state::zjit_enabled_p}; +use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; /// Used to track all of the various block references that contain assumptions /// about the state of the virtual machine. @@ -11,19 +11,28 @@ pub struct Invariants { /// Set of ISEQs whose JIT code assumes that it doesn't escape EP no_ep_escape_iseqs: HashSet, + + /// Map from a class and its associated basic operator to a set of patch points + bop_patch_points: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet>, } /// Called when a basic operator is redefined. Note that all the blocks assuming /// the stability of different operators are invalidated together and we don't /// do fine-grained tracking. #[unsafe(no_mangle)] -pub extern "C" fn rb_zjit_bop_redefined(_klass: RedefinitionFlag, _bop: ruby_basic_operators) { +pub extern "C" fn rb_zjit_bop_redefined(klass: RedefinitionFlag, bop: ruby_basic_operators) { // If ZJIT isn't enabled, do nothing if !zjit_enabled_p() { return; } - unimplemented!("Invalidation on BOP redefinition is not implemented yet"); + let invariants = ZJITState::get_invariants(); + if let Some(code_ptrs) = invariants.bop_patch_points.get(&(klass, bop)) { + // Invalidate all patch points for this BOP + for &ptr in code_ptrs { + unimplemented!("Invalidation on BOP redefinition is not implemented yet: {ptr:?}"); + } + } } /// Invalidate blocks for a given ISEQ that assumes environment pointer is @@ -40,6 +49,8 @@ pub extern "C" fn rb_zjit_invalidate_ep_is_bp(iseq: IseqPtr) { invariants.ep_escape_iseqs.insert(iseq); // If the ISEQ has been compiled assuming it doesn't escape EP, invalidate the JIT code. + // Note: Nobody calls track_no_ep_escape_assumption() for now, so this is always false. + // TODO: Add a PatchPoint that assumes EP == BP in HIR and invalidate it here. if invariants.no_ep_escape_iseqs.contains(&iseq) { unimplemented!("Invalidation on EP escape is not implemented yet"); } @@ -55,3 +66,9 @@ pub fn track_no_ep_escape_assumption(iseq: IseqPtr) { pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { ZJITState::get_invariants().ep_escape_iseqs.contains(&iseq) } + +/// Track a patch point for a basic operator in a given class. +pub fn track_bop_assumption(klass: RedefinitionFlag, bop: ruby_basic_operators, code_ptr: CodePtr) { + let invariants = ZJITState::get_invariants(); + invariants.bop_patch_points.entry((klass, bop)).or_default().insert(code_ptr); +} diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index d76c3d76d3719d..f62cccd3aafb45 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -62,7 +62,7 @@ pub trait Allocator { /// Pointer into a [VirtualMemory] represented as an offset from the base. /// Note: there is no NULL constant for [CodePtr]. You should use `Option` instead. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)] #[repr(C, packed)] pub struct CodePtr(u32);