diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index bc5b81a9b99b2c..f3f496c29ac593 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -74,7 +74,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 2bc1e39e0c807a..50ff7014fff342 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -51,7 +51,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 5187138be97c8e..a5e491d7a44cec 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -55,7 +55,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 38148d4f667ce6..f0dab96550c7ac 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -80,15 +80,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 + uses: github/codeql-action/init@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 + uses: github/codeql-action/autobuild@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 + uses: github/codeql-action/analyze@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0 with: category: '/language:${{ matrix.language }}' upload: False @@ -118,7 +118,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 + uses: github/codeql-action/upload-sarif@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 1ed83de53521a3..d8c075fc7a64ab 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -66,7 +66,7 @@ jobs: steps: - name: Set up Ruby & MSYS2 - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: ${{ matrix.baseruby }} diff --git a/.github/workflows/rjit-bindgen.yml b/.github/workflows/rjit-bindgen.yml index 65f7b080a3d678..b77d79e83ab2e4 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -47,7 +47,7 @@ jobs: steps: - name: Set up Ruby - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.1' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 69b31e6b698783..99f85235ad6f04 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v2.1.27 + uses: github/codeql-action/upload-sarif@df5a14dc28094dc936e103b37d749c6628682b60 # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index f4d8c378373eb0..bb4bf8441f85e5 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -47,7 +47,7 @@ jobs: steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 42b485700d3838..8f70fcfaaf6d35 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -67,7 +67,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 5ec4bfafaed22e..1e0a0574bea660 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -100,7 +100,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 56e2711b5b9d5d..e1a18297551bd9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -91,7 +91,7 @@ jobs: ${{ steps.find-tools.outputs.needs }} if: ${{ steps.find-tools.outputs.needs != '' }} - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 92ff9eadaed607..dcf7e1874af78f 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -145,7 +145,7 @@ jobs: if: ${{ matrix.rust_version }} run: rustup install ${{ matrix.rust_version }} --profile minimal - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0 with: ruby-version: '3.0' bundler: none diff --git a/NEWS.md b/NEWS.md index 34fa51ce9d051a..f3368421a9a10e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,10 @@ Note: We're only listing outstanding class updates. * Exception#set_backtrace now accepts arrays of `Thread::Backtrace::Location`. `Kernel#raise`, `Thread#raise` and `Fiber#raise` also accept this new format. [[Feature #13557]] +* Range + + * Range#size now raises TypeError if the range is not iterable. [[Misc #18984]] + ## Stdlib updates The following default gems are updated. @@ -47,7 +51,7 @@ The following default gems are updated. * optparse 0.5.0 * prism 0.25.0 * rdoc 6.6.3.1 -* reline 0.5.1 +* reline 0.5.2 * resolv 0.4.0 * stringio 3.1.1 * strscan 3.1.1 @@ -118,11 +122,17 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log a warning on verbose mode (`-w`). [[Feature #15554]] +* Redefining some core methods that are specially optimized by the interpeter + and JIT like `String.freeze` or `Integer#+` now emits a performance class + warning (`-W:performance` or `Warning[:performance] = true`). + [[Feature #20429]] + [Feature #13557]: https://bugs.ruby-lang.org/issues/13557 [Feature #15554]: https://bugs.ruby-lang.org/issues/15554 [Feature #16495]: https://bugs.ruby-lang.org/issues/16495 [Feature #18290]: https://bugs.ruby-lang.org/issues/18290 [Feature #18980]: https://bugs.ruby-lang.org/issues/18980 +[Misc #18984]: https://bugs.ruby-lang.org/issues/18984 [Feature #19117]: https://bugs.ruby-lang.org/issues/19117 [Bug #19918]: https://bugs.ruby-lang.org/issues/19918 [Bug #20064]: https://bugs.ruby-lang.org/issues/20064 diff --git a/class.c b/class.c index ada95dde1a57d5..36736a6091dcff 100644 --- a/class.c +++ b/class.c @@ -2335,7 +2335,7 @@ rb_freeze_singleton_class(VALUE x) VALUE klass = RBASIC_CLASS(x); if (klass && // no class when hidden from ObjectSpace FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) { - OBJ_FREEZE_RAW(klass); + OBJ_FREEZE(klass); } } } diff --git a/common.mk b/common.mk index 3fce9df761bf80..77aa8b04ffee8b 100644 --- a/common.mk +++ b/common.mk @@ -105,7 +105,6 @@ PRISM_FILES = prism/api_node.$(OBJEXT) \ prism/util/pm_list.$(OBJEXT) \ prism/util/pm_memchr.$(OBJEXT) \ prism/util/pm_newline_list.$(OBJEXT) \ - prism/util/pm_state_stack.$(OBJEXT) \ prism/util/pm_string.$(OBJEXT) \ prism/util/pm_string_list.$(OBJEXT) \ prism/util/pm_strncasecmp.$(OBJEXT) \ @@ -227,6 +226,11 @@ srcs: $(srcdir)/lib/prism/node.rb $(srcdir)/lib/prism/node.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/node.rb.erb $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/node.rb $(srcdir)/lib/prism/node.rb +main: $(srcdir)/lib/prism/reflection.rb +srcs: $(srcdir)/lib/prism/reflection.rb +$(srcdir)/lib/prism/reflection.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/reflection.rb.erb + $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/reflection.rb $(srcdir)/lib/prism/reflection.rb + main: $(srcdir)/lib/prism/serialize.rb srcs: $(srcdir)/lib/prism/serialize.rb $(srcdir)/lib/prism/serialize.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/serialize.rb.erb @@ -337,7 +341,7 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ $(top_srcdir)/yjit/src/lib.rs -all: $(SHOWFLAGS) main docs +all: $(SHOWFLAGS) main main: $(SHOWFLAGS) exts $(ENCSTATIC:static=lib)encs @$(NULLCMD) @@ -479,7 +483,7 @@ docs: srcs-doc $(DOCTARGETS) pkgconfig-data: $(ruby_pc) $(ruby_pc): $(srcdir)/template/ruby.pc.in config.status -install-all: docs pre-install-all do-install-all post-install-all +install-all: pre-install-all docs do-install-all post-install-all pre-install-all:: all pre-install-local pre-install-ext pre-install-gem pre-install-doc do-install-all: pre-install-all $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=all $(INSTALL_DOC_OPTS) @@ -2267,7 +2271,6 @@ ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -2704,7 +2707,6 @@ builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -3339,7 +3341,6 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -3804,7 +3805,6 @@ cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -6779,7 +6779,6 @@ eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7260,7 +7259,6 @@ gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7517,7 +7515,6 @@ goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7761,7 +7758,6 @@ hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -8800,7 +8796,6 @@ iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -9057,7 +9052,6 @@ load.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -load.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -10388,7 +10382,6 @@ miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -11976,7 +11969,6 @@ prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12172,7 +12164,6 @@ prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12382,7 +12373,6 @@ prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12570,7 +12560,6 @@ prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12596,7 +12585,6 @@ prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/prettyprint.$(OBJEXT): {$(VPATH)}prism/ast.h @@ -12619,7 +12607,6 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12641,7 +12628,6 @@ prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12663,7 +12649,6 @@ prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12685,7 +12670,6 @@ prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/static_literals.$(OBJEXT): {$(VPATH)}prism/ast.h @@ -12727,16 +12711,12 @@ prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.c prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/util/pm_memchr.$(OBJEXT): {$(VPATH)}prism/ast.h prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.c prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.c -prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.c prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h @@ -12757,7 +12737,6 @@ prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.c @@ -12784,7 +12763,6 @@ prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -13001,7 +12979,6 @@ proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -15485,7 +15462,6 @@ rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -15739,7 +15715,6 @@ rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -16019,7 +15994,6 @@ ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -18461,7 +18435,6 @@ thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -19723,7 +19696,6 @@ vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -19982,7 +19954,6 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -20213,7 +20184,6 @@ vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -20655,7 +20625,6 @@ vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -21097,7 +21066,6 @@ yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h diff --git a/compile.c b/compile.c index 791620aaa25578..2a4eb70122d912 100644 --- a/compile.c +++ b/compile.c @@ -1999,6 +1999,19 @@ iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, return arg_size; } +static void +iseq_set_use_block(rb_iseq_t *iseq) +{ + struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); + if (!body->param.flags.use_block) { + body->param.flags.use_block = 1; + + rb_vm_t *vm = GET_VM(); + st_data_t key = (st_data_t)rb_intern_str(body->location.label); // String -> ID + st_insert(vm->unused_block_warning_table, key, 1); + } +} + static int iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *const node_args) { @@ -2100,7 +2113,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons if (block_id) { body->param.block_start = arg_size++; body->param.flags.has_block = TRUE; - body->param.flags.use_block = 1; + iseq_set_use_block(iseq); } iseq_calc_param_size(iseq); @@ -5921,7 +5934,7 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, ADD_INSN(ret, line_node, putnil); ADD_INSN3(ret, line_node, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); - ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); return; case NODE_BACK_REF: @@ -8637,7 +8650,7 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node) ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_INLINE_BLOCK; } else if (strcmp(RSTRING_PTR(string), "use_block") == 0) { - ISEQ_BODY(iseq)->param.flags.use_block = 1; + iseq_set_use_block(iseq); } else { goto unknown_arg; @@ -9486,7 +9499,7 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } if (use_block && parent_block == NULL) { - ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); } flag |= VM_CALL_SUPER | VM_CALL_FCALL; @@ -9532,7 +9545,7 @@ compile_yield(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i ADD_SEQ(ret, args); ADD_INSN1(ret, node, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), flag, keywords, FALSE)); - ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); if (popped) { ADD_INSN(ret, node, pop); @@ -10014,7 +10027,7 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa case NODE_ZLIST:{ VALUE lit = rb_ary_new(); - OBJ_FREEZE_RAW(lit); + OBJ_FREEZE(lit); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); *value_p = lit; @@ -11766,6 +11779,10 @@ rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE misc, VALUE locals, VALUE params, ISEQ_BODY(iseq)->param.flags.ambiguous_param0 = TRUE; } + if (Qtrue == rb_hash_aref(params, SYM(use_block))) { + ISEQ_BODY(iseq)->param.flags.use_block = TRUE; + } + if (int_param(&i, params, SYM(kwrest))) { struct rb_iseq_param_keyword *keyword = (struct rb_iseq_param_keyword *)ISEQ_BODY(iseq)->param.keyword; if (keyword == NULL) { diff --git a/complex.c b/complex.c index 4e2bff5df427aa..ff278f80fae3a6 100644 --- a/complex.c +++ b/complex.c @@ -397,7 +397,7 @@ nucomp_s_new_internal(VALUE klass, VALUE real, VALUE imag) RCOMPLEX_SET_REAL(obj, real); RCOMPLEX_SET_IMAG(obj, imag); - OBJ_FREEZE_RAW((VALUE)obj); + OBJ_FREEZE((VALUE)obj); return (VALUE)obj; } @@ -1717,7 +1717,7 @@ nucomp_loader(VALUE self, VALUE a) RCOMPLEX_SET_REAL(dat, rb_ivar_get(a, id_i_real)); RCOMPLEX_SET_IMAG(dat, rb_ivar_get(a, id_i_imag)); - OBJ_FREEZE_RAW(self); + OBJ_FREEZE(self); return self; } diff --git a/cont.c b/cont.c index f7a4863f2cd270..2cbabba48900fc 100644 --- a/cont.c +++ b/cont.c @@ -3227,7 +3227,13 @@ rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass) static VALUE fiber_raise(rb_fiber_t *fiber, VALUE exception) { - if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) { + if (fiber == fiber_current()) { + rb_exc_raise(exception); + } + else if (fiber->resuming_fiber) { + return fiber_raise(fiber->resuming_fiber, exception); + } + else if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) { return fiber_transfer_kw(fiber, -1, &exception, RB_NO_KEYWORDS); } else { diff --git a/defs/gmake.mk b/defs/gmake.mk index 40a4750c733e5f..e502b9f83e19c3 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -193,7 +193,7 @@ $(SCRIPTBINDIR): $(Q) mkdir $@ .PHONY: commit -COMMIT_PREPARE := $(filter-out commit do-commit,$(MAKECMDGOALS)) up +COMMIT_PREPARE := $(subst :,\:,$(filter-out commit do-commit,$(MAKECMDGOALS))) up commit: pre-commit $(DOT_WAIT) do-commit $(DOT_WAIT) post_commit pre-commit: $(COMMIT_PREPARE) diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 2991385b94a6e6..4dabaca840e47f 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -96,11 +96,20 @@ 5. Build Ruby: ``` shell - make install + make ``` 6. [Run tests](testing_ruby.md) to confirm your build succeeded. +7. Install Ruby: + + ``` shell + make install + ``` + + - If you need to run `make install` with `sudo` and want to avoid document generation with different permissions, you can use + `make SUDO=sudo install`. + ### Unexplainable Build Errors If you are having unexplainable build errors, after saving all your work, try running `git clean -xfd` in the source root to remove all git ignored local files. If you are working from a source directory that's been updated several times, you may have temporary build artifacts from previous releases which can cause build failures. diff --git a/enumerator.c b/enumerator.c index 989fdb34263d0f..193a865dbc5536 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3536,10 +3536,19 @@ static VALUE enum_product_total_size(VALUE enums) { VALUE total = INT2FIX(1); + VALUE sizes = rb_ary_hidden_new(RARRAY_LEN(enums)); long i; for (i = 0; i < RARRAY_LEN(enums); i++) { VALUE size = enum_size(RARRAY_AREF(enums, i)); + if (size == INT2FIX(0)) { + rb_ary_resize(sizes, 0); + return size; + } + rb_ary_push(sizes, size); + } + for (i = 0; i < RARRAY_LEN(sizes); i++) { + VALUE size = RARRAY_AREF(sizes, i); if (NIL_P(size) || (RB_TYPE_P(size, T_FLOAT) && isinf(NUM2DBL(size)))) { return size; diff --git a/ext/pathname/extconf.rb b/ext/pathname/extconf.rb index 84e68277aae84e..b4e1617b9e6b7f 100644 --- a/ext/pathname/extconf.rb +++ b/ext/pathname/extconf.rb @@ -1,4 +1,3 @@ # frozen_string_literal: false require 'mkmf' -have_func("rb_file_s_birthtime") create_makefile('pathname') diff --git a/ext/pathname/pathname.c b/ext/pathname/pathname.c index 878f216fb5cad7..cdecb3f897ee68 100644 --- a/ext/pathname/pathname.c +++ b/ext/pathname/pathname.c @@ -479,7 +479,6 @@ path_atime(VALUE self) return rb_funcall(rb_cFile, id_atime, 1, get_strpath(self)); } -#if defined(HAVE_RB_FILE_S_BIRTHTIME) /* * call-seq: * pathname.birthtime -> time @@ -494,10 +493,6 @@ path_birthtime(VALUE self) { return rb_funcall(rb_cFile, id_birthtime, 1, get_strpath(self)); } -#else -/* check at compilation time for `respond_to?` */ -# define path_birthtime rb_f_notimplement -#endif /* * call-seq: diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index f0e5ee4e85ff09..820b8228a31fb7 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1623,10 +1623,9 @@ strio_read(int argc, VALUE *argv, VALUE self) if (len > rest) len = rest; rb_str_resize(str, len); MEMCPY(RSTRING_PTR(str), RSTRING_PTR(ptr->string) + ptr->pos, char, len); - if (binary) - rb_enc_associate(str, rb_ascii8bit_encoding()); - else + if (!binary) { rb_enc_copy(str, ptr->string); + } } ptr->pos += RSTRING_LEN(str); return str; diff --git a/gc.c b/gc.c index 6655d62fa89855..4bf80ff3361cdd 100644 --- a/gc.c +++ b/gc.c @@ -21,8 +21,6 @@ #include -#define sighandler_t ruby_sighandler_t - #ifndef _WIN32 #include #include @@ -800,7 +798,7 @@ enum gc_mode { }; typedef struct rb_global_space { - VALUE next_object_id; + unsigned long long next_object_id; rb_ractor_t *prev_id_assigner; rb_nativethread_lock_t next_object_id_lock; @@ -1659,6 +1657,7 @@ total_freed_objects(rb_objspace_t *objspace) #define gc_mode(objspace) gc_mode_verify((enum gc_mode)(objspace)->flags.mode) #define gc_mode_set(objspace, m) ((objspace)->flags.mode = (unsigned int)gc_mode_verify(m)) +#define gc_needs_major_flags objspace->rgengc.need_major_gc #define is_marking(objspace) (gc_mode(objspace) == gc_mode_marking) #define is_sweeping(objspace) (gc_mode(objspace) == gc_mode_sweeping) @@ -3489,7 +3488,7 @@ heap_prepare(rb_objspace_t *objspace, rb_size_pool_t *size_pool, rb_heap_t *heap * sweeping and still don't have a free page, then * gc_sweep_finish_size_pool should allow us to create a new page. */ if (heap->free_pages == NULL && !heap_increment(objspace, size_pool, heap)) { - if (objspace->rgengc.need_major_gc == GPR_FLAG_NONE) { + if (gc_needs_major_flags == GPR_FLAG_NONE) { rb_bug("cannot create a new page after GC"); } else { // Major GC is required, which will allow us to create new page @@ -5233,7 +5232,7 @@ rb_global_space_t * rb_global_space_init(void) { rb_global_space_t *global_space = calloc1(sizeof(rb_global_space_t)); - global_space->next_object_id = INT2FIX(OBJ_ID_INITIAL); + global_space->next_object_id = OBJ_ID_INITIAL; global_space->prev_id_assigner = NULL; rb_nativethread_lock_initialize(&global_space->next_object_id_lock); rb_nativethread_lock_initialize(&global_space->absorbed_thread_tbl_lock); @@ -6492,7 +6491,7 @@ id2ref(VALUE objid) rb_global_space_t *global_space = &rb_global_space; rb_native_mutex_lock(&global_space->next_object_id_lock); - VALUE not_id_value = rb_int_ge(objid, global_space->next_object_id); + VALUE not_id_value = rb_int_ge(objid, ULL2NUM(global_space->next_object_id)); rb_native_mutex_unlock(&global_space->next_object_id_lock); if (not_id_value) { @@ -6540,8 +6539,8 @@ cached_object_id(VALUE obj) rb_global_space_t *global_space = &rb_global_space; rb_native_mutex_lock(&global_space->next_object_id_lock); - id = global_space->next_object_id; - global_space->next_object_id = rb_int_plus(id, INT2FIX(OBJ_ID_INCREMENT)); + id = ULL2NUM(global_space->next_object_id); + global_space->next_object_id += OBJ_ID_INCREMENT; global_space->prev_id_assigner = rb_current_allocating_ractor(); rb_native_mutex_unlock(&global_space->next_object_id_lock); @@ -7415,10 +7414,7 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context sweep_page->size_pool->total_freed_objects += ctx->freed_slots; if (heap_pages_deferred_final && !finalizing) { - rb_thread_t *th = GET_THREAD(); - if (th) { - gc_finalize_deferred_register(objspace); - } + gc_finalize_deferred_register(objspace); } #if RGENGC_CHECK_MODE @@ -7593,7 +7589,7 @@ gc_sweep_finish_size_pool(rb_objspace_t *objspace, rb_size_pool_t *size_pool) grow_heap = TRUE; } else if (is_growth_heap) { /* Only growth heaps are allowed to start a major GC. */ - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_NOFREE; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; size_pool->force_major_gc_count++; } } @@ -9750,11 +9746,6 @@ gc_mark_roots(rb_objspace_t *objspace, const char **categoryp) rb_global_space_t *global_space = &rb_global_space; MARK_CHECKPOINT("object_id"); - rb_native_mutex_lock(&global_space->next_object_id_lock); - if (global_space->prev_id_assigner == objspace->ractor) { - rb_gc_mark(global_space->next_object_id); - } - rb_native_mutex_unlock(&global_space->next_object_id_lock); rb_native_mutex_lock(&objspace->obj_id_lock); mark_tbl_no_pin(objspace, objspace->obj_to_id_tbl); /* Only mark ids */ rb_native_mutex_unlock(&objspace->obj_id_lock); @@ -10671,7 +10662,7 @@ gc_marks_finish(rb_objspace_t *objspace) } else { gc_report(1, objspace, "gc_marks_finish: next is full GC!!)\n"); - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_NOFREE; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; } } } @@ -10696,20 +10687,20 @@ gc_marks_finish(rb_objspace_t *objspace) } if (objspace->rgengc.uncollectible_wb_unprotected_objects > objspace->rgengc.uncollectible_wb_unprotected_objects_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_SHADY; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_SHADY; } if (objspace->rgengc.old_objects > objspace->rgengc.old_objects_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_OLDGEN; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDGEN; } if (RGENGC_FORCE_MAJOR_GC) { - objspace->rgengc.need_major_gc = GPR_FLAG_MAJOR_BY_FORCE; + gc_needs_major_flags = GPR_FLAG_MAJOR_BY_FORCE; } const char *next_gc; if (global_space->rglobalgc.need_global_gc) { next_gc = "global"; } - else if (objspace->rgengc.need_major_gc) { + else if (gc_needs_major_flags) { next_gc = "major"; } else { @@ -11747,7 +11738,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) #if RGENGC_ESTIMATE_OLDMALLOC if (!full_mark) { if (objspace->rgengc.oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_OLDMALLOC; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC; objspace->rgengc.oldmalloc_increase_limit = (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor); @@ -11758,7 +11749,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRIuSIZE"\t%"PRIuSIZE"\t%"PRIdSIZE"\n", rb_gc_count(), - objspace->rgengc.need_major_gc, + gc_needs_major_flags, objspace->rgengc.oldmalloc_increase, objspace->rgengc.oldmalloc_increase_limit, gc_params.oldmalloc_limit_max); @@ -11919,8 +11910,8 @@ gc_set_flags_finish(rb_objspace_t *objspace, unsigned int reason, unsigned int * objspace->flags.immediate_sweep = !(flag & (1<rgengc.need_major_gc) { - reason |= objspace->rgengc.need_major_gc; + if (gc_needs_major_flags) { + reason |= gc_needs_major_flags; *do_full_mark = TRUE; } else if (RGENGC_FORCE_MAJOR_GC) { @@ -11928,7 +11919,7 @@ gc_set_flags_finish(rb_objspace_t *objspace, unsigned int reason, unsigned int * *do_full_mark = TRUE; } - objspace->rgengc.need_major_gc = GPR_FLAG_NONE; + gc_needs_major_flags = GPR_FLAG_NONE; if (*do_full_mark && (reason & GPR_FLAG_MAJOR_MASK) == 0) { reason |= GPR_FLAG_MAJOR_BY_FORCE; /* GC by CAPI, METHOD, and so on. */ @@ -12074,10 +12065,8 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) static void gc_rest(rb_objspace_t *objspace) { - int marking = is_incremental_marking(objspace); - int sweeping = is_lazy_sweeping(objspace); - - if (marking || sweeping) { + if (is_incremental_marking(objspace) || is_lazy_sweeping(objspace)) { + unsigned int lock_lev; LOCAL_GC_BEGIN(objspace); { gc_enter(objspace, gc_enter_event_rest); @@ -12572,12 +12561,11 @@ gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_NONE: - case T_NIL: case T_MOVED: case T_ZOMBIE: return FALSE; case T_SYMBOL: - if (DYNAMIC_SYM_P(obj) && (RSYMBOL(obj)->id & ~ID_SCOPE_MASK)) { + if (RSYMBOL(obj)->id & ~ID_SCOPE_MASK) { return FALSE; } /* fall through */ @@ -13884,7 +13872,7 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned SET(major_by, major_by); if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */ - unsigned int need_major_flags = objspace->rgengc.need_major_gc; + unsigned int need_major_flags = gc_needs_major_flags; need_major_by = (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree : (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen : diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index eb212db7dc9555..0a05166784b4e0 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -905,6 +905,10 @@ RB_OBJ_FROZEN(VALUE obj) } } +RUBY_SYMBOL_EXPORT_BEGIN +void rb_obj_freeze_inline(VALUE obj); +RUBY_SYMBOL_EXPORT_END + RBIMPL_ATTR_ARTIFICIAL() /** * This is an implementation detail of RB_OBJ_FREEZE(). 3rd parties need not @@ -915,14 +919,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline void RB_OBJ_FREEZE_RAW(VALUE obj) { - RB_FL_SET_RAW(obj, RUBY_FL_FREEZE); - if (TYPE(obj) == T_STRING) { - RB_FL_UNSET_RAW(obj, FL_USER3); // STR_CHILLED - } + rb_obj_freeze_inline(obj); } -RUBY_SYMBOL_EXPORT_BEGIN -void rb_obj_freeze_inline(VALUE obj); -RUBY_SYMBOL_EXPORT_END - #endif /* RBIMPL_FL_TYPE_H */ diff --git a/iseq.c b/iseq.c index 7b3fc1a3743d7e..9bc8fb6c61cbcc 100644 --- a/iseq.c +++ b/iseq.c @@ -3204,6 +3204,7 @@ iseq_data_to_ary(const rb_iseq_t *iseq) } if (iseq_body->param.flags.has_kwrest) rb_hash_aset(params, ID2SYM(rb_intern("kwrest")), INT2FIX(keyword->rest_start)); if (iseq_body->param.flags.ambiguous_param0) rb_hash_aset(params, ID2SYM(rb_intern("ambiguous_param0")), Qtrue); + if (iseq_body->param.flags.use_block) rb_hash_aset(params, ID2SYM(rb_intern("use_block")), Qtrue); } /* body */ diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index e61c1ad231b297..a340463c9844e5 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -143,18 +143,8 @@ def self.build_message(gem) # Additionally, we need to skip Bootsnap and Zeitwerk if present, these # gems decorate Kernel#require, so they are not really the ones issuing # the require call users should be warned about. Those are upwards. - frames_to_skip = 2 - location = nil - Thread.each_caller_location do |cl| - if frames_to_skip >= 1 - frames_to_skip -= 1 - next - end - - if cl.base_label != "require" - location = cl.path - break - end + location = Thread.each_caller_location(2) do |cl| + break cl.path unless cl.base_label == "require" end if location && File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR) diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 96e1403bf7f1da..2933d284500668 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -65,7 +65,7 @@ def add_extra_platforms!(platforms) platforms.concat(new_platforms) - less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform } + less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform } platforms.delete(Bundler.local_platform) if less_specific_platform platforms diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index ff74b5fb35438b..b078b482375711 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -50,25 +50,6 @@ def initialize(irb_context) attr_reader :irb_context - def unwrap_string_literal(str) - return if str.empty? - - sexp = Ripper.sexp(str) - if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - @irb_context.workspace.binding.eval(str).to_s - else - str - end - end - - def ruby_args(arg) - # Use throw and catch to handle arg that includes `;` - # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] - catch(:EXTRACT_RUBY_ARGS) do - @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" - end || [[], {}] - end - def execute(arg) #nop end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 3c4a54e5e2fa64..cb7e0c4873b3a3 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -8,6 +8,8 @@ module IRB module Command class Edit < Base + include RubyArgsExtractor + category "Misc" description 'Open a file or source location.' help_message <<~HELP_MESSAGE diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb new file mode 100644 index 00000000000000..249b5cdedeb080 --- /dev/null +++ b/lib/irb/command/internal_helpers.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module IRB + module Command + # Internal use only, for default command's backward compatibility. + module RubyArgsExtractor # :nodoc: + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + end + end +end diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 33e327f4a93838..1cd3f279d1003e 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -10,6 +10,7 @@ module IRB module Command class LoaderCommand < Base + include RubyArgsExtractor include IrbLoader def raise_cmd_argument_error diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index f6b19648640489..cbd9998bc4e601 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -11,6 +11,8 @@ module IRB module Command class Ls < Base + include RubyArgsExtractor + category "Context" description "Show methods, constants, and variables." diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index 70dc69cdec5ef5..f96be20de86bbd 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -3,6 +3,8 @@ module IRB module Command class Measure < Base + include RubyArgsExtractor + category "Misc" description "`measure` enables the mode to measure processing time. `measure :off` disables it." diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index f9393cd3b5cab9..8a2188e4ebc365 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,6 +3,8 @@ module IRB module Command class ShowDoc < Base + include RubyArgsExtractor + category "Context" description "Look up documentation with RI." diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index c4c8fc004121c2..f4c6f104a2d577 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -7,6 +7,8 @@ module IRB module Command class ShowSource < Base + include RubyArgsExtractor + category "Context" description "Show the source code of a given method, class/module, or constant." diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 24428a5c130ed7..138d61c9307228 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,6 +9,8 @@ module IRB module Command class MultiIRBCommand < Base + include RubyArgsExtractor + private def print_deprecated_warning diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 6025b0547da0be..d680655feed9f0 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "command" +require_relative "command/internal_helpers" require_relative "command/context" require_relative "command/exit" require_relative "command/force_exit" diff --git a/lib/prism.rb b/lib/prism.rb index 23a72fa49a334b..c512cb40158359 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -24,6 +24,7 @@ module Prism autoload :NodeInspector, "prism/node_inspector" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" + autoload :Reflection, "prism/reflection" autoload :Serialize, "prism/serialize" autoload :Translation, "prism/translation" autoload :Visitor, "prism/visitor" @@ -64,22 +65,6 @@ def self.lex_ripper(source) def self.load(source, serialized) Serialize.load(source, serialized) end - - # :call-seq: - # Prism::parse_failure?(source, **options) -> bool - # - # Returns true if the source parses with errors. - def self.parse_failure?(source, **options) - !parse_success?(source, **options) - end - - # :call-seq: - # Prism::parse_file_failure?(filepath, **options) -> bool - # - # Returns true if the file at filepath parses with errors. - def self.parse_file_failure?(filepath, **options) - !parse_file_success?(filepath, **options) - end end require_relative "prism/node" diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 0a064a5c941850..1fd053f9020448 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -286,12 +286,22 @@ def parse_success?(code, **options) LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) } end + # Mirror the Prism.parse_failure? API by using the serialization API. + def parse_failure?(code, **options) + !parse_success?(code, **options) + end + # Mirror the Prism.parse_file_success? API by using the serialization API. def parse_file_success?(filepath, **options) options[:filepath] = filepath LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) } end + # Mirror the Prism.parse_file_failure? API by using the serialization API. + def parse_file_failure?(filepath, **options) + !parse_file_success?(filepath, **options) + end + private def dump_common(string, options) # :nodoc: diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 6cf28460c2d5af..e55cac6df6d21b 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -63,7 +63,6 @@ Gem::Specification.new do |spec| "include/prism/util/pm_list.h", "include/prism/util/pm_memchr.h", "include/prism/util/pm_newline_list.h", - "include/prism/util/pm_state_stack.h", "include/prism/util/pm_strncasecmp.h", "include/prism/util/pm_string.h", "include/prism/util/pm_string_list.h", @@ -88,6 +87,7 @@ Gem::Specification.new do |spec| "lib/prism/parse_result/newlines.rb", "lib/prism/pattern.rb", "lib/prism/polyfill/string.rb", + "lib/prism/reflection.rb", "lib/prism/serialize.rb", "lib/prism/translation.rb", "lib/prism/translation/parser.rb", @@ -117,7 +117,6 @@ Gem::Specification.new do |spec| "src/util/pm_list.c", "src/util/pm_memchr.c", "src/util/pm_newline_list.c", - "src/util/pm_state_stack.c", "src/util/pm_string.c", "src/util/pm_string_list.c", "src/util/pm_strncasecmp.c", @@ -136,6 +135,7 @@ Gem::Specification.new do |spec| "sig/prism/pack.rbs", "sig/prism/parse_result.rbs", "sig/prism/pattern.rbs", + "sig/prism/reflection.rbs", "sig/prism/serialize.rbs", "sig/prism/visitor.rbs", "rbi/prism.rbi", @@ -145,7 +145,11 @@ Gem::Specification.new do |spec| "rbi/prism/node_ext.rbi", "rbi/prism/node.rbi", "rbi/prism/parse_result.rbi", + "rbi/prism/reflection.rbi", + "rbi/prism/translation/parser.rbi", "rbi/prism/translation/parser/compiler.rbi", + "rbi/prism/translation/parser33.rbi", + "rbi/prism/translation/parser34.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/translation/ripper/ripper_compiler.rbi", "rbi/prism/translation/ruby_parser.rbi", diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index e4d7ecf1a29189..6e5ef11bc000d1 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -112,7 +112,11 @@ def set_pasting_state(in_pasting) else prompt = @prompt end - if @prompt_proc + if !@is_multiline + mode_string = check_mode_string + prompt = mode_string + prompt if mode_string + [prompt] + [''] * (buffer.size - 1) + elsif @prompt_proc prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") } prompt_list.map!{ prompt } if @vi_arg or @searching_prompt prompt_list = [prompt] if prompt_list.empty? @@ -1220,7 +1224,7 @@ def call_completion_proc_with_checking_args(pre, target, post) end def line() - current_line unless eof? + @buffer_of_lines.join("\n") unless eof? end def current_line @@ -1304,14 +1308,12 @@ def retrieve_completion_block(set_completion_quote_character = false) end target = before end - if @is_multiline - lines = whole_lines - if @line_index > 0 - preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing - end - if (lines.size - 1) > @line_index - postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") - end + lines = whole_lines + if @line_index > 0 + preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing + end + if (lines.size - 1) > @line_index + postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") end [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)] end @@ -1333,20 +1335,16 @@ def insert_text(text) def delete_text(start = nil, length = nil) if start.nil? and length.nil? - if @is_multiline - if @buffer_of_lines.size == 1 - @buffer_of_lines[@line_index] = '' - @byte_pointer = 0 - elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 - @buffer_of_lines.pop - @line_index -= 1 - @byte_pointer = 0 - elsif @line_index < (@buffer_of_lines.size - 1) - @buffer_of_lines.delete_at(@line_index) - @byte_pointer = 0 - end - else - set_current_line('', 0) + if @buffer_of_lines.size == 1 + @buffer_of_lines[@line_index] = '' + @byte_pointer = 0 + elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 + @buffer_of_lines.pop + @line_index -= 1 + @byte_pointer = 0 + elsif @line_index < (@buffer_of_lines.size - 1) + @buffer_of_lines.delete_at(@line_index) + @byte_pointer = 0 end elsif not start.nil? and not length.nil? if current_line @@ -1502,7 +1500,7 @@ def finish byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if (@byte_pointer < current_line.bytesize) @byte_pointer += byte_size - elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 + elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 @byte_pointer = 0 @line_index += 1 end @@ -1515,7 +1513,7 @@ def finish if @byte_pointer > 0 byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size - elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 + elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 @line_index -= 1 @byte_pointer = current_line.bytesize end @@ -1543,131 +1541,95 @@ def finish end alias_method :end_of_line, :ed_move_to_end - private def generate_searcher - Fiber.new do |first_key| - prev_search_key = first_key - search_word = String.new(encoding: @encoding) - multibyte_buf = String.new(encoding: 'ASCII-8BIT') - last_hit = nil - case first_key - when "\C-r".ord - prompt_name = 'reverse-i-search' - when "\C-s".ord - prompt_name = 'i-search' + private def generate_searcher(search_key) + search_word = String.new(encoding: @encoding) + multibyte_buf = String.new(encoding: 'ASCII-8BIT') + hit_pointer = nil + lambda do |key| + search_again = false + case key + when "\C-h".ord, "\C-?".ord + grapheme_clusters = search_word.grapheme_clusters + if grapheme_clusters.size > 0 + grapheme_clusters.pop + search_word = grapheme_clusters.join + end + when "\C-r".ord, "\C-s".ord + search_again = true if search_key == key + search_key = key + else + multibyte_buf << key + if multibyte_buf.dup.force_encoding(@encoding).valid_encoding? + search_word << multibyte_buf.dup.force_encoding(@encoding) + multibyte_buf.clear + end end - loop do - key = Fiber.yield(search_word) - search_again = false - case key - when -1 # determined - Reline.last_incremental_search = search_word - break - when "\C-h".ord, "\C-?".ord - grapheme_clusters = search_word.grapheme_clusters - if grapheme_clusters.size > 0 - grapheme_clusters.pop - search_word = grapheme_clusters.join - end - when "\C-r".ord, "\C-s".ord - search_again = true if prev_search_key == key - prev_search_key = key - else - multibyte_buf << key - if multibyte_buf.dup.force_encoding(@encoding).valid_encoding? - search_word << multibyte_buf.dup.force_encoding(@encoding) - multibyte_buf.clear + hit = nil + if not search_word.empty? and @line_backup_in_history&.include?(search_word) + hit_pointer = Reline::HISTORY.size + hit = @line_backup_in_history + else + if search_again + if search_word.empty? and Reline.last_incremental_search + search_word = Reline.last_incremental_search end - end - hit = nil - if not search_word.empty? and @line_backup_in_history&.include?(search_word) - @history_pointer = nil - hit = @line_backup_in_history - else - if search_again - if search_word.empty? and Reline.last_incremental_search - search_word = Reline.last_incremental_search - end - if @history_pointer - case prev_search_key - when "\C-r".ord - history_pointer_base = 0 - history = Reline::HISTORY[0..(@history_pointer - 1)] - when "\C-s".ord - history_pointer_base = @history_pointer + 1 - history = Reline::HISTORY[(@history_pointer + 1)..-1] - end - else - history_pointer_base = 0 - history = Reline::HISTORY - end - elsif @history_pointer - case prev_search_key + if @history_pointer + case search_key when "\C-r".ord history_pointer_base = 0 - history = Reline::HISTORY[0..@history_pointer] + history = Reline::HISTORY[0..(@history_pointer - 1)] when "\C-s".ord - history_pointer_base = @history_pointer - history = Reline::HISTORY[@history_pointer..-1] + history_pointer_base = @history_pointer + 1 + history = Reline::HISTORY[(@history_pointer + 1)..-1] end else history_pointer_base = 0 history = Reline::HISTORY end - case prev_search_key + elsif @history_pointer + case search_key when "\C-r".ord - hit_index = history.rindex { |item| - item.include?(search_word) - } + history_pointer_base = 0 + history = Reline::HISTORY[0..@history_pointer] when "\C-s".ord - hit_index = history.index { |item| - item.include?(search_word) - } - end - if hit_index - @history_pointer = history_pointer_base + hit_index - hit = Reline::HISTORY[@history_pointer] + history_pointer_base = @history_pointer + history = Reline::HISTORY[@history_pointer..-1] end + else + history_pointer_base = 0 + history = Reline::HISTORY end - case prev_search_key + case search_key when "\C-r".ord - prompt_name = 'reverse-i-search' + hit_index = history.rindex { |item| + item.include?(search_word) + } when "\C-s".ord - prompt_name = 'i-search' + hit_index = history.index { |item| + item.include?(search_word) + } end - if hit - if @is_multiline - @buffer_of_lines = hit.split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - @byte_pointer = current_line.bytesize - @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] - else - @buffer_of_lines = [hit] - @byte_pointer = hit.bytesize - @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit] - end - last_hit = hit - else - if @is_multiline - @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word] - else - @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit] - end + if hit_index + hit_pointer = history_pointer_base + hit_index + hit = Reline::HISTORY[hit_pointer] end end + case search_key + when "\C-r".ord + prompt_name = 'reverse-i-search' + when "\C-s".ord + prompt_name = 'i-search' + end + prompt_name = "failed #{prompt_name}" unless hit + [search_word, prompt_name, hit_pointer] end end private def incremental_search_history(key) unless @history_pointer - if @is_multiline - @line_backup_in_history = whole_buffer - else - @line_backup_in_history = current_line - end + @line_backup_in_history = whole_buffer end - searcher = generate_searcher - searcher.resume(key) + searcher = generate_searcher(key) @searching_prompt = "(reverse-i-search)`': " termination_keys = ["\C-j".ord] termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators @@ -1679,53 +1641,41 @@ def finish else buffer = @line_backup_in_history end - if @is_multiline - @buffer_of_lines = buffer.split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - else - @buffer_of_lines = [buffer] - end + @buffer_of_lines = buffer.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = @buffer_of_lines.size - 1 @searching_prompt = nil @waiting_proc = nil @byte_pointer = 0 - searcher.resume(-1) when "\C-g".ord - if @is_multiline - @buffer_of_lines = @line_backup_in_history.split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - else - @buffer_of_lines = [@line_backup_in_history] - end - @history_pointer = nil + @buffer_of_lines = @line_backup_in_history.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = @buffer_of_lines.size - 1 + move_history(nil, line: :end, cursor: :end, save_buffer: false) @searching_prompt = nil @waiting_proc = nil - @line_backup_in_history = nil @byte_pointer = 0 else chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT) if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord - searcher.resume(k) + search_word, prompt_name, hit_pointer = searcher.call(k) + Reline.last_incremental_search = search_word + @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] + @searching_prompt += ': ' unless @is_multiline + move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer else if @history_pointer line = Reline::HISTORY[@history_pointer] else line = @line_backup_in_history end - if @is_multiline - @line_backup_in_history = whole_buffer - @buffer_of_lines = line.split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - else - @line_backup_in_history = current_line - @buffer_of_lines = [line] - end + @line_backup_in_history = whole_buffer + @buffer_of_lines = line.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = @buffer_of_lines.size - 1 @searching_prompt = nil @waiting_proc = nil @byte_pointer = 0 - searcher.resume(-1) end end } @@ -1741,191 +1691,96 @@ def finish end alias_method :forward_search_history, :vi_search_next - private def ed_search_prev_history(key, arg: 1) - history = nil - h_pointer = nil - line_no = nil - substr = current_line.slice(0, @byte_pointer) - if @history_pointer.nil? - return if not current_line.empty? and substr.empty? - history = Reline::HISTORY - elsif @history_pointer.zero? - history = nil - h_pointer = nil - else - history = Reline::HISTORY.slice(0, @history_pointer) - end - return if history.nil? - if @is_multiline - h_pointer = history.rindex { |h| - h.split("\n").each_with_index { |l, i| - if l.start_with?(substr) - line_no = i - break - end - } - not line_no.nil? - } - else - h_pointer = history.rindex { |l| - l.start_with?(substr) - } - end - return if h_pointer.nil? - @history_pointer = h_pointer - cursor = current_byte_pointer_cursor - if @is_multiline - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = line_no - calculate_nearest_cursor(cursor) - else - @buffer_of_lines = [Reline::HISTORY[@history_pointer]] - calculate_nearest_cursor(cursor) + private def search_history(prefix, pointer_range) + pointer_range.each do |pointer| + lines = Reline::HISTORY[pointer].split("\n") + lines.each_with_index do |line, index| + return [pointer, index] if line.start_with?(prefix) + end end + nil + end + + private def ed_search_prev_history(key, arg: 1) + substr = current_line.byteslice(0, @byte_pointer) + return if @history_pointer == 0 + return if @history_pointer.nil? && substr.empty? && !current_line.empty? + + history_range = 0...(@history_pointer || Reline::HISTORY.size) + h_pointer, line_index = search_history(substr, history_range.reverse_each) + return unless h_pointer + move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer) arg -= 1 ed_search_prev_history(key, arg: arg) if arg > 0 end alias_method :history_search_backward, :ed_search_prev_history private def ed_search_next_history(key, arg: 1) - substr = current_line.slice(0, @byte_pointer) - if @history_pointer.nil? - return - elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty? - return - end + substr = current_line.byteslice(0, @byte_pointer) + return if @history_pointer.nil? + + history_range = @history_pointer + 1...Reline::HISTORY.size history = Reline::HISTORY.slice((@history_pointer + 1)..-1) - h_pointer = nil - line_no = nil - if @is_multiline - h_pointer = history.index { |h| - h.split("\n").each_with_index { |l, i| - if l.start_with?(substr) - line_no = i - break - end - } - not line_no.nil? - } - else - h_pointer = history.index { |l| - l.start_with?(substr) - } - end - h_pointer += @history_pointer + 1 if h_pointer and @history_pointer + h_pointer, line_index = search_history(substr, history_range) return if h_pointer.nil? and not substr.empty? - @history_pointer = h_pointer - if @is_multiline - if @history_pointer.nil? and substr.empty? - @buffer_of_lines = [] - @line_index = 0 - @byte_pointer = 0 - else - cursor = current_byte_pointer_cursor - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @line_index = line_no - calculate_nearest_cursor(cursor) - end - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - else - if @history_pointer.nil? and substr.empty? - set_current_line('', 0) - else - set_current_line(Reline::HISTORY[@history_pointer]) - end - end + + move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer) arg -= 1 ed_search_next_history(key, arg: arg) if arg > 0 end alias_method :history_search_forward, :ed_search_next_history + private def move_history(history_pointer, line:, cursor:, save_buffer: true) + history_pointer ||= Reline::HISTORY.size + return if history_pointer < 0 || history_pointer > Reline::HISTORY.size + old_history_pointer = @history_pointer || Reline::HISTORY.size + if old_history_pointer == Reline::HISTORY.size + @line_backup_in_history = save_buffer ? whole_buffer : '' + else + Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer + end + if history_pointer == Reline::HISTORY.size + buf = @line_backup_in_history + @history_pointer = @line_backup_in_history = nil + else + buf = Reline::HISTORY[history_pointer] + @history_pointer = history_pointer + end + @buffer_of_lines = buf.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line + @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor + end + private def ed_prev_history(key, arg: 1) - if @is_multiline and @line_index > 0 + if @line_index > 0 cursor = current_byte_pointer_cursor @line_index -= 1 calculate_nearest_cursor(cursor) return end - if Reline::HISTORY.empty? - return - end - if @history_pointer.nil? - @history_pointer = Reline::HISTORY.size - 1 - cursor = current_byte_pointer_cursor - if @is_multiline - @line_backup_in_history = whole_buffer - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - calculate_nearest_cursor(cursor) - else - @line_backup_in_history = whole_buffer - @buffer_of_lines = [Reline::HISTORY[@history_pointer]] - calculate_nearest_cursor(cursor) - end - elsif @history_pointer.zero? - return - else - if @is_multiline - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer -= 1 - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = @buffer_of_lines.size - 1 - else - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer -= 1 - @buffer_of_lines = [Reline::HISTORY[@history_pointer]] - end - end - if @config.editing_mode_is?(:emacs, :vi_insert) - @byte_pointer = current_line.bytesize - elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = 0 - end + move_history( + (@history_pointer || Reline::HISTORY.size) - 1, + line: :end, + cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, + ) arg -= 1 ed_prev_history(key, arg: arg) if arg > 0 end alias_method :previous_history, :ed_prev_history private def ed_next_history(key, arg: 1) - if @is_multiline and @line_index < (@buffer_of_lines.size - 1) + if @line_index < (@buffer_of_lines.size - 1) cursor = current_byte_pointer_cursor @line_index += 1 calculate_nearest_cursor(cursor) return end - if @history_pointer.nil? - return - elsif @history_pointer == (Reline::HISTORY.size - 1) - if @is_multiline - @history_pointer = nil - @buffer_of_lines = @line_backup_in_history.split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = 0 - else - @history_pointer = nil - @buffer_of_lines = [@line_backup_in_history] - end - else - if @is_multiline - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer += 1 - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = 0 - else - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer += 1 - @buffer_of_lines = [Reline::HISTORY[@history_pointer]] - end - end - if @config.editing_mode_is?(:emacs, :vi_insert) - @byte_pointer = current_line.bytesize - elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = 0 - end + move_history( + (@history_pointer || Reline::HISTORY.size) + 1, + line: :start, + cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, + ) arg -= 1 ed_next_history(key, arg: arg) if arg > 0 end @@ -1956,17 +1811,13 @@ def finish end end else - if @history_pointer - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer = nil - end finish end end private def em_delete_prev_char(key, arg: 1) arg.times do - if @is_multiline and @byte_pointer == 0 and @line_index > 0 + if @byte_pointer == 0 and @line_index > 0 @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) @line_index -= 1 @@ -1990,7 +1841,7 @@ def finish line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer) set_current_line(line, line.bytesize) @kill_ring.append(deleted) - elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 + elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end @@ -2030,7 +1881,7 @@ def finish alias_method :kill_whole_line, :em_kill_line private def em_delete(key) - if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord + if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord @eof = true finish elsif @byte_pointer < current_line.bytesize @@ -2038,7 +1889,7 @@ def finish mbchar = splitted_last.grapheme_clusters.first line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize) set_current_line(line) - elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 + elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end @@ -2281,7 +2132,7 @@ def finish end private def vi_delete_prev_char(key) - if @is_multiline and @byte_pointer == 0 and @line_index > 0 + if @byte_pointer == 0 and @line_index > 0 @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) @line_index -= 1 @@ -2378,7 +2229,7 @@ def finish end private def vi_list_or_eof(key) - if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1) + if current_line.empty? and @buffer_of_lines.size == 1 set_current_line('', 0) @eof = true finish @@ -2409,36 +2260,18 @@ def finish if Reline::HISTORY.empty? return end - if @history_pointer.nil? - @history_pointer = 0 - @line_backup_in_history = current_line - set_current_line(Reline::HISTORY[@history_pointer], 0) - elsif @history_pointer.zero? - return - else - Reline::HISTORY[@history_pointer] = current_line - @history_pointer = 0 - set_current_line(Reline::HISTORY[@history_pointer], 0) - end + move_history(0, line: :start, cursor: :start) end private def vi_histedit(key) path = Tempfile.open { |fp| - if @is_multiline - fp.write whole_lines.join("\n") - else - fp.write current_line - end + fp.write whole_lines.join("\n") fp.path } system("#{ENV['EDITOR']} #{path}") - if @is_multiline - @buffer_of_lines = File.read(path).split("\n") - @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line_index = 0 - else - @buffer_of_lines = File.read(path).split("\n") - end + @buffer_of_lines = File.read(path).split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = 0 finish end @@ -2610,7 +2443,7 @@ def finish end private def vi_join_lines(key, arg: 1) - if @is_multiline and @buffer_of_lines.size > @line_index + 1 + if @buffer_of_lines.size > @line_index + 1 next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip set_current_line(current_line + ' ' + next_line, current_line.bytesize) end @@ -2630,4 +2463,12 @@ def finish @mark_pointer = new_pointer end alias_method :exchange_point_and_mark, :em_exchange_mark + + private def emacs_editing_mode(key) + @config.editing_mode = :emacs + end + + private def vi_editing_mode(key) + @config.editing_mode = :vi_insert + end end diff --git a/lib/reline/version.rb b/lib/reline/version.rb index acb5e279b8cc77..e6f370c6ec1438 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.5.1' + VERSION = '0.5.2' end diff --git a/node.c b/node.c index 8a6b55b0b5808a..7706f71fdaeb44 100644 --- a/node.c +++ b/node.c @@ -81,7 +81,6 @@ rb_node_buffer_new(void) #define xfree ast->node_buffer->config->free #define rb_xmalloc_mul_add ast->node_buffer->config->xmalloc_mul_add #define ruby_xrealloc(var,size) (ast->node_buffer->config->realloc_n((void *)var, 1, size)) -#define rb_gc_mark_and_move ast->node_buffer->config->gc_mark_and_move #endif typedef void node_itr_t(rb_ast_t *ast, void *ctx, NODE *node); diff --git a/prism/config.yml b/prism/config.yml index a0abb98ef7e3a8..8551c95b1f6c23 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -104,6 +104,14 @@ errors: - EXPECT_STRING_CONTENT - EXPECT_WHEN_DELIMITER - EXPRESSION_BARE_HASH + - EXPRESSION_NOT_WRITABLE + - EXPRESSION_NOT_WRITABLE_ENCODING + - EXPRESSION_NOT_WRITABLE_FALSE + - EXPRESSION_NOT_WRITABLE_FILE + - EXPRESSION_NOT_WRITABLE_LINE + - EXPRESSION_NOT_WRITABLE_NIL + - EXPRESSION_NOT_WRITABLE_SELF + - EXPRESSION_NOT_WRITABLE_TRUE - FLOAT_PARSE - FOR_COLLECTION - FOR_IN diff --git a/prism/extension.c b/prism/extension.c index 27e799a8da645e..807c8f69dc2da9 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -969,7 +969,7 @@ parse_input_success_p(pm_string_t *input, const pm_options_t *options) { /** * call-seq: - * Prism::parse_success?(source, **options) -> Array + * Prism::parse_success?(source, **options) -> bool * * Parse the given string and return true if it parses without errors. For * supported options, see Prism::parse. @@ -989,7 +989,19 @@ parse_success_p(int argc, VALUE *argv, VALUE self) { /** * call-seq: - * Prism::parse_file_success?(filepath, **options) -> Array + * Prism::parse_failure?(source, **options) -> bool + * + * Parse the given string and return true if it parses with errors. For + * supported options, see Prism::parse. + */ +static VALUE +parse_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + +/** + * call-seq: + * Prism::parse_file_success?(filepath, **options) -> bool * * Parse the given file and return true if it parses without errors. For * supported options, see Prism::parse. @@ -1008,6 +1020,18 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) { return result; } +/** + * call-seq: + * Prism::parse_file_failure?(filepath, **options) -> bool + * + * Parse the given file and return true if it parses with errors. For + * supported options, see Prism::parse. + */ +static VALUE +parse_file_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + /******************************************************************************/ /* Utility functions exposed to make testing easier */ /******************************************************************************/ @@ -1366,7 +1390,9 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1); rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1); rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1); rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1); #ifndef PRISM_EXCLUDE_SERIALIZATION rb_define_singleton_method(rb_cPrism, "dump", dump, -1); diff --git a/prism/options.c b/prism/options.c index 2854b765b9323b..4d0d6dbc49a70c 100644 --- a/prism/options.c +++ b/prism/options.c @@ -47,29 +47,40 @@ pm_options_command_line_set(pm_options_t *options, uint8_t command_line) { */ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length) { - if (version == NULL && length == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } + switch (length) { + case 0: + if (version == NULL) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } - if (length == 5) { - if (strncmp(version, "3.3.0", length) == 0) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_3_0; - return true; - } + return false; + case 5: + assert(version != NULL); - if (strncmp(version, "3.4.0", length) == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } - } + if (strncmp(version, "3.3.0", length) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_3_0; + return true; + } - if (length == 6 && strncmp(version, "latest", length) == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } + if (strncmp(version, "3.4.0", length) == 0) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + + return false; + case 6: + assert(version != NULL); - return false; + if (strncmp(version, "latest", length) == 0) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + + return false; + default: + return false; + } } // For some reason, GCC analyzer thinks we're leaking allocated scopes and diff --git a/prism/parser.h b/prism/parser.h index b0ff2f38fcf38d..b35026e6a25900 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -13,7 +13,6 @@ #include "prism/util/pm_constant_pool.h" #include "prism/util/pm_list.h" #include "prism/util/pm_newline_list.h" -#include "prism/util/pm_state_stack.h" #include "prism/util/pm_string.h" #include @@ -612,6 +611,11 @@ static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x40; static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED = -1; static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_NONE = 0; +/** + * A struct that represents a stack of boolean values. + */ +typedef uint32_t pm_state_stack_t; + /** * This struct represents the overall parser. It contains a reference to the * source file, as well as pointers that indicate where in the source it's diff --git a/prism/prism.c b/prism/prism.c index 91d27f566d4e00..18675b994a9c24 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2774,6 +2774,7 @@ pm_call_node_not_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *me if (arguments->closing_loc.start != NULL) { node->base.location.end = arguments->closing_loc.end; } else { + assert(receiver != NULL); node->base.location.end = receiver->location.end; } @@ -7631,6 +7632,30 @@ pm_parser_scope_pop(pm_parser_t *parser) { /* Stack helpers */ /******************************************************************************/ +/** + * Pushes a value onto the stack. + */ +static inline void +pm_state_stack_push(pm_state_stack_t *stack, bool value) { + *stack = (*stack << 1) | (value & 1); +} + +/** + * Pops a value off the stack. + */ +static inline void +pm_state_stack_pop(pm_state_stack_t *stack) { + *stack >>= 1; +} + +/** + * Returns the value at the top of the stack. + */ +static inline bool +pm_state_stack_p(const pm_state_stack_t *stack) { + return *stack & 1; +} + static inline void pm_accepts_block_stack_push(pm_parser_t *parser, bool value) { // Use the negation of the value to prevent stack overflow. @@ -12945,6 +12970,32 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod } } +/** + * Certain expressions are not writable, but in order to provide a better + * experience we give a specific error message. In order to maintain as much + * information in the tree as possible, we replace them with local variable + * writes. + */ +static pm_node_t * +parse_unwriteable_write(pm_parser_t *parser, pm_node_t *target, const pm_token_t *equals, pm_node_t *value) { + switch (PM_NODE_TYPE(target)) { + case PM_SOURCE_ENCODING_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING); break; + case PM_FALSE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE); break; + case PM_SOURCE_FILE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_FILE); break; + case PM_SOURCE_LINE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_LINE); break; + case PM_NIL_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_NIL); break; + case PM_SELF_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_SELF); break; + case PM_TRUE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE); break; + default: break; + } + + pm_constant_id_t name = pm_parser_constant_id_location(parser, target->location.start, target->location.end); + pm_local_variable_write_node_t *result = pm_local_variable_write_node_create(parser, name, 0, value, &target->location, equals); + + pm_node_destroy(parser, target); + return (pm_node_t *) result; +} + /** * Parse a list of targets for assignment. This is used in the case of a for * loop or a multi-assignment. For example, in the following code: @@ -19357,13 +19408,25 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_values(parser, previous_binding_power, PM_BINDING_POWER_MULTI_ASSIGNMENT + 1, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); return parse_write(parser, (pm_node_t *) multi_target, &token, value); } + case PM_SOURCE_ENCODING_NODE: + case PM_FALSE_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_NIL_NODE: + case PM_SELF_NODE: + case PM_TRUE_NODE: { + // In these special cases, we have specific error messages + // and we will replace them with local variable writes. + parser_lex(parser); + pm_node_t *value = parse_assignment_values(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); + return parse_unwriteable_write(parser, node, &token, value); + } default: + // In this case we have an = sign, but we don't know what + // it's for. We need to treat it as an error. We'll mark it + // as an error and skip past it. parser_lex(parser); - - // In this case we have an = sign, but we don't know what it's for. We - // need to treat it as an error. For now, we'll mark it as an error - // and just skip right past it. - pm_parser_err_token(parser, &token, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); + pm_parser_err_token(parser, &token, PM_ERR_EXPRESSION_NOT_WRITABLE); return node; } } @@ -21191,6 +21254,8 @@ pm_parser_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, if (inline_messages) { pm_buffer_append_byte(buffer, ' '); + assert(error->error != NULL); + const char *message = error->error->message; pm_buffer_append_string(buffer, message, strlen(message)); } diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 12a984e5a28bb4..6b5a28531585fe 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -60,6 +60,17 @@ module Prism DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot end + # Returns a list of the fields that exist for this node class. Fields + # describe the structure of the node. This kind of reflection is useful for + # things like recursively visiting each node _and_ field in the tree. + def self.fields + # This method should only be called on subclasses of Node, not Node + # itself. + raise NoMethodError, "undefined method `fields' for #{inspect}" if self == Node + + Reflection.fields_for(self) + end + # -------------------------------------------------------------------------- # :section: Node interface # These methods are effectively abstract methods that must be implemented by @@ -102,6 +113,11 @@ module Prism def inspect(inspector = NodeInspector.new) raise NoMethodError, "undefined method `inspect' for #{inspect}" end + + # Returns the type of the node as a symbol. + def self.type + raise NoMethodError, "undefined method `type' for #{inspect}" + end end <%- nodes.each do |node| -%> diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb new file mode 100644 index 00000000000000..13d1da33e874ff --- /dev/null +++ b/prism/templates/lib/prism/reflection.rb.erb @@ -0,0 +1,151 @@ +module Prism + # The Reflection module provides the ability to reflect on the structure of + # the syntax tree itself, as opposed to looking at a single syntax tree. This + # is useful in metaprogramming contexts. + module Reflection + # A field represents a single piece of data on a node. It is the base class + # for all other field types. + class Field + # The name of the field. + attr_reader :name + + # Initializes the field with the given name. + def initialize(name) + @name = name + end + end + + # A node field represents a single child node in the syntax tree. It + # resolves to a Prism::Node in Ruby. + class NodeField < Field + end + + # An optional node field represents a single child node in the syntax tree + # that may or may not be present. It resolves to either a Prism::Node or nil + # in Ruby. + class OptionalNodeField < Field + end + + # A node list field represents a list of child nodes in the syntax tree. It + # resolves to an array of Prism::Node instances in Ruby. + class NodeListField < Field + end + + # A constant field represents a constant value on a node. Effectively, it + # represents an identifier found within the source. It resolves to a symbol + # in Ruby. + class ConstantField < Field + end + + # An optional constant field represents a constant value on a node that may + # or may not be present. It resolves to either a symbol or nil in Ruby. + class OptionalConstantField < Field + end + + # A constant list field represents a list of constant values on a node. It + # resolves to an array of symbols in Ruby. + class ConstantListField < Field + end + + # A string field represents a string value on a node. It almost always + # represents the unescaped value of a string-like literal. It resolves to a + # string in Ruby. + class StringField < Field + end + + # A location field represents the location of some part of the node in the + # source code. For example, the location of a keyword or an operator. It + # resolves to a Prism::Location in Ruby. + class LocationField < Field + end + + # An optional location field represents the location of some part of the + # node in the source code that may or may not be present. It resolves to + # either a Prism::Location or nil in Ruby. + class OptionalLocationField < Field + end + + # A uint8 field represents an unsigned 8-bit integer value on a node. It + # resolves to an Integer in Ruby. + class UInt8Field < Field + end + + # A uint32 field represents an unsigned 32-bit integer value on a node. It + # resolves to an Integer in Ruby. + class UInt32Field < Field + end + + # A flags field represents a bitset of flags on a node. It resolves to an + # integer in Ruby. Note that the flags cannot be accessed directly on the + # node because the integer is kept private. Instead, the various flags in + # the bitset should be accessed through their query methods. + class FlagsField < Field + # The names of the flags in the bitset. + attr_reader :flags + + # Initializes the flags field with the given name and flags. + def initialize(name, flags) + super(name) + @flags = flags + end + end + + # An integer field represents an arbitrarily-sized integer value. It is used + # exclusively to represent the value of an integer literal. It resolves to + # an Integer in Ruby. + class IntegerField < Field + end + + # A double field represents a double-precision floating point value. It is + # used exclusively to represent the value of a floating point literal. It + # resolves to a Float in Ruby. + class DoubleField < Field + end + + # Returns the fields for the given node. + def self.fields_for(node) + case node.type + <%- nodes.each do |node| -%> + when :<%= node.human %> + [<%= node.fields.map { |field| + case field + when Prism::Template::NodeField + "NodeField.new(:#{field.name})" + when Prism::Template::OptionalNodeField + "OptionalNodeField.new(:#{field.name})" + when Prism::Template::NodeListField + "NodeListField.new(:#{field.name})" + when Prism::Template::ConstantField + "ConstantField.new(:#{field.name})" + when Prism::Template::OptionalConstantField + "OptionalConstantField.new(:#{field.name})" + when Prism::Template::ConstantListField + "ConstantListField.new(:#{field.name})" + when Prism::Template::StringField + "StringField.new(:#{field.name})" + when Prism::Template::LocationField + "LocationField.new(:#{field.name})" + when Prism::Template::OptionalLocationField + "OptionalLocationField.new(:#{field.name})" + when Prism::Template::UInt8Field + "UInt8Field.new(:#{field.name})" + when Prism::Template::UInt32Field + "UInt32Field.new(:#{field.name})" + when Prism::Template::FlagsField + found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } + "FlagsField.new(:#{field.name}, [#{found.values.map { |value| ":#{value.name.downcase}?" }.join(", ")}])" + when Prism::Template::IntegerField + "IntegerField.new(:#{field.name})" + when Prism::Template::DoubleField + "DoubleField.new(:#{field.name})" + else + raise field.class.name + end + }.join(", ") %>] + <%- end -%> + else + raise "Unknown node type: #{node.type.inspect}" + end + end + end +end diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 97ad451890a2a2..42f802455146ea 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -188,6 +188,14 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE] = { "unexpected '='; target cannot be written", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING] = { "Can't assign to __ENCODING__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE] = { "Can't assign to false", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_FILE] = { "Can't assign to __FILE__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_LINE] = { "Can't assign to __LINE__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_NIL] = { "Can't assign to nil", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_SELF] = { "Can't change the value of self", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE] = { "Can't assign to true", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_SYNTAX }, diff --git a/prism/templates/template.rb b/prism/templates/template.rb index 31257ef1a150c5..d0ce6c6643755d 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -631,6 +631,7 @@ def locals "lib/prism/dsl.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node.rb", + "lib/prism/reflection.rb", "lib/prism/serialize.rb", "lib/prism/visitor.rb", "src/diagnostic.c", diff --git a/prism/util/pm_integer.c b/prism/util/pm_integer.c index 8f8b75474df33a..0739662e98037b 100644 --- a/prism/util/pm_integer.c +++ b/prism/util/pm_integer.c @@ -172,21 +172,21 @@ karatsuba_multiply(pm_integer_t *destination, pm_integer_t *left, pm_integer_t * pm_integer_t y0 = { 0, half, right_values, false }; pm_integer_t y1 = { 0, right_length - half, right_values + half, false }; - pm_integer_t z0; + pm_integer_t z0 = { 0 }; karatsuba_multiply(&z0, &x0, &y0, base); - pm_integer_t z2; + pm_integer_t z2 = { 0 }; karatsuba_multiply(&z2, &x1, &y1, base); // For simplicity to avoid considering negative values, // use `z1 = (x0 + x1) * (y0 + y1) - z0 - z2` instead of original karatsuba algorithm. - pm_integer_t x01; + pm_integer_t x01 = { 0 }; big_add(&x01, &x0, &x1, base); - pm_integer_t y01; + pm_integer_t y01 = { 0 }; big_add(&y01, &y0, &y1, base); - pm_integer_t xy; + pm_integer_t xy = { 0 }; karatsuba_multiply(&xy, &x01, &y01, base); pm_integer_t z1; @@ -194,7 +194,11 @@ karatsuba_multiply(pm_integer_t *destination, pm_integer_t *left, pm_integer_t * size_t length = left_length + right_length; uint32_t *values = (uint32_t*) xcalloc(length, sizeof(uint32_t)); + + assert(z0.values != NULL); memcpy(values, z0.values, sizeof(uint32_t) * z0.length); + + assert(z2.values != NULL); memcpy(values + 2 * half, z2.values, sizeof(uint32_t) * z2.length); uint32_t carry = 0; @@ -326,6 +330,8 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u INTEGER_EXTRACT(source, source_length, source_values) size_t bigints_length = (source_length + 1) / 2; + assert(bigints_length > 0); + pm_integer_t *bigints = (pm_integer_t *) xcalloc(bigints_length, sizeof(pm_integer_t)); if (bigints == NULL) return; @@ -345,13 +351,13 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u base = next_base; size_t next_length = (bigints_length + 1) / 2; - pm_integer_t *next_bigints = (pm_integer_t *) xmalloc(sizeof(pm_integer_t) * next_length); + pm_integer_t *next_bigints = (pm_integer_t *) xcalloc(next_length, sizeof(pm_integer_t)); for (size_t bigints_index = 0; bigints_index < bigints_length; bigints_index += 2) { if (bigints_index + 1 == bigints_length) { next_bigints[bigints_index / 2] = bigints[bigints_index]; } else { - pm_integer_t multiplied; + pm_integer_t multiplied = { 0 }; karatsuba_multiply(&multiplied, &base, &bigints[bigints_index + 1], base_to); big_add(&next_bigints[bigints_index / 2], &bigints[bigints_index], &multiplied, base_to); @@ -584,7 +590,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) { } // Otherwise, first we'll convert the base from 1<<32 to 10**9. - pm_integer_t converted; + pm_integer_t converted = { 0 }; pm_integer_convert_base(&converted, integer, (uint64_t) 1 << 32, 1000000000); if (converted.values == NULL) { diff --git a/prism/util/pm_state_stack.c b/prism/util/pm_state_stack.c deleted file mode 100644 index 2a424b4c03c296..00000000000000 --- a/prism/util/pm_state_stack.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "prism/util/pm_state_stack.h" - -/** - * Pushes a value onto the stack. - */ -void -pm_state_stack_push(pm_state_stack_t *stack, bool value) { - *stack = (*stack << 1) | (value & 1); -} - -/** - * Pops a value off the stack. - */ -void -pm_state_stack_pop(pm_state_stack_t *stack) { - *stack >>= 1; -} - -/** - * Returns the value at the top of the stack. - */ -bool -pm_state_stack_p(pm_state_stack_t *stack) { - return *stack & 1; -} diff --git a/prism/util/pm_state_stack.h b/prism/util/pm_state_stack.h deleted file mode 100644 index 1ce57a2209f0c2..00000000000000 --- a/prism/util/pm_state_stack.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file pm_state_stack.h - * - * A stack of boolean values. - */ -#ifndef PRISM_STATE_STACK_H -#define PRISM_STATE_STACK_H - -#include "prism/defines.h" - -#include -#include - -/** - * A struct that represents a stack of boolean values. - */ -typedef uint32_t pm_state_stack_t; - -/** - * Pushes a value onto the stack. - * - * @param stack The stack to push the value onto. - * @param value The value to push onto the stack. - */ -void pm_state_stack_push(pm_state_stack_t *stack, bool value); - -/** - * Pops a value off the stack. - * - * @param stack The stack to pop the value off of. - */ -void pm_state_stack_pop(pm_state_stack_t *stack); - -/** - * Returns the value at the top of the stack. - * - * @param stack The stack to get the value from. - * @return The value at the top of the stack. - */ -bool pm_state_stack_p(pm_state_stack_t *stack); - -#endif diff --git a/rational.c b/rational.c index 3b82016f323087..014cbb6c6adf87 100644 --- a/rational.c +++ b/rational.c @@ -418,7 +418,7 @@ nurat_s_new_internal(VALUE klass, VALUE num, VALUE den) RATIONAL_SET_NUM((VALUE)obj, num); RATIONAL_SET_DEN((VALUE)obj, den); - OBJ_FREEZE_RAW((VALUE)obj); + OBJ_FREEZE((VALUE)obj); return (VALUE)obj; } @@ -1847,7 +1847,7 @@ nurat_loader(VALUE self, VALUE a) nurat_canonicalize(&num, &den); RATIONAL_SET_NUM((VALUE)dat, num); RATIONAL_SET_DEN((VALUE)dat, den); - OBJ_FREEZE_RAW(self); + OBJ_FREEZE(self); return self; } diff --git a/ruby.c b/ruby.c index fb60551c3fc050..e939320f1f63d3 100644 --- a/ruby.c +++ b/ruby.c @@ -706,11 +706,11 @@ ruby_init_loadpath(void) p -= bindir_len; archlibdir = rb_str_subseq(sopath, 0, p - libpath); rb_str_cat_cstr(archlibdir, libdir); - OBJ_FREEZE_RAW(archlibdir); + OBJ_FREEZE(archlibdir); } else if (p - libpath >= libdir_len && !strncmp(p - libdir_len, libdir, libdir_len)) { archlibdir = rb_str_subseq(sopath, 0, (p2 ? p2 : p) - libpath); - OBJ_FREEZE_RAW(archlibdir); + OBJ_FREEZE(archlibdir); p -= libdir_len; } #ifdef ENABLE_MULTIARCH @@ -741,7 +741,7 @@ ruby_init_loadpath(void) #endif rb_gc_register_address(&ruby_prefix_path); ruby_prefix_path = PREFIX_PATH(); - OBJ_FREEZE_RAW(ruby_prefix_path); + OBJ_FREEZE(ruby_prefix_path); if (!archlibdir) archlibdir = ruby_prefix_path; rb_gc_register_address(&ruby_archlibdir_path); ruby_archlibdir_path = archlibdir; diff --git a/ruby_parser.c b/ruby_parser.c index 5d9c6c938f35db..16a868bc6b3707 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -309,18 +309,6 @@ syntax_error_new(void) return rb_class_new_instance(0, 0, rb_eSyntaxError); } -static VALUE -obj_write(VALUE old, VALUE *slot, VALUE young) -{ - return RB_OBJ_WRITE(old, slot, young); -} - -static VALUE -default_rs(void) -{ - return rb_default_rs; -} - static void * memmove2(void *dest, const void *src, size_t t, size_t n) { @@ -429,7 +417,6 @@ static const rb_parser_config_t rb_global_parser_config = { .ary_push = rb_ary_push, .ary_new_from_args = rb_ary_new_from_args, .ary_unshift = rb_ary_unshift, - .ary_modify = rb_ary_modify, .array_len = rb_array_len, .array_aref = RARRAY_AREF, @@ -476,7 +463,6 @@ static const rb_parser_config_t rb_global_parser_config = { .stderr_tty_p = rb_stderr_tty_p, .write_error_str = rb_write_error_str, - .default_rs = default_rs, .io_write = rb_io_write, .io_flush = rb_io_flush, .io_puts = rb_io_puts, @@ -501,7 +487,6 @@ static const rb_parser_config_t rb_global_parser_config = { .enc_mbcput = enc_mbcput, .enc_find_index = rb_enc_find_index, .enc_from_index = enc_from_index, - .enc_associate_index = rb_enc_associate_index, .enc_isspace = enc_isspace, .enc_coderange_7bit = ENC_CODERANGE_7BIT, .enc_coderange_unknown = ENC_CODERANGE_UNKNOWN, @@ -528,10 +513,8 @@ static const rb_parser_config_t rb_global_parser_config = { .sized_xfree = ruby_sized_xfree, .sized_realloc_n = ruby_sized_realloc_n, - .obj_write = obj_write, .gc_guard = gc_guard, .gc_mark = rb_gc_mark, - .gc_mark_and_move = rb_gc_mark_and_move, .reg_compile = rb_reg_compile, .reg_check_preprocess = rb_reg_check_preprocess, diff --git a/rubyparser.h b/rubyparser.h index d36e8dcede032f..6d346a7ff484a9 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1264,7 +1264,6 @@ typedef struct rb_parser_config_struct { VALUE (*ary_push)(VALUE ary, VALUE elem); VALUE (*ary_new_from_args)(long n, ...); VALUE (*ary_unshift)(VALUE ary, VALUE item); - void (*ary_modify)(VALUE ary); long (*array_len)(VALUE a); VALUE (*array_aref)(VALUE, long); @@ -1318,7 +1317,6 @@ typedef struct rb_parser_config_struct { /* IO */ int (*stderr_tty_p)(void); void (*write_error_str)(VALUE mesg); - VALUE (*default_rs)(void); VALUE (*io_write)(VALUE io, VALUE str); VALUE (*io_flush)(VALUE io); VALUE (*io_puts)(int argc, const VALUE *argv, VALUE out); @@ -1345,7 +1343,6 @@ typedef struct rb_parser_config_struct { int (*enc_mbcput)(unsigned int c, void *buf, rb_encoding *enc); int (*enc_find_index)(const char *name); rb_encoding *(*enc_from_index)(int idx); - VALUE (*enc_associate_index)(VALUE obj, int encindex); int (*enc_isspace)(OnigCodePoint c, rb_encoding *enc); rb_encoding *(*enc_compatible)(VALUE str1, VALUE str2); VALUE (*enc_from_encoding)(rb_encoding *enc); @@ -1378,10 +1375,8 @@ typedef struct rb_parser_config_struct { /* GC */ void (*sized_xfree)(void *x, size_t size); void *(*sized_realloc_n)(void *ptr, size_t new_count, size_t element_size, size_t old_count); - VALUE (*obj_write)(VALUE, VALUE *, VALUE); void (*gc_guard)(VALUE); void (*gc_mark)(VALUE); - void (*gc_mark_and_move)(VALUE *ptr); /* Re */ VALUE (*reg_compile)(VALUE str, int options, const char *sourcefile, int sourceline); diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index c81c7095b0e74a..5f1b034bfc0de1 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -1262,43 +1262,47 @@ end end - it "adds current musl platform" do - build_repo4 do - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux" - end + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux-musl" - end - end + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" - gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + gem "rcee_precompiled", "0.5.0" + G - gem "rcee_precompiled", "0.5.0" - G + simulate_platform host_platform do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - simulate_platform "x86_64-linux-musl" do - bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-musl) - expect(lockfile).to eq(<<~L) - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - rcee_precompiled (0.5.0-x86_64-linux) - rcee_precompiled (0.5.0-x86_64-linux-musl) + PLATFORMS + x86_64-linux + x86_64-linux-musl - PLATFORMS - x86_64-linux - x86_64-linux-musl + DEPENDENCIES + rcee_precompiled (= 0.5.0) - DEPENDENCIES - rcee_precompiled (= 0.5.0) - - BUNDLED WITH - #{Bundler::VERSION} - L + BUNDLED WITH + #{Bundler::VERSION} + L + end + end end end diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb index eb4b39c8be55df..b3e021e6360b7a 100644 --- a/spec/ruby/core/fiber/raise_spec.rb +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -91,6 +91,40 @@ fiber_two.resume.should == [:yield_one, :rescued] end + + ruby_version_is "3.4" do + it "raises on the resumed fiber" do + root_fiber = Fiber.current + f1 = Fiber.new { root_fiber.transfer } + f2 = Fiber.new { f1.resume } + f2.transfer + + -> do + f2.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "raises on itself" do + -> do + Fiber.current.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "should raise on parent fiber" do + f2 = nil + f1 = Fiber.new do + # This is equivalent to Kernel#raise: + f2.raise(RuntimeError, "Expected error") + end + f2 = Fiber.new do + f1.resume + end + + -> do + f2.resume + end.should raise_error(RuntimeError, "Expected error") + end + end end diff --git a/spec/ruby/core/thread/each_caller_location_spec.rb b/spec/ruby/core/thread/each_caller_location_spec.rb index dbece06cd8183e..29c271789b5225 100644 --- a/spec/ruby/core/thread/each_caller_location_spec.rb +++ b/spec/ruby/core/thread/each_caller_location_spec.rb @@ -40,10 +40,10 @@ }.should raise_error(LocalJumpError, "no block given") end - it "doesn't accept positional and keyword arguments" do + it "doesn't accept keyword arguments" do -> { Thread.each_caller_location(12, foo: 10) {} - }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") + }.should raise_error(ArgumentError); end end end diff --git a/string.c b/string.c index 98bf0dcbb82d11..b760c4652cc410 100644 --- a/string.c +++ b/string.c @@ -380,7 +380,7 @@ fstr_update_callback(st_data_t *key, st_data_t *value, st_data_t data, int exist RSTRING(str)->len, ENCODING_GET(str)); } - OBJ_FREEZE_RAW(str); + OBJ_FREEZE(str); } else { if (!OBJ_FROZEN(str) || CHILLED_STRING_P(str)) { @@ -416,7 +416,7 @@ rb_fstring(VALUE str) bare = BARE_STRING_P(str); if (!bare) { if (STR_EMBED_P(str)) { - OBJ_FREEZE_RAW(str); + OBJ_FREEZE(str); FL_SET_RAW(str, RUBY_FL_SHAREABLE); return str; } @@ -437,7 +437,7 @@ rb_fstring(VALUE str) if (!bare) { str_replace_shared_without_enc(str, fstr); - OBJ_FREEZE_RAW(str); + OBJ_FREEZE(str); FL_SET_RAW(str, RUBY_FL_SHAREABLE); return str; } diff --git a/struct.c b/struct.c index e82bcc3e032804..544228f76b9582 100644 --- a/struct.c +++ b/struct.c @@ -136,7 +136,7 @@ struct_set_members(VALUE klass, VALUE /* frozen hidden array */ members) j = struct_member_pos_probe(j, mask); } } - OBJ_FREEZE_RAW(back); + OBJ_FREEZE(back); } rb_ivar_set(klass, id_members, members); rb_ivar_set(klass, id_back_members, back); @@ -422,7 +422,7 @@ struct_make_members_list(va_list ar) } ary = rb_hash_keys(list); RBASIC_CLEAR_CLASS(ary); - OBJ_FREEZE_RAW(ary); + OBJ_FREEZE(ary); return ary; } @@ -682,7 +682,7 @@ rb_struct_s_def(int argc, VALUE *argv, VALUE klass) } rest = rb_hash_keys(rest); RBASIC_CLEAR_CLASS(rest); - OBJ_FREEZE_RAW(rest); + OBJ_FREEZE(rest); if (NIL_P(name)) { st = anonymous_struct(klass); } @@ -794,7 +794,7 @@ VALUE rb_struct_initialize(VALUE self, VALUE values) { rb_struct_initialize_m(RARRAY_LENINT(values), RARRAY_CONST_PTR(values), self); - if (rb_obj_is_kind_of(self, rb_cData)) OBJ_FREEZE_RAW(self); + if (rb_obj_is_kind_of(self, rb_cData)) OBJ_FREEZE(self); RB_GC_GUARD(values); return Qnil; } @@ -1685,7 +1685,7 @@ rb_data_s_def(int argc, VALUE *argv, VALUE klass) } rest = rb_hash_keys(rest); RBASIC_CLEAR_CLASS(rest); - OBJ_FREEZE_RAW(rest); + OBJ_FREEZE(rest); data_class = anonymous_struct(klass); setup_data(data_class, rest); if (rb_block_given_p()) { @@ -1802,7 +1802,7 @@ rb_data_initialize_m(int argc, const VALUE *argv, VALUE self) rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg); // Freeze early before potentially raising, so that we don't leave an // unfrozen copy on the heap, which could get exposed via ObjectSpace. - OBJ_FREEZE_RAW(self); + OBJ_FREEZE(self); if (arg.unknown_keywords != Qnil) { rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords)); } @@ -1814,7 +1814,7 @@ static VALUE rb_data_init_copy(VALUE copy, VALUE s) { copy = rb_struct_init_copy(copy, s); - RB_OBJ_FREEZE_RAW(copy); + RB_OBJ_FREEZE(copy); return copy; } diff --git a/test/prism/reflection_test.rb b/test/prism/reflection_test.rb new file mode 100644 index 00000000000000..869b68b1f8f5cb --- /dev/null +++ b/test/prism/reflection_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module Prism + class ReflectionTest < TestCase + def test_fields_for + fields = Reflection.fields_for(CallNode) + methods = CallNode.instance_methods(false) + + fields.each do |field| + if field.is_a?(Reflection::FlagsField) + field.flags.each do |flag| + assert_includes methods, flag + end + else + assert_includes methods, field.name + end + end + end + end +end diff --git a/test/reline/helper.rb b/test/reline/helper.rb index f2f3421ded0bf4..26fe8344829523 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -131,7 +131,7 @@ def input_raw_keys(input, convert = true) def assert_line_around_cursor(before, after) before = convert_str(before) after = convert_str(after) - line = @line_editor.line + line = @line_editor.current_line byte_pointer = @line_editor.instance_variable_get(:@byte_pointer) actual_before = line.byteslice(0, byte_pointer) actual_after = line.byteslice(byte_pointer..) diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 8f5676a1d4f170..a9baf9ad3724a6 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1436,4 +1436,9 @@ def test_unix_line_discard input_keys("\C-f\C-u", false) assert_line_around_cursor('', '') end + + def test_vi_editing_mode + @line_editor.__send__(:vi_editing_mode, nil) + assert(@config.editing_mode_is?(:vi_insert)) + end end diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index cf3943ae379bce..4deae2dd8313c1 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -911,4 +911,9 @@ def test_vi_motion_operators input_keys("test = { foo: bar }\C-[BBBldt}b") end end + + def test_emacs_editing_mode + @line_editor.__send__(:emacs_editing_mode, nil) + assert(@config.editing_mode_is?(:emacs)) + end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index fe0f8842eafa92..09d064934a9c32 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -309,6 +309,21 @@ def test_prompt_with_escape_sequence_and_autowrap EOC end + def test_readline_with_multiline_input + start_terminal(5, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt}, startup_message: 'Multiline REPL.') + write("def foo\n bar\nend\n") + write("Reline.readline('prompt> ')\n") + write("\C-p\C-p") + close + assert_screen(<<~EOC) + => :foo + [0000]> Reline.readline('prompt> ') + prompt> def foo + bar + end + EOC + end + def test_multiline_and_autowrap start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write("def aaaaaaaaaa\n 33333333\n end\C-a\C-pputs\C-e\e\C-m888888888888888") diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index cfc65faacb1ab1..fca7b620302680 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -155,6 +155,10 @@ def test_caller_with_nil_length end def test_each_backtrace_location + assert_nil(Thread.each_caller_location {}) + + assert_raise(LocalJumpError) {Thread.each_caller_location} + i = 0 cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1} assert_equal(cl.to_s, ecl.to_s) @@ -181,6 +185,10 @@ def test_each_backtrace_location assert_raise(StopIteration) { ecl.next } + + ary = [] + cl = caller_locations(1, 2); Thread.each_caller_location(1, 2) {|x| ary << x} + assert_equal(cl.map(&:to_s), ary.map(&:to_s)) end def test_caller_locations_first_label diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 825c191d874c81..7599d434635d19 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -953,11 +953,7 @@ def each(&block) assert_equal(true, e.is_lambda) end - def test_product - ## - ## Enumerator::Product - ## - + def test_product_new # 0-dimensional e = Enumerator::Product.new assert_instance_of(Enumerator::Product, e) @@ -994,15 +990,16 @@ def test_product e.each { |x,| heads << x } assert_equal [1, 1, 2, 2, 3, 3], heads + # Any enumerable is 0 size + assert_equal(0, Enumerator::Product.new([], 1..).size) + # Reject keyword arguments assert_raise(ArgumentError) { Enumerator::Product.new(1..3, foo: 1, bar: 2) } + end - ## - ## Enumerator.product - ## - + def test_s_product # without a block e = Enumerator.product(1..3, %w[a b]) assert_instance_of(Enumerator::Product, e) @@ -1029,6 +1026,8 @@ def test_product assert_equal(nil, e.size) assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + assert_equal(0, Enumerator.product([], 1..).size) + # Reject keyword arguments assert_raise(ArgumentError) { Enumerator.product(1..3, foo: 1, bar: 2) diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index dc84d8bd7c4b51..dbf91fe8c94025 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -827,6 +827,24 @@ def test_compile_prism_with_file end end + def block_using_method + yield + end + + def block_unused_method + end + + def test_unused_param + a = RubyVM::InstructionSequence.of(method(:block_using_method)).to_a + + omit 'TODO: Prism' if a.dig(4, :parser) != :"parse.y" + + assert_equal true, a.dig(11, :use_block) + + b = RubyVM::InstructionSequence.of(method(:block_unused_method)).to_a + assert_equal nil, b.dig(11, :use_block) + end + def test_compile_prism_with_invalid_object_type assert_raise(TypeError) do RubyVM::InstructionSequence.compile_prism(Object.new) diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index a41704cf069055..5301b51650b3cc 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1692,5 +1692,19 @@ def f6 = super{} # zsuper / unuse assert_match(/-:23: warning.+f5/, err.join) assert_match(/-:24: warning.+f6/, err.join) end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + class C0 + def f = yield + end + + class C1 < C0 + def f = nil + end + + C1.new.f{} # do not warn on duck typing + RUBY + assert_equal 0, err.size, err.join("\n") + end end end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 13261eacdd1f79..ebe85dac826d6e 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3358,7 +3358,7 @@ def test_uplus_minus require 'objspace' - str = "bar".freeze + str = "test_uplus_minus_str".freeze assert_includes ObjectSpace.dump(str), '"fstring":true' assert_predicate(str, :frozen?) @@ -3368,7 +3368,7 @@ def test_uplus_minus assert_not_same(str, +str) assert_same(str, -str) - bar = -%w(b a r).join('') + bar = -%w(test uplus minus str).join('_') assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}") end diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 82fb50b70492c7..f97306717d1b47 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -285,9 +285,12 @@ def assert_contains_make_command(target, output, msg = nil) def setup @orig_hooks = {} @orig_env = ENV.to_hash - @tmp = File.expand_path("../../tmp", __dir__) - FileUtils.mkdir_p @tmp + top_srcdir = __dir__ + "/../.." + @tmp = File.expand_path(ENV.fetch("GEM_TEST_TMPDIR", "tmp"), top_srcdir) + + FileUtils.mkdir_p(@tmp, mode: 0o700) # =rwx + @tmp = File.realpath(@tmp) @tempdir = Dir.mktmpdir("test_rubygems_", @tmp) 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 34db31f61c81f4..b9fed0e2b0b46c 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.91" +version = "0.9.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" +checksum = "06dab8dbb0beb0a575a80c4b46355c8ace1f3dc5df60a3109758f205f1061366" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.91" +version = "0.9.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" +checksum = "164d44950a42f2ba2f94efdcb650e14764270f84d281352aebb261806da0b2ce" 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 00a48df5d57ddb..9f844b62be54dc 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.91" +rb-sys = "0.9.94" 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 f96b1442936e2f..7e1617a663784b 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.91" +version = "0.9.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" +checksum = "06dab8dbb0beb0a575a80c4b46355c8ace1f3dc5df60a3109758f205f1061366" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.91" +version = "0.9.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" +checksum = "164d44950a42f2ba2f94efdcb650e14764270f84d281352aebb261806da0b2ce" 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 dca81463949243..a84cc8aabbf6ca 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.91" +rb-sys = "0.9.94" diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index e17cd0abb11a01..90316505819b8c 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -700,6 +700,18 @@ def test_read s.force_encoding(Encoding::US_ASCII) assert_same(s, f.read(nil, s)) assert_string("", Encoding::UTF_8, s, bug13806) + + bug20418 = '[Bug #20418] ™€®'.b + f = StringIO.new(bug20418) + s = "" + assert_equal(Encoding::UTF_8, s.encoding, bug20418) + f.read(4, s) + assert_equal(Encoding::UTF_8, s.encoding, bug20418) + + f.rewind + s = "" + f.read(nil, s) + assert_equal(Encoding::ASCII_8BIT, s.encoding, bug20418) end def test_readpartial @@ -711,8 +723,8 @@ def test_readpartial assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(f.size)) f.rewind # not empty buffer - s = '0123456789' - assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(f.size, s)) + s = '0123456789'.b + assert_equal("\u3042\u3044".b, f.readpartial(f.size, s)) end def test_read_nonblock @@ -736,8 +748,8 @@ def test_read_nonblock_no_exceptions assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size)) f.rewind # not empty buffer - s = '0123456789' - assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size, s)) + s = '0123456789'.b + assert_equal("\u3042\u3044".b, f.read_nonblock(f.size, s)) end def test_sysread diff --git a/thread.c b/thread.c index 404c009a13c78c..b4edc845670746 100644 --- a/thread.c +++ b/thread.c @@ -2300,7 +2300,7 @@ rb_thread_s_handle_interrupt(VALUE self, VALUE mask_arg) mask = mask_arg; } else if (RB_TYPE_P(mask, T_HASH)) { - OBJ_FREEZE_RAW(mask); + OBJ_FREEZE(mask); } rb_ary_push(th->pending_interrupt_mask_stack, mask); @@ -5864,7 +5864,7 @@ rb_uninterruptible(VALUE (*b_proc)(VALUE), VALUE data) rb_thread_t *cur_th = GET_THREAD(); rb_hash_aset(interrupt_mask, rb_cObject, sym_never); - OBJ_FREEZE_RAW(interrupt_mask); + OBJ_FREEZE(interrupt_mask); rb_ary_push(cur_th->pending_interrupt_mask_stack, interrupt_mask); VALUE ret = rb_ensure(b_proc, data, uninterruptible_exit, Qnil); diff --git a/universal_parser.c b/universal_parser.c index 23a86311a5e039..2055681889ddb1 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -95,7 +95,6 @@ #undef rb_ary_new_from_args #define rb_ary_new_from_args p->config->ary_new_from_args #define rb_ary_unshift p->config->ary_unshift -#define rb_ary_modify p->config->ary_modify #undef RARRAY_LEN #define RARRAY_LEN p->config->array_len #define RARRAY_AREF p->config->array_aref @@ -154,7 +153,6 @@ #define rb_stderr_tty_p p->config->stderr_tty_p #define rb_write_error_str p->config->write_error_str -#define rb_default_rs p->config->default_rs() #define rb_io_write p->config->io_write #define rb_io_flush p->config->io_flush #define rb_io_puts p->config->io_puts @@ -179,7 +177,6 @@ #define rb_enc_mbcput p->config->enc_mbcput #define rb_enc_find_index p->config->enc_find_index #define rb_enc_from_index p->config->enc_from_index -#define rb_enc_associate_index p->config->enc_associate_index #define rb_enc_isspace p->config->enc_isspace #define ENC_CODERANGE_7BIT p->config->enc_coderange_7bit #define ENC_CODERANGE_UNKNOWN p->config->enc_coderange_unknown @@ -202,8 +199,6 @@ #define ruby_sized_xfree p->config->sized_xfree #define SIZED_REALLOC_N(v, T, m, n) ((v) = (T *)p->config->sized_realloc_n((void *)(v), (m), sizeof(T), (n))) -#undef RB_OBJ_WRITE -#define RB_OBJ_WRITE(old, slot, young) p->config->obj_write((VALUE)(old), (VALUE *)(slot), (VALUE)(young)) #undef RB_GC_GUARD #define RB_GC_GUARD p->config->gc_guard #define rb_gc_mark p->config->gc_mark diff --git a/variable.c b/variable.c index 2478d65db2b5b4..983cd76be2e67d 100644 --- a/variable.c +++ b/variable.c @@ -1874,7 +1874,10 @@ rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id) void rb_obj_freeze_inline(VALUE x) { if (RB_FL_ABLE(x)) { - RB_OBJ_FREEZE_RAW(x); + RB_FL_SET_RAW(x, RUBY_FL_FREEZE); + if (TYPE(x) == T_STRING) { + RB_FL_UNSET_RAW(x, FL_USER3); // STR_CHILLED + } rb_shape_t * next_shape = rb_shape_transition_shape_frozen(x); diff --git a/vm.c b/vm.c index 329e47fe68de94..917195377881f2 100644 --- a/vm.c +++ b/vm.c @@ -1008,6 +1008,11 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co } #endif + // Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp). + if (env->iseq) { + rb_yjit_invalidate_ep_is_bp(env->iseq); + } + return (VALUE)env; } @@ -4300,6 +4305,7 @@ Init_BareVM(void) vm->negative_cme_table = rb_id_table_create(16); vm->overloaded_cme_table = st_init_numtable(); vm->constant_cache = rb_id_table_create(0); + vm->unused_block_warning_table = st_init_numtable(); rb_native_mutex_initialize(&vm->subclass_list_lock); vm->subclass_list_lock_owner = NULL; diff --git a/vm_args.c b/vm_args.c index 9df175eaa94a79..1a78e96776d53b 100644 --- a/vm_args.c +++ b/vm_args.c @@ -1021,7 +1021,7 @@ vm_caller_setup_arg_block(const rb_execution_context_t *ec, rb_control_frame_t * VALUE callback_arg = rb_ary_hidden_new(2); rb_ary_push(callback_arg, block_code); rb_ary_push(callback_arg, ref); - OBJ_FREEZE_RAW(callback_arg); + OBJ_FREEZE(callback_arg); func = rb_func_lambda_new(refine_sym_proc_call, callback_arg, 1, UNLIMITED_ARGUMENTS); rb_hash_aset(ref, block_code, func); } diff --git a/vm_backtrace.c b/vm_backtrace.c index 3fe816930da2de..22b28368d74d77 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -1170,17 +1170,17 @@ rb_make_backtrace(void) return rb_ec_backtrace_str_ary(GET_EC(), BACKTRACE_START, ALL_BACKTRACE_LINES); } -static VALUE -ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, int to_str) +static long +ec_backtrace_range(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, long *len_ptr) { - VALUE level, vn; + VALUE level, vn, opts; long lev, n; - VALUE btval; - VALUE r; - int too_large; - rb_scan_args(argc, argv, "02", &level, &vn); + rb_scan_args(argc, argv, "02:", &level, &vn, &opts); + if (!NIL_P(opts)) { + rb_get_kwargs(opts, (ID []){0}, 0, 0, NULL); + } if (argc == 2 && NIL_P(vn)) argc--; switch (argc) { @@ -1201,7 +1201,7 @@ ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *arg n = ALL_BACKTRACE_LINES; break; case Qnil: - return Qnil; + return -1; default: lev = beg + lev_plus; n = len; @@ -1225,6 +1225,20 @@ ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *arg break; } + *len_ptr = n; + return lev; +} + +static VALUE +ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, int to_str) +{ + long lev, n; + VALUE btval, r; + int too_large; + + lev = ec_backtrace_range(ec, argc, argv, lev_default, lev_plus, &n); + if (lev < 0) return Qnil; + if (n == 0) { return rb_ary_new(); } @@ -1354,15 +1368,19 @@ rb_f_caller_locations(int argc, VALUE *argv, VALUE _) /* * call-seq: - * Thread.each_caller_location{ |loc| ... } -> nil + * Thread.each_caller_location(...) { |loc| ... } -> nil * * Yields each frame of the current execution stack as a * backtrace location object. */ static VALUE -each_caller_location(VALUE unused) +each_caller_location(int argc, VALUE *argv, VALUE _) { - rb_ec_partial_backtrace_object(GET_EC(), 2, ALL_BACKTRACE_LINES, NULL, FALSE, TRUE); + rb_execution_context_t *ec = GET_EC(); + long n, lev = ec_backtrace_range(ec, argc, argv, 1, 1, &n); + if (lev >= 0 && n != 0) { + rb_ec_partial_backtrace_object(ec, lev, n, NULL, FALSE, TRUE); + } return Qnil; } @@ -1442,7 +1460,7 @@ Init_vm_backtrace(void) rb_define_global_function("caller", rb_f_caller, -1); rb_define_global_function("caller_locations", rb_f_caller_locations, -1); - rb_define_singleton_method(rb_cThread, "each_caller_location", each_caller_location, 0); + rb_define_singleton_method(rb_cThread, "each_caller_location", each_caller_location, -1); } /* debugger API */ diff --git a/vm_core.h b/vm_core.h index 949d912872a1dd..6395ebf759e9cd 100644 --- a/vm_core.h +++ b/vm_core.h @@ -796,6 +796,7 @@ typedef struct rb_vm_struct { struct rb_id_table *negative_cme_table; st_table *overloaded_cme_table; // cme -> overloaded_cme + st_table *unused_block_warning_table; // This id table contains a mapping from ID to ICs. It does this with ID // keys and nested st_tables as values. The nested tables have ICs as keys diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 04e7d3714ef288..ccf93fa6b553a4 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2980,9 +2980,9 @@ VALUE rb_gen_method_name(VALUE owner, VALUE name); // in vm_backtrace.c static void warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, void *pc) { - static st_table *dup_check_table = NULL; + rb_vm_t *vm = GET_VM(); + st_table *dup_check_table = vm->unused_block_warning_table; - st_data_t key = 0; union { VALUE v; unsigned char b[SIZEOF_VALUE]; @@ -2992,6 +2992,14 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, .v = (VALUE)cme->def, }; + // relax check + st_data_t key = (st_data_t)cme->def->original_id; + + if (st_lookup(dup_check_table, key, NULL)) { + return; + } + + // strict check // make unique key from pc and me->def pointer for (int i=0; idefined_class, ISEQ_BODY(iseq)->location.base_label); if (!NIL_P(m_loc)) { - rb_warning("the passed block for '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored", + rb_warning("the block passed to '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored", name, RARRAY_AREF(m_loc, 0), RARRAY_AREF(m_loc, 1)); } else { diff --git a/yjit.c b/yjit.c index ffd180429e4485..8ae010ac40b0b4 100644 --- a/yjit.c +++ b/yjit.c @@ -629,6 +629,12 @@ rb_get_iseq_body_stack_max(const rb_iseq_t *iseq) return iseq->body->stack_max; } +enum rb_iseq_type +rb_get_iseq_body_type(const rb_iseq_t *iseq) +{ + return iseq->body->type; +} + bool rb_get_iseq_flags_has_lead(const rb_iseq_t *iseq) { diff --git a/yjit.h b/yjit.h index dde9f750aaf72b..baf984eb530808 100644 --- a/yjit.h +++ b/yjit.h @@ -48,6 +48,7 @@ void rb_yjit_tracing_invalidate_all(void); void rb_yjit_show_usage(int help, int highlight, unsigned int width, int columns); void rb_yjit_lazy_push_frame(const VALUE *pc); void rb_yjit_invalidate_no_singleton_class(VALUE klass); +void rb_yjit_invalidate_ep_is_bp(const rb_iseq_t *iseq); #else // !USE_YJIT @@ -71,6 +72,7 @@ static inline void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic static inline void rb_yjit_tracing_invalidate_all(void) {} static inline void rb_yjit_lazy_push_frame(const VALUE *pc) {} static inline void rb_yjit_invalidate_no_singleton_class(VALUE klass) {} +static inline void rb_yjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {} #endif // #if USE_YJIT diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index c58df7c3773b90..d91e330f5e1eb1 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -298,6 +298,7 @@ fn main() { .allowlist_type("ruby_tag_type") .allowlist_type("ruby_vm_throw_flags") .allowlist_type("vm_check_match_type") + .allowlist_type("rb_iseq_type") // From yjit.c .allowlist_function("rb_iseq_(get|set)_yjit_payload") @@ -415,6 +416,7 @@ fn main() { .allowlist_function("rb_get_iseq_body_parent_iseq") .allowlist_function("rb_get_iseq_body_iseq_encoded") .allowlist_function("rb_get_iseq_body_stack_max") + .allowlist_function("rb_get_iseq_body_type") .allowlist_function("rb_get_iseq_flags_has_lead") .allowlist_function("rb_get_iseq_flags_has_opt") .allowlist_function("rb_get_iseq_flags_has_kw") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index d60f4f0ec1029b..9d355b854d3c83 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -46,7 +46,7 @@ type InsnGenFn = fn( /// Represents a [core::Block] while we build it. pub struct JITState { /// Instruction sequence for the compiling block - iseq: IseqPtr, + pub iseq: IseqPtr, /// The iseq index of the first instruction in the block starting_insn_idx: IseqIdx, @@ -101,6 +101,9 @@ pub struct JITState { /// A list of classes that are not supposed to have a singleton class. pub no_singleton_class_assumptions: Vec, + /// When true, the block is valid only when base pointer is equal to environment pointer. + pub no_ep_escape: bool, + /// When true, the block is valid only when there is a total of one ractor running pub block_assumes_single_ractor: bool, @@ -130,6 +133,7 @@ impl JITState { bop_assumptions: vec![], stable_constant_names_assumption: None, no_singleton_class_assumptions: vec![], + no_ep_escape: false, block_assumes_single_ractor: false, perf_map: Rc::default(), perf_stack: vec![], @@ -171,6 +175,23 @@ impl JITState { unsafe { *(self.pc.offset(arg_idx + 1)) } } + /// Return true if the current ISEQ could escape an environment. + /// + /// 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. + /// Also, some method calls escape the environment to the heap. + fn escapes_ep(&self) -> bool { + match unsafe { get_iseq_body_type(self.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, + // If this ISEQ has previously escaped EP, give up the optimization. + _ if iseq_escapes_ep(self.iseq) => true, + _ => false, + } + } + // Get the index of the next instruction fn next_insn_idx(&self) -> u16 { self.insn_idx + insn_len(self.get_opcode()) as u16 @@ -250,6 +271,19 @@ impl JITState { true } + /// Assume that base pointer is equal to environment pointer in the current ISEQ. + /// Return true if it's safe to assume so. + fn assume_no_ep_escape(&mut self, asm: &mut Assembler, ocb: &mut OutlinedCb) -> bool { + if jit_ensure_block_entry_exit(self, asm, ocb).is_none() { + return false; // out of space, give up + } + if self.escapes_ep() { + return false; // EP has been escaped in this ISEQ. disable the optimization to avoid an invalidation loop. + } + self.no_ep_escape = true; + true + } + fn get_cfp(&self) -> *mut rb_control_frame_struct { unsafe { get_ec_cfp(self.ec) } } @@ -2203,16 +2237,22 @@ fn gen_get_lep(jit: &JITState, asm: &mut Assembler) -> Opnd { fn gen_getlocal_generic( jit: &mut JITState, asm: &mut Assembler, + ocb: &mut OutlinedCb, ep_offset: u32, level: u32, ) -> Option { - // Load environment pointer EP (level 0) from CFP - let ep_opnd = gen_get_ep(asm, level); + let local_opnd = if level == 0 && jit.assume_no_ep_escape(asm, ocb) { + // Load the local using SP register + asm.ctx.ep_opnd(-(ep_offset as i32)) + } else { + // Load environment pointer EP (level 0) from CFP + let ep_opnd = gen_get_ep(asm, level); - // Load the local from the block - // val = *(vm_get_ep(GET_EP(), level) - idx); - let offs = -(SIZEOF_VALUE_I32 * ep_offset as i32); - let local_opnd = Opnd::mem(64, ep_opnd, offs); + // Load the local from the block + // val = *(vm_get_ep(GET_EP(), level) - idx); + let offs = -(SIZEOF_VALUE_I32 * ep_offset as i32); + Opnd::mem(64, ep_opnd, offs) + }; // Write the local at SP let stack_top = if level == 0 { @@ -2230,29 +2270,29 @@ fn gen_getlocal_generic( fn gen_getlocal( jit: &mut JITState, asm: &mut Assembler, - _ocb: &mut OutlinedCb, + ocb: &mut OutlinedCb, ) -> Option { let idx = jit.get_arg(0).as_u32(); let level = jit.get_arg(1).as_u32(); - gen_getlocal_generic(jit, asm, idx, level) + gen_getlocal_generic(jit, asm, ocb, idx, level) } fn gen_getlocal_wc0( jit: &mut JITState, asm: &mut Assembler, - _ocb: &mut OutlinedCb, + ocb: &mut OutlinedCb, ) -> Option { let idx = jit.get_arg(0).as_u32(); - gen_getlocal_generic(jit, asm, idx, 0) + gen_getlocal_generic(jit, asm, ocb, idx, 0) } fn gen_getlocal_wc1( jit: &mut JITState, asm: &mut Assembler, - _ocb: &mut OutlinedCb, + ocb: &mut OutlinedCb, ) -> Option { let idx = jit.get_arg(0).as_u32(); - gen_getlocal_generic(jit, asm, idx, 1) + gen_getlocal_generic(jit, asm, ocb, idx, 1) } fn gen_setlocal_generic( @@ -2264,11 +2304,11 @@ fn gen_setlocal_generic( ) -> Option { let value_type = asm.ctx.get_opnd_type(StackOpnd(0)); - // Load environment pointer EP at level - let ep_opnd = gen_get_ep(asm, level); - // Fallback because of write barrier if asm.ctx.get_chain_depth() > 0 { + // Load environment pointer EP at level + let ep_opnd = gen_get_ep(asm, level); + // This function should not yield to the GC. // void rb_vm_env_write(const VALUE *ep, int index, VALUE v) let index = -(ep_offset as i64); @@ -2286,16 +2326,27 @@ fn gen_setlocal_generic( return Some(KeepCompiling); } - // Write barriers may be required when VM_ENV_FLAG_WB_REQUIRED is set, however write barriers - // only affect heap objects being written. If we know an immediate value is being written we - // can skip this check. - if !value_type.is_imm() { - // flags & VM_ENV_FLAG_WB_REQUIRED + let (flags_opnd, local_opnd) = if level == 0 && jit.assume_no_ep_escape(asm, ocb) { + // Load flags and the local using SP register + let local_opnd = asm.ctx.ep_opnd(-(ep_offset as i32)); + let flags_opnd = asm.ctx.ep_opnd(VM_ENV_DATA_INDEX_FLAGS as i32); + (flags_opnd, local_opnd) + } else { + // Load flags and the local for the level + let ep_opnd = gen_get_ep(asm, level); let flags_opnd = Opnd::mem( 64, ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_FLAGS as i32, ); + (flags_opnd, Opnd::mem(64, ep_opnd, -SIZEOF_VALUE_I32 * ep_offset as i32)) + }; + + // Write barriers may be required when VM_ENV_FLAG_WB_REQUIRED is set, however write barriers + // only affect heap objects being written. If we know an immediate value is being written we + // can skip this check. + if !value_type.is_imm() { + // flags & VM_ENV_FLAG_WB_REQUIRED asm.test(flags_opnd, VM_ENV_FLAG_WB_REQUIRED.into()); // if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0 @@ -2319,8 +2370,7 @@ fn gen_setlocal_generic( let stack_top = asm.stack_pop(1); // Write the value at the environment pointer - let offs = -(SIZEOF_VALUE_I32 * ep_offset as i32); - asm.mov(Opnd::mem(64, ep_opnd, offs), stack_top); + asm.mov(local_opnd, stack_top); Some(KeepCompiling) } @@ -8223,6 +8273,7 @@ fn gen_struct_aset( fn gen_send_dynamic Opnd>( jit: &mut JITState, asm: &mut Assembler, + ocb: &mut OutlinedCb, cd: *const rb_call_data, sp_pops: usize, vm_sendish: F, @@ -8262,7 +8313,10 @@ fn gen_send_dynamic Opnd>( gen_counter_incr(asm, Counter::num_send_dynamic); jit_perf_symbol_pop!(jit, asm, PerfMap::Codegen); - Some(KeepCompiling) + + // End the current block for invalidationg and sharing the same successor + jump_to_next_insn(jit, asm, ocb); + Some(EndBlock) } fn gen_send_general( @@ -8776,7 +8830,7 @@ fn gen_opt_send_without_block( } // Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send - gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { + gen_send_dynamic(jit, asm, ocb, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { extern "C" { fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE; } @@ -8801,7 +8855,7 @@ fn gen_send( // Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send let blockiseq = jit.get_arg(1).as_iseq(); - gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { + gen_send_dynamic(jit, asm, ocb, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { extern "C" { fn rb_vm_send(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE; } @@ -8824,7 +8878,7 @@ fn gen_invokeblock( } // Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send - gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_invokeblock_sp_pops((*cd).ci) }, |asm| { + gen_send_dynamic(jit, asm, ocb, cd, unsafe { rb_yjit_invokeblock_sp_pops((*cd).ci) }, |asm| { extern "C" { fn rb_vm_invokeblock(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE; } @@ -8984,7 +9038,7 @@ fn gen_invokesuper( // Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send let blockiseq = jit.get_arg(1).as_iseq(); - gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { + gen_send_dynamic(jit, asm, ocb, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| { extern "C" { fn rb_vm_invokesuper(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE; } diff --git a/yjit/src/core.rs b/yjit/src/core.rs index fb7d52cc5d0f61..ccc458fe227b17 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -1657,6 +1657,9 @@ impl JITState { for klass in self.no_singleton_class_assumptions { track_no_singleton_class_assumption(blockref, klass); } + if self.no_ep_escape { + track_no_ep_escape_assumption(blockref, self.iseq); + } blockref } @@ -1798,6 +1801,13 @@ impl Context { return Opnd::mem(64, SP, offset); } + /// Get an operand for the adjusted environment pointer address using SP register. + /// This is valid only when a Binding object hasn't been created for the frame. + pub fn ep_opnd(&self, offset: i32) -> Opnd { + let ep_offset = self.get_stack_size() as i32 + 1; + self.sp_opnd(-ep_offset + offset) + } + /// Stop using a register for a given stack temp. /// This allows us to reuse the register for a value that we know is dead /// and will no longer be used (e.g. popped stack temp). @@ -3124,6 +3134,12 @@ pub fn defer_compilation( // Likely a stub due to the increased chain depth let target0_address = branch.set_target(0, blockid, &next_ctx, ocb); + // Pad the block if it has the potential to be invalidated. This must be + // done before gen_fn() in case the jump is overwritten by a fallthrough. + if jit.block_entry_exit.is_some() { + asm.pad_inval_patch(); + } + // Call the branch generation function asm_comment!(asm, "defer_compilation"); asm.mark_branch_start(&branch); @@ -3307,9 +3323,10 @@ pub fn invalidate_block_version(blockref: &BlockRef) { assert!( cb.get_write_ptr() <= block_end, - "invalidation wrote past end of block (code_size: {:?}, new_size: {})", + "invalidation wrote past end of block (code_size: {:?}, new_size: {}, start_addr: {:?})", block.code_size(), cb.get_write_ptr().as_offset() - block_start.as_offset(), + block.start_addr.raw_ptr(cb), ); cb.set_write_ptr(cur_pos); cb.set_dropped_bytes(cur_dropped_bytes); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 9547e3fa2cc67b..d07262ad4ffb2f 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -170,6 +170,7 @@ pub use rb_iseq_encoded_size as get_iseq_encoded_size; pub use rb_get_iseq_body_local_iseq as get_iseq_body_local_iseq; pub use rb_get_iseq_body_iseq_encoded as get_iseq_body_iseq_encoded; pub use rb_get_iseq_body_stack_max as get_iseq_body_stack_max; +pub use rb_get_iseq_body_type as get_iseq_body_type; pub use rb_get_iseq_flags_has_lead as get_iseq_flags_has_lead; pub use rb_get_iseq_flags_has_opt as get_iseq_flags_has_opt; pub use rb_get_iseq_flags_has_kw as get_iseq_flags_has_kw; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 359227d60db211..98d380826a69cf 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -478,6 +478,16 @@ pub struct iseq_inline_iv_cache_entry { pub struct iseq_inline_cvar_cache_entry { pub entry: *mut rb_cvar_class_tbl_entry, } +pub const ISEQ_TYPE_TOP: rb_iseq_type = 0; +pub const ISEQ_TYPE_METHOD: rb_iseq_type = 1; +pub const ISEQ_TYPE_BLOCK: rb_iseq_type = 2; +pub const ISEQ_TYPE_CLASS: rb_iseq_type = 3; +pub const ISEQ_TYPE_RESCUE: rb_iseq_type = 4; +pub const ISEQ_TYPE_ENSURE: rb_iseq_type = 5; +pub const ISEQ_TYPE_EVAL: rb_iseq_type = 6; +pub const ISEQ_TYPE_MAIN: rb_iseq_type = 7; +pub const ISEQ_TYPE_PLAIN: rb_iseq_type = 8; +pub type rb_iseq_type = u32; pub const BUILTIN_ATTR_LEAF: rb_builtin_attr = 1; pub const BUILTIN_ATTR_SINGLE_NOARG_LEAF: rb_builtin_attr = 2; pub const BUILTIN_ATTR_INLINE_BLOCK: rb_builtin_attr = 4; @@ -1153,6 +1163,7 @@ extern "C" { pub fn rb_get_iseq_body_local_table_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_iseq_body_iseq_encoded(iseq: *const rb_iseq_t) -> *mut VALUE; pub fn rb_get_iseq_body_stack_max(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; + pub fn rb_get_iseq_body_type(iseq: *const rb_iseq_t) -> rb_iseq_type; pub fn rb_get_iseq_flags_has_lead(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_opt(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_kw(iseq: *const rb_iseq_t) -> bool; diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index e4602934409dac..a5fd6b7ab5fdb0 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -59,6 +59,11 @@ pub struct Invariants { /// there has been a singleton class for the class after boot, so you cannot /// assume no singleton class going forward. no_singleton_classes: HashMap>, + + /// A map from an ISEQ to a set of blocks that assume base pointer is equal + /// to environment pointer. When the set is empty, it means that EP has been + /// escaped in the ISEQ. + no_ep_escape_iseqs: HashMap>, } /// Private singleton instance of the invariants global struct. @@ -76,6 +81,7 @@ impl Invariants { constant_state_blocks: HashMap::new(), block_constant_states: HashMap::new(), no_singleton_classes: HashMap::new(), + no_ep_escape_iseqs: HashMap::new(), }); } } @@ -154,6 +160,23 @@ pub fn has_singleton_class_of(klass: VALUE) -> bool { .map_or(false, |blocks| blocks.is_empty()) } +/// Track that a block will assume that base pointer is equal to environment pointer. +pub fn track_no_ep_escape_assumption(uninit_block: BlockRef, iseq: IseqPtr) { + Invariants::get_instance() + .no_ep_escape_iseqs + .entry(iseq) + .or_default() + .insert(uninit_block); +} + +/// Returns true if a given ISEQ has previously escaped an environment. +pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { + Invariants::get_instance() + .no_ep_escape_iseqs + .get(&iseq) + .map_or(false, |blocks| blocks.is_empty()) +} + // Checks rb_method_basic_definition_p and registers the current block for invalidation if method // lookup changes. // A "basic method" is one defined during VM boot, so we can use this to check assumptions based on @@ -420,6 +443,10 @@ pub fn block_assumptions_free(blockref: BlockRef) { for (_, blocks) in invariants.no_singleton_classes.iter_mut() { blocks.remove(&blockref); } + // Remove tracking for blocks assuming EP doesn't escape + for (_, blocks) in invariants.no_ep_escape_iseqs.iter_mut() { + blocks.remove(&blockref); + } } /// Callback from the opt_setinlinecache instruction in the interpreter. @@ -515,6 +542,34 @@ pub extern "C" fn rb_yjit_invalidate_no_singleton_class(klass: VALUE) { } } +/// Invalidate blocks for a given ISEQ that assumes environment pointer is +/// equal to base pointer. +#[no_mangle] +pub extern "C" fn rb_yjit_invalidate_ep_is_bp(iseq: IseqPtr) { + // Skip tracking EP escapes on boot. We don't need to invalidate anything during boot. + if unsafe { INVARIANTS.is_none() } { + return; + } + + // If an EP escape for this ISEQ is detected for the first time, invalidate all blocks + // associated to the ISEQ. + let no_ep_escape_iseqs = &mut Invariants::get_instance().no_ep_escape_iseqs; + match no_ep_escape_iseqs.get_mut(&iseq) { + Some(blocks) => { + // Invalidate existing blocks and let jit.ep_is_bp() + // return true when they are compiled again + for block in mem::take(blocks) { + invalidate_block_version(&block); + incr_counter!(invalidate_no_singleton_class); + } + } + None => { + // Let jit.ep_is_bp() return false for this ISEQ + no_ep_escape_iseqs.insert(iseq, HashSet::new()); + } + } +} + // Invalidate all generated code and patch C method return code to contain // logic for firing the c_return TracePoint event. Once rb_vm_barrier() // returns, all other ractors are pausing inside RB_VM_LOCK_ENTER(), which