diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index e5f055f8c43242..6865c218b1a799 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -51,11 +51,12 @@ jobs:
- name: Install libraries
run: |
brew upgrade
- brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline
+ brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline bison
working-directory: src
- name: Set ENV
run: |
echo "MAKEFLAGS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV
+ echo "PATH="/usr/local/opt/bison/bin:$PATH"" >> $GITHUB_ENV
- run: ./autogen.sh
working-directory: src
- name: Run configure
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
new file mode 100644
index 00000000000000..751180d1d20536
--- /dev/null
+++ b/.github/workflows/scorecards.yml
@@ -0,0 +1,72 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecards supply-chain security
+on:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '22 4 * * 2'
+ push:
+ branches: [ "master" ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecards analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecards on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+ repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard.
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27
+ with:
+ sarif_file: results.sarif
diff --git a/NEWS.md b/NEWS.md
index 0367e8bfd0eb1b..19f5546fca1645 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -232,7 +232,7 @@ Note: We're only listing outstanding class updates.
* cgi 0.3.3
* date 3.2.3
* erb 3.0.0
- * error_highlight 0.4.0
+ * error_highlight 0.5.0
* etc 1.4.0
* fiddle 1.1.1
* io-console 0.5.11
@@ -291,6 +291,11 @@ The following deprecated methods are removed.
* `Kernel#trust`, `Kernel#untrust`, `Kernel#untrusted?`
[[Feature #16131]]
+### Source code incompatiblity of extension libraries [[Bug #19100]]
+
+* Extension libraries provide PRNG, subclasses of `Random`, need updates.
+ See [PRNG update] below for more information.
+
## Stdlib compatibility issues
* `Psych` no longer bundles libyaml sources.
@@ -300,6 +305,16 @@ The following deprecated methods are removed.
## C API updates
+### Updated C APIs
+
+The following APIs are updated.
+
+* PRNG update
+
+ `rb_random_interface_t` updated and versioned.
+ Extension libraries which use this interface and built for older versions.
+ Also `init_int32` function needs to be defined.
+
### Removed C APIs
The following deprecated APIs are removed.
@@ -400,3 +415,4 @@ The following deprecated APIs are removed.
[Feature #19013]: https://bugs.ruby-lang.org/issues/19013
[Feature #19026]: https://bugs.ruby-lang.org/issues/19026
[Feature #19060]: https://bugs.ruby-lang.org/issues/19060
+[Bug #19100]: https://bugs.ruby-lang.org/issues/19100
diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb
index dfcec633d63fa5..e77dce8b85d2b4 100755
--- a/bootstraptest/runner.rb
+++ b/bootstraptest/runner.rb
@@ -108,10 +108,16 @@ def putc(c)
def wn=(wn)
unless wn == 1
- if /(?:\A|\s)--jobserver-(?:auth|fds)=\K(\d+),(\d+)/ =~ ENV.delete("MAKEFLAGS")
+ if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS")
begin
- r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
- w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
+ if fifo = $3
+ fifo.gsub!(/\\(?=.)/, '')
+ r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY)
+ w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY)
+ else
+ r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
+ w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
+ end
rescue => e
r.close if r
else
diff --git a/class.c b/class.c
index d181fb0b2ee635..85663ada50ab79 100644
--- a/class.c
+++ b/class.c
@@ -197,7 +197,7 @@ class_alloc(VALUE flags, VALUE klass)
{
size_t alloc_size = sizeof(struct RClass);
-#if USE_RVARGC
+#if RCLASS_EXT_EMBEDDED
alloc_size += sizeof(rb_classext_t);
#endif
@@ -206,7 +206,7 @@ class_alloc(VALUE flags, VALUE klass)
if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED;
RVARGC_NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size);
-#if USE_RVARGC
+#if RCLASS_EXT_EMBEDDED
memset(RCLASS_EXT(obj), 0, sizeof(rb_classext_t));
#else
obj->ptr = ZALLOC(rb_classext_t);
diff --git a/common.mk b/common.mk
index e03af24cfcb04a..51c75df94d31ce 100644
--- a/common.mk
+++ b/common.mk
@@ -303,7 +303,7 @@ showflags:
" LC_ALL = $(LC_ALL)" \
" LC_CTYPE = $(LC_CTYPE)" \
" MFLAGS = $(MFLAGS)" \
- " RUST = $(RUST)" \
+ " RUSTC = $(RUSTC)" \
" YJIT_RUSTC_ARGS = $(YJIT_RUSTC_ARGS)" \
$(MESSAGE_END)
-@$(CC_VERSION)
@@ -956,8 +956,6 @@ PHONY:
{$(srcdir)}.y.c:
$(ECHO) generating $@
$(Q)$(BASERUBY) $(tooldir)/id2token.rb $(SRC_FILE) > parse.tmp.y
- $(Q)$(BASERUBY) $(tooldir)/pure_parser.rb parse.tmp.y $(YACC)
- $(Q)$(RM) parse.tmp.y.bak
$(Q)$(YACC) -d $(YFLAGS) -o y.tab.c parse.tmp.y
$(Q)$(RM) parse.tmp.y
$(Q)sed -f $(tooldir)/ytab.sed -e "/^#/s|parse\.tmp\.[iy]|$(SRC_FILE)|" -e "/^#/s!y\.tab\.c!$@!" y.tab.c > $@.new
@@ -6040,6 +6038,7 @@ enumerator.$(OBJEXT): {$(VPATH)}missing.h
enumerator.$(OBJEXT): {$(VPATH)}onigmo.h
enumerator.$(OBJEXT): {$(VPATH)}oniguruma.h
enumerator.$(OBJEXT): {$(VPATH)}ruby_assert.h
+enumerator.$(OBJEXT): {$(VPATH)}shape.h
enumerator.$(OBJEXT): {$(VPATH)}st.h
enumerator.$(OBJEXT): {$(VPATH)}subst.h
error.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
@@ -9378,6 +9377,7 @@ memory_view.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
memory_view.$(OBJEXT): {$(VPATH)}memory_view.c
memory_view.$(OBJEXT): {$(VPATH)}memory_view.h
memory_view.$(OBJEXT): {$(VPATH)}missing.h
+memory_view.$(OBJEXT): {$(VPATH)}shape.h
memory_view.$(OBJEXT): {$(VPATH)}st.h
memory_view.$(OBJEXT): {$(VPATH)}subst.h
memory_view.$(OBJEXT): {$(VPATH)}util.h
@@ -10631,6 +10631,7 @@ object.$(OBJEXT): {$(VPATH)}shape.h
object.$(OBJEXT): {$(VPATH)}st.h
object.$(OBJEXT): {$(VPATH)}subst.h
object.$(OBJEXT): {$(VPATH)}util.h
+object.$(OBJEXT): {$(VPATH)}variable.h
pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h
pack.$(OBJEXT): $(top_srcdir)/internal/array.h
pack.$(OBJEXT): $(top_srcdir)/internal/bits.h
@@ -10812,6 +10813,7 @@ pack.$(OBJEXT): {$(VPATH)}onigmo.h
pack.$(OBJEXT): {$(VPATH)}oniguruma.h
pack.$(OBJEXT): {$(VPATH)}pack.c
pack.$(OBJEXT): {$(VPATH)}pack.rbinc
+pack.$(OBJEXT): {$(VPATH)}shape.h
pack.$(OBJEXT): {$(VPATH)}st.h
pack.$(OBJEXT): {$(VPATH)}subst.h
pack.$(OBJEXT): {$(VPATH)}util.h
@@ -11024,6 +11026,7 @@ parse.$(OBJEXT): {$(VPATH)}ractor.h
parse.$(OBJEXT): {$(VPATH)}regenc.h
parse.$(OBJEXT): {$(VPATH)}regex.h
parse.$(OBJEXT): {$(VPATH)}ruby_assert.h
+parse.$(OBJEXT): {$(VPATH)}shape.h
parse.$(OBJEXT): {$(VPATH)}st.h
parse.$(OBJEXT): {$(VPATH)}subst.h
parse.$(OBJEXT): {$(VPATH)}symbol.h
@@ -11855,6 +11858,7 @@ random.$(OBJEXT): {$(VPATH)}ractor.h
random.$(OBJEXT): {$(VPATH)}random.c
random.$(OBJEXT): {$(VPATH)}random.h
random.$(OBJEXT): {$(VPATH)}ruby_atomic.h
+random.$(OBJEXT): {$(VPATH)}shape.h
random.$(OBJEXT): {$(VPATH)}siphash.c
random.$(OBJEXT): {$(VPATH)}siphash.h
random.$(OBJEXT): {$(VPATH)}st.h
@@ -12047,6 +12051,7 @@ range.$(OBJEXT): {$(VPATH)}missing.h
range.$(OBJEXT): {$(VPATH)}onigmo.h
range.$(OBJEXT): {$(VPATH)}oniguruma.h
range.$(OBJEXT): {$(VPATH)}range.c
+range.$(OBJEXT): {$(VPATH)}shape.h
range.$(OBJEXT): {$(VPATH)}st.h
range.$(OBJEXT): {$(VPATH)}subst.h
rational.$(OBJEXT): $(hdrdir)/ruby/ruby.h
@@ -14026,6 +14031,7 @@ shape.$(OBJEXT): {$(VPATH)}constant.h
shape.$(OBJEXT): {$(VPATH)}debug_counter.h
shape.$(OBJEXT): {$(VPATH)}defines.h
shape.$(OBJEXT): {$(VPATH)}encoding.h
+shape.$(OBJEXT): {$(VPATH)}gc.h
shape.$(OBJEXT): {$(VPATH)}id.h
shape.$(OBJEXT): {$(VPATH)}id_table.h
shape.$(OBJEXT): {$(VPATH)}intern.h
@@ -16009,6 +16015,7 @@ time.$(OBJEXT): {$(VPATH)}missing.h
time.$(OBJEXT): {$(VPATH)}onigmo.h
time.$(OBJEXT): {$(VPATH)}oniguruma.h
time.$(OBJEXT): {$(VPATH)}ruby_assert.h
+time.$(OBJEXT): {$(VPATH)}shape.h
time.$(OBJEXT): {$(VPATH)}st.h
time.$(OBJEXT): {$(VPATH)}subst.h
time.$(OBJEXT): {$(VPATH)}time.c
@@ -16373,6 +16380,7 @@ transient_heap.$(OBJEXT): {$(VPATH)}internal/warning_push.h
transient_heap.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
transient_heap.$(OBJEXT): {$(VPATH)}missing.h
transient_heap.$(OBJEXT): {$(VPATH)}ruby_assert.h
+transient_heap.$(OBJEXT): {$(VPATH)}shape.h
transient_heap.$(OBJEXT): {$(VPATH)}st.h
transient_heap.$(OBJEXT): {$(VPATH)}subst.h
transient_heap.$(OBJEXT): {$(VPATH)}transient_heap.c
diff --git a/compile.c b/compile.c
index 359d55c0f26eeb..d8d2738eb8919b 100644
--- a/compile.c
+++ b/compile.c
@@ -7449,7 +7449,30 @@ compile_iter(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
ISEQ_TYPE_BLOCK, line);
CHECK(COMPILE(ret, "iter caller", node->nd_iter));
}
- ADD_LABEL(ret, retry_end_l);
+
+ {
+ // We need to put the label "retry_end_l" immediately after the last "send" instruction.
+ // This because vm_throw checks if the break cont is equal to the index of next insn of the "send".
+ // (Otherwise, it is considered "break from proc-closure". See "TAG_BREAK" handling in "vm_throw_start".)
+ //
+ // Normally, "send" instruction is at the last.
+ // However, qcall under branch coverage measurement adds some instructions after the "send".
+ //
+ // Note that "invokesuper" appears instead of "send".
+ INSN *iobj;
+ LINK_ELEMENT *last_elem = LAST_ELEMENT(ret);
+ iobj = IS_INSN(last_elem) ? (INSN*) last_elem : (INSN*) get_prev_insn((INSN*) last_elem);
+ while (INSN_OF(iobj) != BIN(send) && INSN_OF(iobj) != BIN(invokesuper)) {
+ iobj = (INSN*) get_prev_insn(iobj);
+ }
+ ELEM_INSERT_NEXT(&iobj->link, (LINK_ELEMENT*) retry_end_l);
+
+ // LINK_ANCHOR has a pointer to the last element, but ELEM_INSERT_NEXT does not update it
+ // even if we add an insn to the last of LINK_ANCHOR. So this updates it manually.
+ if (&iobj->link == LAST_ELEMENT(ret)) {
+ ret->last = (LINK_ELEMENT*) retry_end_l;
+ }
+ }
if (popped) {
ADD_INSN(ret, line_node, pop);
diff --git a/configure.ac b/configure.ac
index 93fdb5fed7ebd7..46caa99e7e39bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2965,6 +2965,23 @@ STATIC=
])
}
+EXTSTATIC=
+AC_SUBST(EXTSTATIC)dnl
+AC_ARG_WITH(static-linked-ext,
+ AS_HELP_STRING([--with-static-linked-ext], [link external modules statically]),
+ [AS_CASE([$withval],[yes],[STATIC=;EXTSTATIC=static],[no],[],[EXTSTATIC="$withval"])])
+AS_CASE([",$EXTSTATIC,"], [,static,|*,enc,*], [
+ ENCOBJS='enc/encinit.$(OBJEXT) enc/libenc.$(LIBEXT) enc/libtrans.$(LIBEXT)'
+ EXTOBJS='ext/extinit.$(OBJEXT)'
+ AC_DEFINE_UNQUOTED(EXTSTATIC, 1)
+ AC_SUBST(ENCSTATIC, static)
+], [
+ ENCOBJS='dmyenc.$(OBJEXT)'
+ EXTOBJS='dmyext.$(OBJEXT)'
+])
+AC_SUBST(ENCOBJS)
+AC_SUBST(EXTOBJS)
+
: "rpath" && {
AS_CASE(["$target_os"],
[solaris*], [ AS_IF([test "$GCC" = yes], [
@@ -3269,23 +3286,6 @@ AC_ARG_WITH(ext,
AC_ARG_WITH(out-ext,
AS_HELP_STRING([--with-out-ext=EXTS],
[pass to --without-ext option of extmk.rb]))
-EXTSTATIC=
-AC_SUBST(EXTSTATIC)dnl
-AC_ARG_WITH(static-linked-ext,
- AS_HELP_STRING([--with-static-linked-ext], [link external modules statically]),
- [AS_CASE([$withval],[yes],[STATIC=;EXTSTATIC=static],[no],[],[EXTSTATIC="$withval"])])
-AS_CASE([",$EXTSTATIC,"], [,static,|*,enc,*], [
- ENCOBJS='enc/encinit.$(OBJEXT) enc/libenc.$(LIBEXT) enc/libtrans.$(LIBEXT)'
- EXTOBJS='ext/extinit.$(OBJEXT)'
- AC_DEFINE_UNQUOTED(EXTSTATIC, 1)
- AC_SUBST(ENCSTATIC, static)
-], [
- ENCOBJS='dmyenc.$(OBJEXT)'
- EXTOBJS='dmyext.$(OBJEXT)'
-])
-AC_SUBST(ENCOBJS)
-AC_SUBST(EXTOBJS)
-
AC_ARG_WITH(setup,
AS_HELP_STRING([--with-setup=SETUP], [use extension libraries setup]),
[setup=$withval])
@@ -3738,18 +3738,34 @@ AC_SUBST(MJIT_SUPPORT)
AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix
+dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0)
+YJIT_RUSTC_OK=no
+AS_IF([test "$RUSTC" != "no"],
+ AC_MSG_CHECKING([whether ${RUSTC} is new enough for YJIT])
+ AS_IF([echo "fn main() { let x = 1; format!(\"{x}\"); }" | $RUSTC - --emit asm=/dev/null 2>/dev/null],
+ [YJIT_RUSTC_OK=yes]
+ )
+ AC_MSG_RESULT($YJIT_RUSTC_OK)
+)
+
dnl check if we can build YJIT on this target platform
-AS_CASE(["$target_cpu-$target_os"],
- [arm64-darwin*|aarch64-darwin*|x86_64-darwin*], [
- YJIT_TARGET_OK=yes
- ],
- [arm64-*linux*|aarch64-*linux*|x86_64-*linux*], [
- YJIT_TARGET_OK=yes
- ],
- [arm64-*bsd*|aarch64-*bsd*|x86_64-*bsd*], [
- YJIT_TARGET_OK=yes
- ],
- [YJIT_TARGET_OK=no]
+dnl we can't easily cross-compile with rustc so we don't support that
+YJIT_TARGET_OK=no
+AS_IF([test "$cross_compiling" = no],
+ AS_CASE(["$target_cpu-$target_os"],
+ [*android*], [
+ YJIT_TARGET_OK=no
+ ],
+ [arm64-darwin*|aarch64-darwin*|x86_64-darwin*], [
+ YJIT_TARGET_OK=yes
+ ],
+ [arm64-*linux*|aarch64-*linux*|x86_64-*linux*], [
+ YJIT_TARGET_OK=yes
+ ],
+ [arm64-*bsd*|aarch64-*bsd*|x86_64-*bsd*], [
+ YJIT_TARGET_OK=yes
+ ]
+ )
)
dnl build YJIT in release mode if rustc >= 1.58.0 is present and we are on a supported platform
@@ -3757,15 +3773,12 @@ AC_ARG_ENABLE(yjit,
AS_HELP_STRING([--enable-yjit],
[enable experimental in-process JIT compiler that requires Rust build tools [default=no]]),
[YJIT_SUPPORT=$enableval],
- [
- AS_IF([test x"$RUSTC" != "xno"],
- AS_IF([ echo "fn main() { let x = 1; format!(\"{x}\"); }" | $RUSTC - --emit asm=/dev/null ],
- [YJIT_SUPPORT="$YJIT_TARGET_OK"],
- [YJIT_SUPPORT=no]
- ),
- [YJIT_SUPPORT=no]
- )
- ]
+ [AS_CASE(["$enable_jit_support:$YJIT_TARGET_OK:$YJIT_RUSTC_OK"],
+ [yes:yes:yes|:yes:yes], [
+ YJIT_SUPPORT=yes
+ ],
+ [YJIT_SUPPORT=no]
+ )]
)
CARGO=
@@ -3779,7 +3792,6 @@ AS_CASE(["${YJIT_SUPPORT}"],
AS_IF([test x"$RUSTC" = "xno"],
AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install])
)
- AS_IF([test "$cross_compiling" = yes], [RUSTC="$RUSTC --target=$target"])
AS_CASE(["${YJIT_SUPPORT}"],
[yes], [
@@ -3797,6 +3809,7 @@ AS_CASE(["${YJIT_SUPPORT}"],
[stats], [
rb_rust_target_subdir=stats
CARGO_BUILD_ARGS='--profile stats --features stats'
+ AC_DEFINE(YJIT_STATS, 1)
])
AS_IF([test -n "${CARGO_BUILD_ARGS}"], [
diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md
index ace5fbed37c5e8..33ed732a2bc458 100644
--- a/doc/contributing/building_ruby.md
+++ b/doc/contributing/building_ruby.md
@@ -6,7 +6,7 @@
* C compiler
* autoconf - 2.67 or later
- * bison - 2.0 or later
+ * bison - 3.0 or later
* gperf - 3.0.3 or later
* ruby - 2.7 or later
@@ -26,23 +26,54 @@
git clone https://github.com/ruby/ruby.git
```
-4. Generate the configuration files and build. It's generally advisable to use a build directory:
+4. Generate the configure file:
```
./autogen.sh
- mkdir build && cd build # it's good practice to build outside of source dir
- mkdir ~/.rubies # we will install to .rubies/ruby-master in our home dir
+ ```
+
+5. Create a `build` directory outside of the source directory:
+
+ ```
+ mkdir build && cd build
+ ```
+
+ While it's not necessary to build in a separate directory, it's good practice to do so.
+
+6. We'll install Ruby in `~/.rubies/ruby-master`, so create the directory:
+
+ ```
+ mkdir ~/.rubies
+ ```
+
+7. Run configure:
+
+ ```
../configure --prefix="${HOME}/.rubies/ruby-master"
- make install
```
-5. Optional: If you are frequently building Ruby, disabling documentation will reduce the time it takes to `make`:
+ - If you are frequently building Ruby, add the `--disable-install-doc` flag to not build documentation which will speed up the build process.
- ``` shell
- ../configure --prefix="${HOME}/.rubies/ruby-master" --disable-install-doc
+8. Build Ruby:
+
+ ```
+ make install
```
-6. [Run tests](testing_ruby.md) to confirm your build succeeded
+ - If you're on macOS and installed \OpenSSL through Homebrew, you may encounter failure to build \OpenSSL that look like this:
+
+ ```
+ openssl:
+ Could not be configured. It will not be installed.
+ ruby/ext/openssl/extconf.rb: OpenSSL library could not be found. You might want to use --with-openssl-dir=
option to specify the prefix where OpenSSL is installed.
+ Check ext/openssl/mkmf.log for more details.
+ ```
+
+ Adding `--with-openssl-dir=$(brew --prefix openssl)` to the list of options passed to configure may solve the issue.
+
+ Remember to delete your `build` directory and start again from the configure step.
+
+9. [Run tests](testing_ruby.md) to confirm your build succeeded.
### Unexplainable Build Errors
diff --git a/doc/io_streams.rdoc b/doc/io_streams.rdoc
deleted file mode 100644
index c8ce9991cfcce9..00000000000000
--- a/doc/io_streams.rdoc
+++ /dev/null
@@ -1,517 +0,0 @@
-== \IO Streams
-
-This page describes:
-
-- {Stream classes}[rdoc-ref:io_streams.rdoc@Stream+Classes].
-- {Pre-existing streams}[rdoc-ref:io_streams.rdoc@Pre-Existing+Streams].
-- {User-created streams}[rdoc-ref:io_streams.rdoc@User-Created+Streams].
-- {Basic \IO}[rdoc-ref:io_streams.rdoc@Basic+IO], including:
-
- - {Position}[rdoc-ref:io_streams.rdoc@Position].
- - {Open and closed streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams].
- - {End-of-stream}[rdoc-ref:io_streams.rdoc@End-of-Stream].
-
-- {Line \IO}[rdoc-ref:io_streams.rdoc@Line+IO], including:
-
- - {Line separator}[rdoc-ref:io_streams.rdoc@Line+Separator].
- - {Line limit}[rdoc-ref:io_streams.rdoc@Line+Limit].
- - {Line number}[rdoc-ref:io_streams.rdoc@Line+Number].
- - {Line options}[rdoc-ref:io_streams.rdoc@Line+Options].
-
-- {Character \IO}[rdoc-ref:io_streams.rdoc@Character+IO].
-- {Byte \IO}[rdoc-ref:io_streams.rdoc@Byte+IO].
-- {Codepoint \IO}[rdoc-ref:io_streams.rdoc@Codepoint+IO].
-
-=== Stream Classes
-
-Ruby supports processing data as \IO streams;
-that is, as data that may be read, re-read, written, re-written,
-and traversed via iteration.
-
-Core classes with such support include:
-
-- IO, and its derived class File.
-- {StringIO}[rdoc-ref:StringIO]: for processing a string.
-- {ARGF}[rdoc-ref:ARGF]: for processing files cited on the command line.
-
-Except as noted, the instance methods described on this page
-are available in classes \ARGF, \File, \IO, and \StringIO.
-A few, also noted, are available in class \Kernel.
-
-=== Pre-Existing Streams
-
-Pre-existing streams that are referenced by constants include:
-
-- $stdin: read-only instance of \IO.
-- $stdout: write-only instance of \IO.
-- $stderr: read-only instance of \IO.
-- \ARGF: read-only instance of \ARGF.
-
-=== User-Created Streams
-
-You can create streams:
-
-- \File:
-
- - File.new: returns a new \File object;
- the file should be closed when no longer needed.
- - File.open: passes a new \File object to given the block;
- the file is automatically closed on block exit.
-
-- \IO:
-
- - IO.new: returns a new \IO object for the given integer file descriptor;
- the \IO object should be closed when no longer needed.
- - IO.open: passes a new \IO object to the given block;
- the \IO object is automatically closed on block exit.
- - IO.popen: returns a new \IO object that is connected to the $stdin
- and $stdout of a newly-launched subprocess.
- - Kernel#open: returns a new \IO object connected to a given source:
- stream, file, or subprocess;
- the \IO object should be closed when no longer needed.
-
-- \StringIO:
-
- - StringIO.new: returns a new \StringIO object;
- the \StringIO object should be closed when no longer needed.
- - StringIO.open: passes a new \StringIO object to the given block;
- the \StringIO object is automatically closed on block exit.
-
-(You cannot create an \ARGF object, but one already exists.)
-
-=== About the Examples
-
-Many examples here use these variables:
-
- :include: doc/examples/files.rdoc
-
-=== Basic \IO
-
-You can perform basic stream \IO with these methods:
-
-- IO#read: Returns all remaining or the next _n_ bytes read from the stream,
- for a given _n_:
-
- f = File.new('t.txt')
- f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
- f.rewind
- f.read(30) # => "First line\r\nSecond line\r\n\r\nFou"
- f.read(30) # => "rth line\r\nFifth line\r\n"
- f.read(30) # => nil
- f.close
-
-- IO#write: Writes one or more given strings to the stream:
-
- $stdout.write('Hello', ', ', 'World!', "\n") # => 14
- $stdout.write('foo', :bar, 2, "\n")
-
- Output:
-
- Hello, World!
- foobar2
-
-==== Position
-
-An \IO stream has a nonnegative integer _position_,
-which is the byte offset at which the next read or write is to occur.
-A new stream has position zero (and line number zero);
-method +rewind+ resets the position (and line number) to zero.
-
-The relevant methods:
-
-- IO#tell (aliased as +#pos+):
- Returns the current position (in bytes) in the stream:
-
- f = File.new('t.txt')
- f.tell # => 0
- f.gets # => "First line\n"
- f.tell # => 12
- f.close
-
-- IO#pos=: Sets the position of the stream (in bytes):
-
- f = File.new('t.txt')
- f.tell # => 0
- f.pos = 20 # => 20
- f.tell # => 20
- f.close
-
-- IO#seek: Sets the position of the stream to a given integer +offset+
- (in bytes), with respect to a given constant +whence+, which is one of:
-
- - +:CUR+ or IO::SEEK_CUR:
- Repositions the stream to its current position plus the given +offset+:
-
- f = File.new('t.txt')
- f.tell # => 0
- f.seek(20, :CUR) # => 0
- f.tell # => 20
- f.seek(-10, :CUR) # => 0
- f.tell # => 10
- f.close
-
- - +:END+ or IO::SEEK_END:
- Repositions the stream to its end plus the given +offset+:
-
- f = File.new('t.txt')
- f.tell # => 0
- f.seek(0, :END) # => 0 # Repositions to stream end.
- f.tell # => 52
- f.seek(-20, :END) # => 0
- f.tell # => 32
- f.seek(-40, :END) # => 0
- f.tell # => 12
- f.close
-
- - +:SET+ or IO:SEEK_SET:
- Repositions the stream to the given +offset+:
-
- f = File.new('t.txt')
- f.tell # => 0
- f.seek(20, :SET) # => 0
- f.tell # => 20
- f.seek(40, :SET) # => 0
- f.tell # => 40
- f.close
-
-- IO#rewind: Positions the stream to the beginning (also resetting the line number):
-
- f = File.new('t.txt')
- f.tell # => 0
- f.gets # => "First line\n"
- f.tell # => 12
- f.rewind # => 0
- f.tell # => 0
- f.lineno # => 0
- f.close
-
-==== Open and Closed Streams
-
-A new \IO stream may be open for reading, open for writing, or both.
-
-A stream is automatically closed when claimed by the garbage collector.
-
-Attempted reading or writing on a closed stream raises an exception.
-
-- IO#close: Closes the stream for both reading and writing.
-- IO#close_read: Closes the stream for reading; not in ARGF.
-- IO#close_write: Closes the stream for writing; not in ARGF.
-- IO#closed?: Returns whether the stream is closed.
-
-==== End-of-Stream
-
-You can query whether a stream is positioned at its end using
-method IO#eof? (also aliased as +#eof+).
-
-You can reposition to end-of-stream by reading all stream content:
-
- f = File.new('t.txt')
- f.eof? # => false
- f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
- f.eof? # => true
-
-Or by using method IO#seek:
-
- f = File.new('t.txt')
- f.eof? # => false
- f.seek(0, :END)
- f.eof? # => true
-
-=== Line \IO
-
-You can read an \IO stream line-by-line using these methods:
-
-- IO#each_line: Passes each line to the block:
-
- f = File.new('t.txt')
- f.each_line {|line| p line }
-
- Output:
-
- "First line\n"
- "Second line\n"
- "\n"
- "Fourth line\n"
- "Fifth line\n"
-
- The reading may begin mid-line:
-
- f = File.new('t.txt')
- f.pos = 27
- f.each_line {|line| p line }
-
- Output:
-
- "rth line\n"
- "Fifth line\n"
-
-- IO#gets (also in Kernel): Returns the next line (which may begin mid-line):
-
- f = File.new('t.txt')
- f.gets # => "First line\n"
- f.gets # => "Second line\n"
- f.pos = 27
- f.gets # => "rth line\n"
- f.readlines # => ["Fifth line\n"]
- f.gets # => nil
-
-- IO#readline (also in Kernel; not in StringIO):
- Like #gets, but raises an exception at end-of-stream.
-
-- IO#readlines (also in Kernel): Returns all remaining lines in an array;
- may begin mid-line:
-
- f = File.new('t.txt')
- f.pos = 19
- f.readlines # => ["ine\n", "\n", "Fourth line\n", "Fifth line\n"]
- f.readlines # => []
-
-Each of these reader methods may be called with:
-
-- An optional line separator, +sep+.
-- An optional line-size limit, +limit+.
-- Both +sep+ and +limit+.
-
-You can write to an \IO stream line-by-line using this method:
-
-- IO#puts (also in Kernel; not in \StringIO): Writes objects to the stream:
-
- f = File.new('t.tmp', 'w')
- f.puts('foo', :bar, 1, 2.0, Complex(3, 0))
- f.flush
- File.read('t.tmp') # => "foo\nbar\n1\n2.0\n3+0i\n"
-
-==== Line Separator
-
-The default line separator is the given by the global variable $/,
-whose value is by default "\n".
-The line to be read next is all data from the current position
-to the next line separator:
-
- f = File.new('t.txt')
- f.gets # => "First line\n"
- f.gets # => "Second line\n"
- f.gets # => "\n"
- f.gets # => "Fourth line\n"
- f.gets # => "Fifth line\n"
- f.close
-
-You can specify a different line separator:
-
- f = File.new('t.txt')
- f.gets('l') # => "First l"
- f.gets('li') # => "ine\nSecond li"
- f.gets('lin') # => "ne\n\nFourth lin"
- f.gets # => "e\n"
- f.close
-
-There are two special line separators:
-
-- +nil+: The entire stream is read into a single string:
-
- f = File.new('t.txt')
- f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
- f.close
-
-- '' (the empty string): The next "paragraph" is read
- (paragraphs being separated by two consecutive line separators):
-
- f = File.new('t.txt')
- f.gets('') # => "First line\nSecond line\n\n"
- f.gets('') # => "Fourth line\nFifth line\n"
- f.close
-
-==== Line Limit
-
-The line to be read may be further defined by an optional integer argument +limit+,
-which specifies that the number of bytes returned may not be (much) longer
-than the given +limit+;
-a multi-byte character will not be split, and so a line may be slightly longer
-than the given limit.
-
-If +limit+ is not given, the line is determined only by +sep+.
-
- # Text with 1-byte characters.
- File.new('t.txt') {|f| f.gets(1) } # => "F"
- File.new('t.txt') {|f| f.gets(2) } # => "Fi"
- File.new('t.txt') {|f| f.gets(3) } # => "Fir"
- File.new('t.txt') {|f| f.gets(4) } # => "Firs"
- # No more than one line.
- File.new('t.txt') {|f| f.gets(10) } # => "First line"
- File.new('t.txt') {|f| f.gets(11) } # => "First line\n"
- File.new('t.txt') {|f| f.gets(12) } # => "First line\n"
-
- # Text with 2-byte characters, which will not be split.
- File.new('r.rus') {|f| f.gets(1).size } # => 1
- File.new('r.rus') {|f| f.gets(2).size } # => 1
- File.new('r.rus') {|f| f.gets(3).size } # => 2
- File.new('r.rus') {|f| f.gets(4).size } # => 2
-
-==== Line Separator and Line Limit
-
-With arguments +sep+ and +limit+ given,
-combines the two behaviors:
-
-- Returns the next line as determined by line separator +sep+.
-- But returns no more bytes than are allowed by the limit.
-
-Example:
-
- File.new('t.txt') {|f| f.gets('li', 20) } # => "First li"
- File.new('t.txt') {|f| f.gets('li', 2) } # => "Fi"
-
-==== Line Number
-
-A readable \IO stream has a _line_ _number_,
-which is the non-negative integer line number
-in the stream where the next read will occur.
-
-The line number is the number of lines read by certain line-oriented methods
-(IO.foreach, IO#each_line, IO#gets, IO#readline, and IO#readlines)
-according to the given (or default) line separator +sep+.
-
-A new stream is initially has line number zero (and position zero);
-method +rewind+ resets the line number (and position) to zero.
-
-\Method IO#lineno returns the line number.
-
-Reading lines from a stream usually changes its line number:
-
- f = File.new('t.txt', 'r')
- f.lineno # => 0
- f.readline # => "This is line one.\n"
- f.lineno # => 1
- f.readline # => "This is the second line.\n"
- f.lineno # => 2
- f.readline # => "Here's the third line.\n"
- f.lineno # => 3
- f.eof? # => true
- f.close
-
-Iterating over lines in a stream usually changes its line number:
-
- File.open('t.txt') do |f|
- f.each_line do |line|
- p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}"
- end
- end
-
-Output:
-
- "position=11 eof?=false lineno=1"
- "position=23 eof?=false lineno=2"
- "position=24 eof?=false lineno=3"
- "position=36 eof?=false lineno=4"
- "position=47 eof?=true lineno=5"
-
-==== Line Options
-
-A number of \IO methods accept optional keyword arguments
-that determine how lines in a stream are to be treated:
-
-- +:chomp+: If +true+, line separators are omitted; default is +false+.
-
-=== Character \IO
-
-You can process an \IO stream character-by-character using these methods:
-
-- IO#getc: Reads and returns the next character from the stream:
-
- f = File.new('t.rus')
- f.getc # => "т"
- f.getc # => "е"
- f.getc # => "с"
- f.getc # => "т"
- f.getc # => nil
-
-- IO#readchar (not in \StringIO):
- Like #getc, but raises an exception at end-of-stream:
-
- f.readchar # Raises EOFError.
-
-- IO#ungetc (not in \ARGF):
- Pushes back ("unshifts") a character or integer onto the stream:
-
- path = 't.tmp'
- File.write(path, 'foo')
- File.open(path) do |f|
- f.ungetc('т')
- f.read # => "тfoo"
- end
-
-- IO#putc (also in Kernel): Writes a character to the stream:
-
- File.open('t.tmp', 'w') do |f|
- f.putc('т')
- f.putc('е')
- f.putc('с')
- f.putc('т')
- end
- File.read('t.tmp') # => "тест"
-
-- IO#each_char: Reads each remaining character in the stream,
- passing the character to the given block:
-
- File.open('t.rus') do |f|
- f.pos = 4
- f.each_char {|c| p c }
- end
-
- Output:
-
- "с"
- "т"
-
-=== Byte \IO
-
-You can process an \IO stream byte-by-byte using these methods:
-
-- IO#getbyte: Returns the next 8-bit byte as an integer in range 0..255:
-
- File.read('t.dat')
- # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"
- File.read('t.dat')
- # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"
- f = File.new('t.dat')
- f.getbyte # => 254
- f.getbyte # => 255
- f.seek(-2, :END)
- f.getbyte # => 153
- f.getbyte # => 148
- f.getbyte # => nil
-
-- IO#readbyte (not in \StringIO):
- Like #getbyte, but raises an exception if at end-of-stream:
-
- f.readbyte # Raises EOFError.
-
-- IO#ungetbyte (not in \ARGF):
- Pushes back ("unshifts") a byte back onto the stream:
-
- f.ungetbyte(0)
- f.ungetbyte(01)
- f.read # => "\u0001\u0000"
-
-- IO#each_byte: Reads each remaining byte in the stream,
- passing the byte to the given block:
-
- f.seek(-4, :END)
- f.each_byte {|b| p b }
-
- Output:
-
- 153
- 147
- 153
- 148
-
-=== Codepoint \IO
-
-You can process an \IO stream codepoint-by-codepoint using method
-+#each_codepoint+:
-
- a = []
- File.open('t.rus') do |f|
- f.each_codepoint {|c| a << c }
- end
- a # => [1090, 1077, 1089, 1090]
diff --git a/ext/-test-/random/bad_version.c b/ext/-test-/random/bad_version.c
new file mode 100644
index 00000000000000..dae63a6d190171
--- /dev/null
+++ b/ext/-test-/random/bad_version.c
@@ -0,0 +1,135 @@
+#include "ruby/random.h"
+
+#if RUBY_RANDOM_INTERFACE_VERSION_MAJOR < RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX
+# define DEFINE_VERSION_MAX 1
+#else
+# define DEFINE_VERSION_MAX 0
+#endif
+
+NORETURN(static void must_not_reach(void));
+static void
+must_not_reach(void)
+{
+ rb_raise(rb_eTypeError, "must not reach");
+}
+
+NORETURN(static void bad_version_init(rb_random_t *, const uint32_t *, size_t));
+static void
+bad_version_init(rb_random_t *rnd, const uint32_t *buf, size_t len)
+{
+ must_not_reach();
+}
+
+NORETURN(static void bad_version_init_int32(rb_random_t *, uint32_t));
+RB_RANDOM_DEFINE_INIT_INT32_FUNC(bad_version)
+
+NORETURN(static void bad_version_get_bytes(rb_random_t *, void *, size_t));
+static void
+bad_version_get_bytes(rb_random_t *rnd, void *p, size_t n)
+{
+ must_not_reach();
+}
+
+NORETURN(static uint32_t bad_version_get_int32(rb_random_t *));
+static uint32_t
+bad_version_get_int32(rb_random_t *rnd)
+{
+ must_not_reach();
+ UNREACHABLE_RETURN(0);
+}
+
+static VALUE
+bad_version_alloc(VALUE klass, const rb_data_type_t *type)
+{
+ rb_random_t *rnd;
+ VALUE obj = TypedData_Make_Struct(klass, rb_random_t, type, rnd);
+ rb_random_base_init(rnd);
+ return obj;
+}
+
+/* version 0 */
+static const rb_random_interface_t random_version_zero_if;
+
+static rb_random_data_type_t version_zero_type = {
+ "random/version_zero",
+ {
+ rb_random_mark,
+ RUBY_TYPED_DEFAULT_FREE,
+ },
+ RB_RANDOM_PARENT,
+ (void *)&random_version_zero_if,
+ RUBY_TYPED_FREE_IMMEDIATELY
+};
+
+static VALUE
+version_zero_alloc(VALUE klass)
+{
+ return bad_version_alloc(klass, &version_zero_type);
+}
+
+static void
+init_version_zero(VALUE mod, VALUE base)
+{
+ VALUE c = rb_define_class_under(mod, "VersionZero", base);
+ rb_define_alloc_func(c, version_zero_alloc);
+ RB_RANDOM_DATA_INIT_PARENT(version_zero_type);
+}
+
+#if DEFINE_VERSION_MAX
+/* version max */
+static const rb_random_interface_t random_version_max_if;
+static rb_random_data_type_t version_max_type = {
+ "random/version_max",
+ {
+ rb_random_mark,
+ RUBY_TYPED_DEFAULT_FREE,
+ },
+ RB_RANDOM_PARENT,
+ (void *)&random_version_max_if,
+ RUBY_TYPED_FREE_IMMEDIATELY
+};
+
+static VALUE
+version_max_alloc(VALUE klass)
+{
+ return bad_version_alloc(klass, &version_max_type);
+}
+
+static void
+init_version_max(VALUE mod, VALUE base)
+{
+ VALUE c = rb_define_class_under(mod, "VersionMax", base);
+ rb_define_alloc_func(c, version_max_alloc);
+ RB_RANDOM_DATA_INIT_PARENT(version_max_type);
+}
+#else
+static void
+init_version_max(mod, base)
+{
+}
+#endif
+
+void
+Init_random_bad_version(VALUE mod, VALUE base)
+{
+ init_version_zero(mod, base);
+ init_version_max(mod, base);
+}
+
+#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR
+
+#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR 0
+static const rb_random_interface_t random_version_zero_if = {
+ 0,
+ RB_RANDOM_INTERFACE_DEFINE(bad_version)
+};
+#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR
+
+#if DEFINE_VERSION_MAX
+#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX
+static const rb_random_interface_t random_version_max_if = {
+ 0,
+ RB_RANDOM_INTERFACE_DEFINE(bad_version)
+};
+#undef RUBY_RANDOM_INTERFACE_VERSION_MAJOR
+#endif
diff --git a/ext/-test-/random/depend b/ext/-test-/random/depend
index 602526cf7b76b9..f2cbf7fc144a07 100644
--- a/ext/-test-/random/depend
+++ b/ext/-test-/random/depend
@@ -1,4 +1,164 @@
# AUTOGENERATED DEPENDENCIES START
+bad_version.o: $(RUBY_EXTCONF_H)
+bad_version.o: $(arch_hdrdir)/ruby/config.h
+bad_version.o: $(hdrdir)/ruby/assert.h
+bad_version.o: $(hdrdir)/ruby/backward.h
+bad_version.o: $(hdrdir)/ruby/backward/2/assume.h
+bad_version.o: $(hdrdir)/ruby/backward/2/attributes.h
+bad_version.o: $(hdrdir)/ruby/backward/2/bool.h
+bad_version.o: $(hdrdir)/ruby/backward/2/inttypes.h
+bad_version.o: $(hdrdir)/ruby/backward/2/limits.h
+bad_version.o: $(hdrdir)/ruby/backward/2/long_long.h
+bad_version.o: $(hdrdir)/ruby/backward/2/stdalign.h
+bad_version.o: $(hdrdir)/ruby/backward/2/stdarg.h
+bad_version.o: $(hdrdir)/ruby/defines.h
+bad_version.o: $(hdrdir)/ruby/intern.h
+bad_version.o: $(hdrdir)/ruby/internal/abi.h
+bad_version.o: $(hdrdir)/ruby/internal/anyargs.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/char.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/double.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/int.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/long.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/short.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
+bad_version.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
+bad_version.o: $(hdrdir)/ruby/internal/assume.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/artificial.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/cold.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/const.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/constexpr.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/deprecated.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/error.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/forceinline.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/format.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/noalias.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/noexcept.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/noinline.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/nonnull.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/noreturn.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/pure.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/restrict.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/warning.h
+bad_version.o: $(hdrdir)/ruby/internal/attr/weakref.h
+bad_version.o: $(hdrdir)/ruby/internal/cast.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
+bad_version.o: $(hdrdir)/ruby/internal/compiler_since.h
+bad_version.o: $(hdrdir)/ruby/internal/config.h
+bad_version.o: $(hdrdir)/ruby/internal/constant_p.h
+bad_version.o: $(hdrdir)/ruby/internal/core.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rarray.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rbasic.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rbignum.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rclass.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rdata.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rfile.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rhash.h
+bad_version.o: $(hdrdir)/ruby/internal/core/robject.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rregexp.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rstring.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rstruct.h
+bad_version.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
+bad_version.o: $(hdrdir)/ruby/internal/ctype.h
+bad_version.o: $(hdrdir)/ruby/internal/dllexport.h
+bad_version.o: $(hdrdir)/ruby/internal/dosish.h
+bad_version.o: $(hdrdir)/ruby/internal/error.h
+bad_version.o: $(hdrdir)/ruby/internal/eval.h
+bad_version.o: $(hdrdir)/ruby/internal/event.h
+bad_version.o: $(hdrdir)/ruby/internal/fl_type.h
+bad_version.o: $(hdrdir)/ruby/internal/gc.h
+bad_version.o: $(hdrdir)/ruby/internal/glob.h
+bad_version.o: $(hdrdir)/ruby/internal/globals.h
+bad_version.o: $(hdrdir)/ruby/internal/has/attribute.h
+bad_version.o: $(hdrdir)/ruby/internal/has/builtin.h
+bad_version.o: $(hdrdir)/ruby/internal/has/c_attribute.h
+bad_version.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
+bad_version.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
+bad_version.o: $(hdrdir)/ruby/internal/has/extension.h
+bad_version.o: $(hdrdir)/ruby/internal/has/feature.h
+bad_version.o: $(hdrdir)/ruby/internal/has/warning.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/array.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/bignum.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/class.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/compar.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/complex.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/cont.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/dir.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/enum.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/enumerator.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/error.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/eval.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/file.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/gc.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/hash.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/io.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/load.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/marshal.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/numeric.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/object.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/parse.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/proc.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/process.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/random.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/range.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/rational.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/re.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/ruby.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/select.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/signal.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/sprintf.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/string.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/struct.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/thread.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/time.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/variable.h
+bad_version.o: $(hdrdir)/ruby/internal/intern/vm.h
+bad_version.o: $(hdrdir)/ruby/internal/interpreter.h
+bad_version.o: $(hdrdir)/ruby/internal/iterator.h
+bad_version.o: $(hdrdir)/ruby/internal/memory.h
+bad_version.o: $(hdrdir)/ruby/internal/method.h
+bad_version.o: $(hdrdir)/ruby/internal/module.h
+bad_version.o: $(hdrdir)/ruby/internal/newobj.h
+bad_version.o: $(hdrdir)/ruby/internal/rgengc.h
+bad_version.o: $(hdrdir)/ruby/internal/scan_args.h
+bad_version.o: $(hdrdir)/ruby/internal/special_consts.h
+bad_version.o: $(hdrdir)/ruby/internal/static_assert.h
+bad_version.o: $(hdrdir)/ruby/internal/stdalign.h
+bad_version.o: $(hdrdir)/ruby/internal/stdbool.h
+bad_version.o: $(hdrdir)/ruby/internal/symbol.h
+bad_version.o: $(hdrdir)/ruby/internal/value.h
+bad_version.o: $(hdrdir)/ruby/internal/value_type.h
+bad_version.o: $(hdrdir)/ruby/internal/variable.h
+bad_version.o: $(hdrdir)/ruby/internal/warning_push.h
+bad_version.o: $(hdrdir)/ruby/internal/xmalloc.h
+bad_version.o: $(hdrdir)/ruby/missing.h
+bad_version.o: $(hdrdir)/ruby/random.h
+bad_version.o: $(hdrdir)/ruby/ruby.h
+bad_version.o: $(hdrdir)/ruby/st.h
+bad_version.o: $(hdrdir)/ruby/subst.h
+bad_version.o: bad_version.c
init.o: $(RUBY_EXTCONF_H)
init.o: $(arch_hdrdir)/ruby/config.h
init.o: $(hdrdir)/ruby.h
diff --git a/ext/-test-/random/loop.c b/ext/-test-/random/loop.c
index 05720964031a74..b789ab1d0177d4 100644
--- a/ext/-test-/random/loop.c
+++ b/ext/-test-/random/loop.c
@@ -13,6 +13,7 @@ static const rb_random_interface_t random_loop_if = {
RB_RANDOM_INTERFACE_DEFINE_WITH_REAL(loop)
};
+RB_RANDOM_DEFINE_INIT_INT32_FUNC(loop)
static size_t
random_loop_memsize(const void *ptr)
{
diff --git a/ext/-test-/rational/depend b/ext/-test-/rational/depend
index 8729695886a1d4..ce977821b88974 100644
--- a/ext/-test-/rational/depend
+++ b/ext/-test-/rational/depend
@@ -174,5 +174,6 @@ rat.o: $(top_srcdir)/internal/static_assert.h
rat.o: $(top_srcdir)/internal/vm.h
rat.o: $(top_srcdir)/internal/warnings.h
rat.o: $(top_srcdir)/ruby_assert.h
+rat.o: $(top_srcdir)/shape.h
rat.o: rat.c
# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c
index 7c7cae34887f9c..61fa0f04ac7de9 100644
--- a/ext/objspace/objspace_dump.c
+++ b/ext/objspace/objspace_dump.c
@@ -515,7 +515,7 @@ dump_object(VALUE obj, struct dump_config *dc)
case T_OBJECT:
dump_append(dc, ", \"ivars\":");
- dump_append_lu(dc, ROBJECT_NUMIV(obj));
+ dump_append_lu(dc, ROBJECT_IV_CAPACITY(obj));
break;
case T_FILE:
diff --git a/ext/pty/depend b/ext/pty/depend
index c43d3dcf9a7083..f251caae3f14d1 100644
--- a/ext/pty/depend
+++ b/ext/pty/depend
@@ -181,5 +181,6 @@ pty.o: $(top_srcdir)/internal/process.h
pty.o: $(top_srcdir)/internal/signal.h
pty.o: $(top_srcdir)/internal/static_assert.h
pty.o: $(top_srcdir)/internal/warnings.h
+pty.o: $(top_srcdir)/shape.h
pty.o: pty.c
# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/ripper/depend b/ext/ripper/depend
index 15c557a8efa052..85520b032e9dcb 100644
--- a/ext/ripper/depend
+++ b/ext/ripper/depend
@@ -20,8 +20,6 @@ static: check
ripper.y: $(srcdir)/tools/preproc.rb $(srcdir)/tools/dsl.rb $(top_srcdir)/parse.y $(top_srcdir)/defs/id.def
$(ECHO) extracting $@ from $(top_srcdir)/parse.y
$(Q) $(RUBY) $(top_srcdir)/tool/id2token.rb $(top_srcdir)/parse.y > ripper.tmp.y
- $(Q) $(RUBY) $(top_srcdir)/tool/pure_parser.rb ripper.tmp.y $(BISON)
- $(Q) $(RM) ripper.tmp.y.bak
$(Q) $(RUBY) $(srcdir)/tools/preproc.rb ripper.tmp.y --output=$@
$(Q) $(RM) ripper.tmp.y
@@ -254,6 +252,7 @@ ripper.o: $(top_srcdir)/internal/warnings.h
ripper.o: $(top_srcdir)/node.h
ripper.o: $(top_srcdir)/regenc.h
ripper.o: $(top_srcdir)/ruby_assert.h
+ripper.o: $(top_srcdir)/shape.h
ripper.o: $(top_srcdir)/symbol.h
ripper.o: ../../probes.h
ripper.o: eventids2.c
diff --git a/ext/ripper/tools/preproc.rb b/ext/ripper/tools/preproc.rb
index b838a78db71cfd..cd85a5da613c33 100644
--- a/ext/ripper/tools/preproc.rb
+++ b/ext/ripper/tools/preproc.rb
@@ -47,7 +47,7 @@ def prelude(f, out)
when /\A%%/
out << "%%\n"
return
- when /\A%token/
+ when /\A%token/, /\A} /
out << line.sub(/<\w+>/, '')
when /\A%type/
out << line.sub(/<\w+>/, '')
diff --git a/ext/socket/depend b/ext/socket/depend
index ffe2fce844296b..28c5540cd64f3d 100644
--- a/ext/socket/depend
+++ b/ext/socket/depend
@@ -197,6 +197,7 @@ ancdata.o: $(top_srcdir)/internal/string.h
ancdata.o: $(top_srcdir)/internal/thread.h
ancdata.o: $(top_srcdir)/internal/vm.h
ancdata.o: $(top_srcdir)/internal/warnings.h
+ancdata.o: $(top_srcdir)/shape.h
ancdata.o: ancdata.c
ancdata.o: constdefs.h
ancdata.o: rubysocket.h
@@ -388,6 +389,7 @@ basicsocket.o: $(top_srcdir)/internal/string.h
basicsocket.o: $(top_srcdir)/internal/thread.h
basicsocket.o: $(top_srcdir)/internal/vm.h
basicsocket.o: $(top_srcdir)/internal/warnings.h
+basicsocket.o: $(top_srcdir)/shape.h
basicsocket.o: basicsocket.c
basicsocket.o: constdefs.h
basicsocket.o: rubysocket.h
@@ -579,6 +581,7 @@ constants.o: $(top_srcdir)/internal/string.h
constants.o: $(top_srcdir)/internal/thread.h
constants.o: $(top_srcdir)/internal/vm.h
constants.o: $(top_srcdir)/internal/warnings.h
+constants.o: $(top_srcdir)/shape.h
constants.o: constants.c
constants.o: constdefs.c
constants.o: constdefs.h
@@ -771,6 +774,7 @@ ifaddr.o: $(top_srcdir)/internal/string.h
ifaddr.o: $(top_srcdir)/internal/thread.h
ifaddr.o: $(top_srcdir)/internal/vm.h
ifaddr.o: $(top_srcdir)/internal/warnings.h
+ifaddr.o: $(top_srcdir)/shape.h
ifaddr.o: constdefs.h
ifaddr.o: ifaddr.c
ifaddr.o: rubysocket.h
@@ -962,6 +966,7 @@ init.o: $(top_srcdir)/internal/string.h
init.o: $(top_srcdir)/internal/thread.h
init.o: $(top_srcdir)/internal/vm.h
init.o: $(top_srcdir)/internal/warnings.h
+init.o: $(top_srcdir)/shape.h
init.o: constdefs.h
init.o: init.c
init.o: rubysocket.h
@@ -1153,6 +1158,7 @@ ipsocket.o: $(top_srcdir)/internal/string.h
ipsocket.o: $(top_srcdir)/internal/thread.h
ipsocket.o: $(top_srcdir)/internal/vm.h
ipsocket.o: $(top_srcdir)/internal/warnings.h
+ipsocket.o: $(top_srcdir)/shape.h
ipsocket.o: constdefs.h
ipsocket.o: ipsocket.c
ipsocket.o: rubysocket.h
@@ -1344,6 +1350,7 @@ option.o: $(top_srcdir)/internal/string.h
option.o: $(top_srcdir)/internal/thread.h
option.o: $(top_srcdir)/internal/vm.h
option.o: $(top_srcdir)/internal/warnings.h
+option.o: $(top_srcdir)/shape.h
option.o: constdefs.h
option.o: option.c
option.o: rubysocket.h
@@ -1535,6 +1542,7 @@ raddrinfo.o: $(top_srcdir)/internal/string.h
raddrinfo.o: $(top_srcdir)/internal/thread.h
raddrinfo.o: $(top_srcdir)/internal/vm.h
raddrinfo.o: $(top_srcdir)/internal/warnings.h
+raddrinfo.o: $(top_srcdir)/shape.h
raddrinfo.o: constdefs.h
raddrinfo.o: raddrinfo.c
raddrinfo.o: rubysocket.h
@@ -1726,6 +1734,7 @@ socket.o: $(top_srcdir)/internal/string.h
socket.o: $(top_srcdir)/internal/thread.h
socket.o: $(top_srcdir)/internal/vm.h
socket.o: $(top_srcdir)/internal/warnings.h
+socket.o: $(top_srcdir)/shape.h
socket.o: constdefs.h
socket.o: rubysocket.h
socket.o: socket.c
@@ -1917,6 +1926,7 @@ sockssocket.o: $(top_srcdir)/internal/string.h
sockssocket.o: $(top_srcdir)/internal/thread.h
sockssocket.o: $(top_srcdir)/internal/vm.h
sockssocket.o: $(top_srcdir)/internal/warnings.h
+sockssocket.o: $(top_srcdir)/shape.h
sockssocket.o: constdefs.h
sockssocket.o: rubysocket.h
sockssocket.o: sockport.h
@@ -2108,6 +2118,7 @@ tcpserver.o: $(top_srcdir)/internal/string.h
tcpserver.o: $(top_srcdir)/internal/thread.h
tcpserver.o: $(top_srcdir)/internal/vm.h
tcpserver.o: $(top_srcdir)/internal/warnings.h
+tcpserver.o: $(top_srcdir)/shape.h
tcpserver.o: constdefs.h
tcpserver.o: rubysocket.h
tcpserver.o: sockport.h
@@ -2299,6 +2310,7 @@ tcpsocket.o: $(top_srcdir)/internal/string.h
tcpsocket.o: $(top_srcdir)/internal/thread.h
tcpsocket.o: $(top_srcdir)/internal/vm.h
tcpsocket.o: $(top_srcdir)/internal/warnings.h
+tcpsocket.o: $(top_srcdir)/shape.h
tcpsocket.o: constdefs.h
tcpsocket.o: rubysocket.h
tcpsocket.o: sockport.h
@@ -2490,6 +2502,7 @@ udpsocket.o: $(top_srcdir)/internal/string.h
udpsocket.o: $(top_srcdir)/internal/thread.h
udpsocket.o: $(top_srcdir)/internal/vm.h
udpsocket.o: $(top_srcdir)/internal/warnings.h
+udpsocket.o: $(top_srcdir)/shape.h
udpsocket.o: constdefs.h
udpsocket.o: rubysocket.h
udpsocket.o: sockport.h
@@ -2681,6 +2694,7 @@ unixserver.o: $(top_srcdir)/internal/string.h
unixserver.o: $(top_srcdir)/internal/thread.h
unixserver.o: $(top_srcdir)/internal/vm.h
unixserver.o: $(top_srcdir)/internal/warnings.h
+unixserver.o: $(top_srcdir)/shape.h
unixserver.o: constdefs.h
unixserver.o: rubysocket.h
unixserver.o: sockport.h
@@ -2872,6 +2886,7 @@ unixsocket.o: $(top_srcdir)/internal/string.h
unixsocket.o: $(top_srcdir)/internal/thread.h
unixsocket.o: $(top_srcdir)/internal/vm.h
unixsocket.o: $(top_srcdir)/internal/warnings.h
+unixsocket.o: $(top_srcdir)/shape.h
unixsocket.o: constdefs.h
unixsocket.o: rubysocket.h
unixsocket.o: sockport.h
diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c
index a4e1ed37a352d6..636d1edda3296c 100644
--- a/ext/socket/raddrinfo.c
+++ b/ext/socket/raddrinfo.c
@@ -618,8 +618,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup)
family = rb_str_dup(rb_id2str(id));
}
else {
- sprintf(pbuf, "unknown:%d", sockaddr->sa_family);
- family = rb_str_new2(pbuf);
+ family = rb_sprintf("unknown:%d", sockaddr->sa_family);
}
addr1 = Qnil;
diff --git a/file.c b/file.c
index efd15ea8a16af8..93f5898ccde0b8 100644
--- a/file.c
+++ b/file.c
@@ -7147,7 +7147,7 @@ const char ruby_null_device[] =
* strings read are converted from external to internal encoding,
* and strings written are converted from internal to external encoding.
* For further details about transcoding input and output,
- * see {Encodings}[rdoc-ref:io_streams.rdoc@Encodings].
+ * see {Encodings}[rdoc-ref:encodings.rdoc@Encodings].
*
* If the external encoding is 'BOM|UTF-8', 'BOM|UTF-16LE'
* or 'BOM|UTF16-BE',
diff --git a/gc.c b/gc.c
index 0309081d2c166b..ef0532e3256d57 100644
--- a/gc.c
+++ b/gc.c
@@ -145,6 +145,7 @@ RubyUpcalls ruby_upcalls;
#include "ractor_core.h"
#include "builtin.h"
+#include "shape.h"
#define rb_setjmp(env) RUBY_SETJMP(env)
#define rb_jmp_buf rb_jmpbuf_t
@@ -2743,6 +2744,12 @@ size_pool_slot_size(unsigned char pool_id)
return slot_size;
}
+size_t
+rb_size_pool_slot_size(unsigned char pool_id)
+{
+ return size_pool_slot_size(pool_id);
+}
+
bool
rb_gc_size_allocatable_p(size_t size)
{
@@ -2947,6 +2954,9 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *
}
obj = newobj_alloc(objspace, cr, size_pool_idx, true);
+#if SHAPE_IN_BASIC_FLAGS
+ flags |= (VALUE)(size_pool_idx) << SHAPE_FLAG_SHIFT;
+#endif
newobj_init(klass, flags, wb_protected, objspace, obj);
gc_event_hook_prep(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj, newobj_fill(obj, 0, 0, 0));
@@ -3041,6 +3051,9 @@ newobj_of0(VALUE klass, VALUE flags, int wb_protected, rb_ractor_t *cr, size_t a
gc_event_hook_available_p(objspace)) &&
wb_protected) {
obj = newobj_alloc(objspace, cr, size_pool_idx, false);
+#if SHAPE_IN_BASIC_FLAGS
+ flags |= (VALUE)size_pool_idx << SHAPE_FLAG_SHIFT;
+#endif
newobj_init(klass, flags, wb_protected, objspace, obj);
}
else {
@@ -3109,10 +3122,10 @@ rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected)
GC_ASSERT((flags & RUBY_T_MASK) == T_OBJECT);
GC_ASSERT(flags & ROBJECT_EMBED);
- uint32_t index_tbl_num_entries = RCLASS_EXT(klass)->max_iv_count;
-
size_t size;
#if USE_RVARGC
+ uint32_t index_tbl_num_entries = RCLASS_EXT(klass)->max_iv_count;
+
size = rb_obj_embedded_size(index_tbl_num_entries);
if (!rb_gc_size_allocatable_p(size)) {
size = sizeof(struct RObject);
@@ -3123,14 +3136,9 @@ rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected)
VALUE obj = newobj_of(klass, flags, 0, 0, 0, wb_protected, size);
-#if USE_RVARGC
- uint32_t capa = (uint32_t)((rb_gc_obj_slot_size(obj) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
- ROBJECT(obj)->numiv = capa;
-#endif
-
#if RUBY_DEBUG
VALUE *ptr = ROBJECT_IVPTR(obj);
- for (size_t i = 0; i < ROBJECT_NUMIV(obj); i++) {
+ for (size_t i = 0; i < ROBJECT_IV_CAPACITY(obj); i++) {
ptr[i] = Qundef;
}
#endif
@@ -3709,7 +3717,7 @@ obj_free(rb_objspace_t *objspace, VALUE obj)
xfree(RCLASS_SUPERCLASSES(obj));
}
-#if !USE_RVARGC
+#if SIZE_POOL_COUNT == 1
if (RCLASS_EXT(obj))
xfree(RCLASS_EXT(obj));
#endif
@@ -5212,7 +5220,7 @@ obj_memsize_of(VALUE obj, int use_all_types)
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) {
- size += ROBJECT_NUMIV(obj) * sizeof(VALUE);
+ size += ROBJECT_IV_CAPACITY(obj) * sizeof(VALUE);
}
break;
case T_MODULE:
@@ -5235,7 +5243,7 @@ obj_memsize_of(VALUE obj, int use_all_types)
if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) {
size += (RCLASS_SUPERCLASS_DEPTH(obj) + 1) * sizeof(VALUE);
}
-#if !USE_RVARGC
+#if SIZE_POOL_COUNT == 1
size += sizeof(rb_classext_t);
#endif
}
@@ -6431,6 +6439,7 @@ invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_
gc_move(objspace, object, forwarding_object, GET_HEAP_PAGE(object)->slot_size, page->slot_size);
/* forwarding_object is now our actual object, and "object"
* is the free slot for the original page */
+
struct heap_page *orig_page = GET_HEAP_PAGE(object);
orig_page->free_slots++;
heap_page_add_freeobj(objspace, orig_page, object);
@@ -8833,6 +8842,7 @@ static rb_size_pool_t *
gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, VALUE src)
{
size_t obj_size;
+ size_t idx = 0;
switch (BUILTIN_TYPE(src)) {
case T_ARRAY:
@@ -8840,7 +8850,7 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V
break;
case T_OBJECT:
- obj_size = rb_obj_embedded_size(ROBJECT_NUMIV(src));
+ obj_size = rb_obj_embedded_size(ROBJECT_IV_CAPACITY(src));
break;
case T_STRING:
@@ -8852,17 +8862,16 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V
}
if (rb_gc_size_allocatable_p(obj_size)){
- return &size_pools[size_pool_idx_for_size(obj_size)];
- }
- else {
- return &size_pools[0];
+ idx = size_pool_idx_for_size(obj_size);
}
+ return &size_pools[idx];
}
static bool
gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, rb_size_pool_t *size_pool, VALUE src)
{
GC_ASSERT(BUILTIN_TYPE(src) != T_MOVED);
+
rb_heap_t *dheap = SIZE_POOL_EDEN_HEAP(gc_compact_destination_pool(objspace, size_pool, src));
if (gc_compact_heap_cursors_met_p(dheap)) {
@@ -10465,9 +10474,10 @@ static void
gc_ref_update_object(rb_objspace_t *objspace, VALUE v)
{
VALUE *ptr = ROBJECT_IVPTR(v);
- uint32_t numiv = ROBJECT_NUMIV(v);
#if USE_RVARGC
+ uint32_t numiv = ROBJECT_IV_CAPACITY(v);
+
size_t slot_size = rb_gc_obj_slot_size(v);
size_t embed_size = rb_obj_embedded_size(numiv);
if (slot_size >= embed_size && !RB_FL_TEST_RAW(v, ROBJECT_EMBED)) {
@@ -10481,9 +10491,10 @@ gc_ref_update_object(rb_objspace_t *objspace, VALUE v)
xfree(ptr);
}
ptr = ROBJECT(v)->as.ary;
-
- uint32_t capa = (uint32_t)((slot_size - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
- ROBJECT(v)->numiv = capa;
+ size_t size_pool_shape_id = size_pool_idx_for_size(embed_size);
+ rb_shape_t * initial_shape = rb_shape_get_shape_by_id((shape_id_t)size_pool_shape_id);
+ rb_shape_t * new_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_shape(v));
+ rb_shape_set_shape(v, new_shape);
}
#endif
@@ -14463,7 +14474,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU
}
case T_OBJECT:
{
- uint32_t len = ROBJECT_NUMIV(obj);
+ uint32_t len = ROBJECT_IV_CAPACITY(obj);
if (RANY(obj)->as.basic.flags & ROBJECT_EMBED) {
APPEND_F("(embed) len:%d", len);
@@ -14804,6 +14815,22 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self)
*/
#include "gc.rbinc"
+/*
+ * call-seq:
+ * GC.using_rvargc? -> true or false
+ *
+ * Returns true if using experimental feature Variable Width Allocation, false
+ * otherwise.
+ */
+static VALUE
+gc_using_rvargc_p(VALUE mod)
+{
+#if USE_RVARGC
+ return Qtrue;
+#else
+ return Qfalse;
+#endif
+}
#if USE_MMTK
VALUE
@@ -14900,6 +14927,8 @@ Init_GC(void)
rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
#endif
+ rb_define_singleton_method(rb_mGC, "using_rvargc?", gc_using_rvargc_p, 0);
+
if (GC_COMPACTION_SUPPORTED) {
rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
diff --git a/gc.h b/gc.h
index 99f625766386b0..7e6ce4f04d6b13 100644
--- a/gc.h
+++ b/gc.h
@@ -120,6 +120,8 @@ VALUE rb_gc_disable_no_rest(void);
struct rb_thread_struct;
+size_t rb_size_pool_slot_size(unsigned char pool_id);
+
#if USE_MMTK
#define MMTK_DEFAULT_PLAN "MarkSweep"
void rb_gc_init_collection(void);
diff --git a/gc.rb b/gc.rb
index 9144a9660362e5..8af36e0cb8fa65 100644
--- a/gc.rb
+++ b/gc.rb
@@ -252,16 +252,6 @@ def self.verify_compaction_references(toward: nil, double_heap: false, expand_he
end
end
- # call-seq:
- # GC.using_rvargc? -> true or false
- #
- # Returns true if using experimental feature Variable Width Allocation, false
- # otherwise.
- def self.using_rvargc? # :nodoc:
- GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] > 1
- end
-
-
# call-seq:
# GC.measure_total_time = true/false
#
diff --git a/include/ruby/internal/core/robject.h b/include/ruby/internal/core/robject.h
index bec0b45fd4e566..f51c5240810cbf 100644
--- a/include/ruby/internal/core/robject.h
+++ b/include/ruby/internal/core/robject.h
@@ -44,7 +44,7 @@
/** @cond INTERNAL_MACRO */
#define ROBJECT_EMBED_LEN_MAX ROBJECT_EMBED_LEN_MAX
#define ROBJECT_EMBED ROBJECT_EMBED
-#define ROBJECT_NUMIV ROBJECT_NUMIV
+#define ROBJECT_IV_CAPACITY ROBJECT_IV_CAPACITY
#define ROBJECT_IVPTR ROBJECT_IVPTR
/** @endcond */
@@ -96,14 +96,6 @@ struct RObject {
/** Basic part, including flags and class. */
struct RBasic basic;
-#if USE_RVARGC
- /**
- * Number of instance variables. This is per object; objects might
- * differ in this field even if they have the identical classes.
- */
- uint32_t numiv;
-#endif
-
/** Object's specific fields. */
union {
@@ -112,14 +104,6 @@ struct RObject {
* this pattern.
*/
struct {
-#if !USE_RVARGC
- /**
- * Number of instance variables. This is per object; objects might
- * differ in this field even if they have the identical classes.
- */
- uint32_t numiv;
-#endif
-
/** Pointer to a C array that holds instance variables. */
VALUE *ivptr;
@@ -156,42 +140,11 @@ struct RObject {
/* Offsets for YJIT */
#ifndef __cplusplus
-# if USE_RVARGC
-static const int32_t ROBJECT_OFFSET_NUMIV = offsetof(struct RObject, numiv);
-# else
-static const int32_t ROBJECT_OFFSET_NUMIV = offsetof(struct RObject, as.heap.numiv);
-# endif
static const int32_t ROBJECT_OFFSET_AS_HEAP_IVPTR = offsetof(struct RObject, as.heap.ivptr);
static const int32_t ROBJECT_OFFSET_AS_HEAP_IV_INDEX_TBL = offsetof(struct RObject, as.heap.iv_index_tbl);
static const int32_t ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary);
#endif
-RBIMPL_ATTR_PURE_UNLESS_DEBUG()
-RBIMPL_ATTR_ARTIFICIAL()
-/**
- * Queries the number of instance variables.
- *
- * @param[in] obj Object in question.
- * @return Its number of instance variables.
- * @pre `obj` must be an instance of ::RObject.
- */
-static inline uint32_t
-ROBJECT_NUMIV(VALUE obj)
-{
- RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
-
-#if USE_RVARGC
- return ROBJECT(obj)->numiv;
-#else
- if (RB_FL_ANY_RAW(obj, ROBJECT_EMBED)) {
- return ROBJECT_EMBED_LEN_MAX;
- }
- else {
- return ROBJECT(obj)->as.heap.numiv;
- }
-#endif
-}
-
RBIMPL_ATTR_PURE_UNLESS_DEBUG()
RBIMPL_ATTR_ARTIFICIAL()
/**
diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h
index d71dfb80fb03a0..348c4ec08f5f95 100644
--- a/include/ruby/onigmo.h
+++ b/include/ruby/onigmo.h
@@ -744,6 +744,8 @@ typedef struct {
typedef struct {
int lower;
int upper;
+ long base_num;
+ long inner_num;
} OnigRepeatRange;
typedef void (*OnigWarnFunc)(const char* s);
diff --git a/include/ruby/random.h b/include/ruby/random.h
index 657b37f034d2c0..39bdb6f3e3950a 100644
--- a/include/ruby/random.h
+++ b/include/ruby/random.h
@@ -16,6 +16,26 @@
#include "ruby/ruby.h"
+/*
+ * version
+ * 0: before versioning; deprecated
+ * 1: added version, flags and init_32bit function
+ */
+#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR 1
+#define RUBY_RANDOM_INTERFACE_VERSION_MINOR 0
+
+#define RUBY_RANDOM_PASTE_VERSION_SUFFIX(x, y, z) x##_##y##_##z
+#define RUBY_RANDOM_WITH_VERSION_SUFFIX(name, major, minor) \
+ RUBY_RANDOM_PASTE_VERSION_SUFFIX(name, major, minor)
+#define rb_random_data_type \
+ RUBY_RANDOM_WITH_VERSION_SUFFIX(rb_random_data_type, \
+ RUBY_RANDOM_INTERFACE_VERSION_MAJOR, \
+ RUBY_RANDOM_INTERFACE_VERSION_MINOR)
+#define RUBY_RANDOM_INTERFACE_VERSION_INITIALIZER \
+ {RUBY_RANDOM_INTERFACE_VERSION_MAJOR, RUBY_RANDOM_INTERFACE_VERSION_MINOR}
+#define RUBY_RANDOM_INTERFACE_VERSION_MAJOR_MAX 0xff
+#define RUBY_RANDOM_INTERFACE_VERSION_MINOR_MAX 0xff
+
RBIMPL_SYMBOL_EXPORT_BEGIN()
/**
@@ -46,6 +66,17 @@ RBIMPL_ATTR_NONNULL(())
*/
typedef void rb_random_init_func(rb_random_t *rng, const uint32_t *buf, size_t len);
+RBIMPL_ATTR_NONNULL(())
+/**
+ * This is the type of functions called when your random object is initialised.
+ * Passed data is the seed integer.
+ *
+ * @param[out] rng Your random struct to fill in.
+ * @param[in] data Seed, single word.
+ * @post `rng` is initialised using the passed seeds.
+ */
+typedef void rb_random_init_int32_func(rb_random_t *rng, uint32_t data);
+
RBIMPL_ATTR_NONNULL(())
/**
* This is the type of functions called from your object's `#rand` method.
@@ -84,9 +115,24 @@ typedef struct {
/** Number of bits of seed numbers. */
size_t default_seed_bits;
- /** Initialiser function. */
+ /**
+ * Major/minor versions of this interface
+ */
+ struct {
+ uint8_t major, minor;
+ } version;
+
+ /**
+ * Reserved flags
+ */
+ uint16_t flags;
+
+ /** Function to initialize from uint32_t array. */
rb_random_init_func *init;
+ /** Function to initialize from single uint32_t. */
+ rb_random_init_int32_func *init_int32;
+
/** Function to obtain a random integer. */
rb_random_get_int32_func *get_int32;
@@ -130,11 +176,12 @@ typedef struct {
} rb_random_interface_t;
/**
- * This utility macro defines 3 functions named prefix_init, prefix_get_int32,
- * prefix_get_bytes.
+ * This utility macro defines 4 functions named prefix_init, prefix_init_int32,
+ * prefix_get_int32, prefix_get_bytes.
*/
#define RB_RANDOM_INTERFACE_DECLARE(prefix) \
static void prefix##_init(rb_random_t *, const uint32_t *, size_t); \
+ static void prefix##_init_int32(rb_random_t *, uint32_t); \
static unsigned int prefix##_get_int32(rb_random_t *); \
static void prefix##_get_bytes(rb_random_t *, void *, size_t)
@@ -161,7 +208,9 @@ typedef struct {
* ```
*/
#define RB_RANDOM_INTERFACE_DEFINE(prefix) \
+ RUBY_RANDOM_INTERFACE_VERSION_INITIALIZER, 0, \
prefix##_init, \
+ prefix##_init_int32, \
prefix##_get_int32, \
prefix##_get_bytes
@@ -173,6 +222,12 @@ typedef struct {
RB_RANDOM_INTERFACE_DEFINE(prefix), \
prefix##_get_real
+#define RB_RANDOM_DEFINE_INIT_INT32_FUNC(prefix) \
+ static void prefix##_init_int32(rb_random_t *rnd, uint32_t data) \
+ { \
+ prefix##_init(rnd, &data, 1); \
+ }
+
#if defined _WIN32 && !defined __CYGWIN__
typedef rb_data_type_t rb_random_data_type_t;
# define RB_RANDOM_PARENT 0
@@ -189,7 +244,7 @@ typedef const rb_data_type_t rb_random_data_type_t;
* 0, RB_RANDOM_INTERFACE_DEFINE(your),
* };
*
- * static inline constexpr your_prng = {
+ * static inline constexpr rb_random_data_type_t your_prng_type = {
* "your PRNG",
* { rb_random_mark, },
* RB_RANDOM_PARENT, // <<-- HERE
diff --git a/inits.c b/inits.c
index d1204c1324f73b..3c2b11c851df43 100644
--- a/inits.c
+++ b/inits.c
@@ -20,6 +20,7 @@ static void Init_builtin_prelude(void);
void
rb_call_inits(void)
{
+ CALL(default_shapes);
CALL(Thread_Mutex);
#if USE_TRANSIENT_HEAP
CALL(TransientHeap);
diff --git a/insns.def b/insns.def
index cde327e430b7cb..9f5ee7095a766d 100644
--- a/insns.def
+++ b/insns.def
@@ -697,7 +697,7 @@ defined
{
val = Qnil;
if (vm_defined(ec, GET_CFP(), op_type, obj, v)) {
- val = pushval;
+ val = pushval;
}
}
diff --git a/internal/class.h b/internal/class.h
index 784d508e20d194..80807256345572 100644
--- a/internal/class.h
+++ b/internal/class.h
@@ -62,7 +62,7 @@ struct RClass {
struct RBasic basic;
VALUE super;
struct rb_id_table *m_tbl;
-#if !USE_RVARGC
+#if SIZE_POOL_COUNT == 1
struct rb_classext_struct *ptr;
#endif
};
@@ -70,7 +70,7 @@ struct RClass {
typedef struct rb_subclass_entry rb_subclass_entry_t;
typedef struct rb_classext_struct rb_classext_t;
-#if USE_RVARGC
+#if RCLASS_EXT_EMBEDDED
# define RCLASS_EXT(c) ((rb_classext_t *)((char *)(c) + sizeof(struct RClass)))
#else
# define RCLASS_EXT(c) (RCLASS(c)->ptr)
diff --git a/internal/gc.h b/internal/gc.h
index 84b7f9fa3e9112..5b2b9e8f706ee4 100644
--- a/internal/gc.h
+++ b/internal/gc.h
@@ -14,6 +14,7 @@
#include "internal/compilers.h" /* for __has_attribute */
#include "ruby/ruby.h" /* for rb_event_flag_t */
+#include "shape.h"
struct rb_execution_context_struct; /* in vm_core.h */
struct rb_objspace; /* in vm_core.h */
@@ -67,12 +68,14 @@ struct rb_objspace; /* in vm_core.h */
rb_obj_write((VALUE)(a), UNALIGNED_MEMBER_ACCESS((VALUE *)(slot)), \
(VALUE)(b), __FILE__, __LINE__)
-#if USE_RVARGC
+#if USE_RVARGC && SHAPE_IN_BASIC_FLAGS
# define SIZE_POOL_COUNT 5
#else
# define SIZE_POOL_COUNT 1
#endif
+#define RCLASS_EXT_EMBEDDED (SIZE_POOL_COUNT > 1)
+
typedef struct ractor_newobj_size_pool_cache {
struct RVALUE *freelist;
struct heap_page *using_page;
diff --git a/internal/variable.h b/internal/variable.h
index 734884a5f6baa1..553e87c4a8b325 100644
--- a/internal/variable.h
+++ b/internal/variable.h
@@ -13,6 +13,7 @@
#include "constant.h" /* for rb_const_entry_t */
#include "ruby/internal/stdbool.h" /* for bool */
#include "ruby/ruby.h" /* for VALUE */
+#include "shape.h" /* for rb_shape_t */
/* global variable */
@@ -53,7 +54,7 @@ VALUE rb_gvar_get(ID);
VALUE rb_gvar_set(ID, VALUE);
VALUE rb_gvar_defined(ID);
void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID);
-void rb_init_iv_list(VALUE obj);
+rb_shape_t * rb_grow_iv_list(VALUE obj);
void rb_ensure_iv_list_size(VALUE obj, uint32_t len, uint32_t newsize);
struct gen_ivtbl * rb_ensure_generic_iv_list_size(VALUE obj, uint32_t newsize);
MJIT_SYMBOL_EXPORT_END
diff --git a/io.c b/io.c
index 62b25c2ebbcaa6..63a96bf2b80a68 100644
--- a/io.c
+++ b/io.c
@@ -2373,7 +2373,7 @@ rb_io_flush(VALUE io)
* tell -> integer
*
* Returns the current position (in bytes) in +self+
- * (see {Position}[rdoc-ref:io_streams.rdoc@Position]):
+ * (see {Position}[rdoc-ref:IO@Position]):
*
* f = File.open('t.txt')
* f.tell # => 0
@@ -2439,7 +2439,7 @@ interpret_seek_whence(VALUE vwhence)
* seek(offset, whence = IO::SEEK_SET) -> 0
*
* Seeks to the position given by integer +offset+
- * (see {Position}[rdoc-ref:io_streams.rdoc@Position])
+ * (see {Position}[rdoc-ref:IO@Position])
* and constant +whence+, which is one of:
*
* - +:CUR+ or IO::SEEK_CUR:
@@ -2499,7 +2499,7 @@ rb_io_seek_m(int argc, VALUE *argv, VALUE io)
* pos = new_position -> new_position
*
* Seeks to the given +new_position+ (in bytes);
- * see {Position}[rdoc-ref:io_streams.rdoc@Position]:
+ * see {Position}[rdoc-ref:IO@Position]:
*
* f = File.open('t.txt')
* f.tell # => 0
@@ -2533,8 +2533,8 @@ static void clear_readconv(rb_io_t *fptr);
*
* Repositions the stream to its beginning,
* setting both the position and the line number to zero;
- * see {Position}[rdoc-ref:io_streams.rdoc@Position]
- * and {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number]:
+ * see {Position}[rdoc-ref:IO@Position]
+ * and {Line Number}[rdoc-ref:IO@Line+Number]:
*
* f = File.open('t.txt')
* f.tell # => 0
@@ -2624,7 +2624,7 @@ io_fillbuf(rb_io_t *fptr)
* eof -> true or false
*
* Returns +true+ if the stream is positioned at its end, +false+ otherwise;
- * see {Position}[rdoc-ref:io_streams.rdoc@Position]:
+ * see {Position}[rdoc-ref:IO@Position]:
*
* f = File.open('t.txt')
* f.eof # => false
@@ -3640,10 +3640,11 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex)
/*
* call-seq:
- * read(maxlen = nil) -> string or nil
- * read(maxlen = nil, out_string) -> out_string or nil
+ * read(maxlen = nil, out_string = nil) -> new_string, out_string, or nil
*
- * Reads bytes from the stream (in binary mode):
+ * Reads bytes from the stream, (in binary mode);
+ * the stream must be opened for reading
+ * (see {Access Modes}[rdoc-ref:File@Access+Modes]):
*
* - If +maxlen+ is +nil+, reads all bytes.
* - Otherwise reads +maxlen+ bytes, if available.
@@ -4192,13 +4193,13 @@ rb_io_gets_internal(VALUE io)
/*
* call-seq:
- * gets(sep = $/, **line_opts) -> string or nil
- * gets(limit, **line_opts) -> string or nil
- * gets(sep, limit, **line_opts) -> string or nil
+ * gets(sep = $/, chomp: false) -> string or nil
+ * gets(limit, chomp: false) -> string or nil
+ * gets(sep, limit, chomp: false) -> string or nil
*
* Reads and returns a line from the stream;
* assigns the return value to $_.
- * See {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO].
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, returns the next line
* as determined by line separator $/, or +nil+ if none:
@@ -4215,7 +4216,7 @@ rb_io_gets_internal(VALUE io)
* With only string argument +sep+ given,
* returns the next line as determined by line separator +sep+,
* or +nil+ if none;
- * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]:
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator]:
*
* f = File.new('t.txt')
* f.gets('l') # => "First l"
@@ -4236,7 +4237,7 @@ rb_io_gets_internal(VALUE io)
*
* With only integer argument +limit+ given,
* limits the number of bytes in the line;
- * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* # No more than one line.
* File.open('t.txt') {|f| f.gets(10) } # => "First line"
@@ -4250,8 +4251,8 @@ rb_io_gets_internal(VALUE io)
* or +nil+ if none.
* - But returns no more bytes than are allowed by the limit.
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.open('t.txt')
* # Chomp the lines.
@@ -4280,8 +4281,8 @@ rb_io_gets_m(int argc, VALUE *argv, VALUE io)
* call-seq:
* lineno -> integer
*
- * Returns the current line number for the stream.
- * See {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number].
+ * Returns the current line number for the stream;
+ * see {Line Number}[rdoc-ref:IO@Line+Number].
*
*/
@@ -4299,8 +4300,8 @@ rb_io_lineno(VALUE io)
* call-seq:
* lineno = integer -> integer
*
- * Sets and returns the line number for the stream.
- * See {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number].
+ * Sets and returns the line number for the stream;
+ * see {Line Number}[rdoc-ref:IO@Line+Number].
*
*/
@@ -4317,12 +4318,14 @@ rb_io_set_lineno(VALUE io, VALUE lineno)
/*
* call-seq:
- * readline(sep = $/, **line_opts) -> string
- * readline(limit, **line_opts) -> string
- * readline(sep, limit, **line_opts) -> string
+ * readline(sep = $/, chomp: false) -> string
+ * readline(limit, chomp: false) -> string
+ * readline(sep, limit, chomp: false) -> string
*
- * Reads a line as with IO#gets, but raises EOFError if already at end-of-file.
+ * Reads a line as with IO#gets, but raises EOFError if already at end-of-stream.
*
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted.
*/
static VALUE
@@ -4340,13 +4343,13 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
/*
* call-seq:
- * readlines(sep = $/, **line_opts) -> array
- * readlines(limit, **line_opts) -> array
- * readlines(sep, limit, **line_opts) -> array
+ * readlines(sep = $/, chomp: false) -> array
+ * readlines(limit, chomp: false) -> array
+ * readlines(sep, limit, chomp: false) -> array
*
* Reads and returns all remaining line from the stream;
* does not modify $_.
- * See {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO].
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, returns lines
* as determined by line separator $/, or +nil+ if none:
@@ -4360,7 +4363,7 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
* With only string argument +sep+ given,
* returns lines as determined by line separator +sep+,
* or +nil+ if none;
- * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]:
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator]:
*
* f = File.new('t.txt')
* f.readlines('li')
@@ -4381,7 +4384,7 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
*
* With only integer argument +limit+ given,
* limits the number of bytes in each line;
- * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* f = File.new('t.txt')
* f.readlines(8)
@@ -4394,8 +4397,8 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
* - Returns lines as determined by line separator +sep+.
* - But returns no more bytes in a line than are allowed by the limit.
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.new('t.txt')
* f.readlines(chomp: true)
@@ -4429,15 +4432,15 @@ io_readlines(const struct getline_arg *arg, VALUE io)
/*
* call-seq:
- * each_line(sep = $/, **line_opts) {|line| ... } -> self
- * each_line(limit, **line_opts) {|line| ... } -> self
- * each_line(sep, limit, **line_opts) {|line| ... } -> self
+ * each_line(sep = $/, chomp: false) {|line| ... } -> self
+ * each_line(limit, chomp: false) {|line| ... } -> self
+ * each_line(sep, limit, chomp: false) {|line| ... } -> self
* each_line -> enumerator
*
* Calls the block with each remaining line read from the stream;
- * does nothing if already at end-of-file;
* returns +self+.
- * See {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO].
+ * Does nothing if already at end-of-stream;
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, reads lines
* as determined by line separator $/:
@@ -4457,7 +4460,7 @@ io_readlines(const struct getline_arg *arg, VALUE io)
*
* With only string argument +sep+ given,
* reads lines as determined by line separator +sep+;
- * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]:
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator]:
*
* f = File.new('t.txt')
* f.each_line('li') {|line| p line }
@@ -4493,7 +4496,7 @@ io_readlines(const struct getline_arg *arg, VALUE io)
*
* With only integer argument +limit+ given,
* limits the number of bytes in each line;
- * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* f = File.new('t.txt')
* f.each_line(8) {|line| p line }
@@ -4517,8 +4520,8 @@ io_readlines(const struct getline_arg *arg, VALUE io)
* - Calls with the next line as determined by line separator +sep+.
* - But returns no more bytes than are allowed by the limit.
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.new('t.txt')
* f.each_line(chomp: true) {|line| p line }
@@ -4560,7 +4563,7 @@ rb_io_each_line(int argc, VALUE *argv, VALUE io)
* each_byte -> enumerator
*
* Calls the given block with each byte (0..255) in the stream; returns +self+.
- * See {Byte IO}[rdoc-ref:io_streams.rdoc@Byte+IO].
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.new('t.rus')
* a = []
@@ -4708,7 +4711,7 @@ io_getc(rb_io_t *fptr, rb_encoding *enc)
* each_char -> enumerator
*
* Calls the given block with each character in the stream; returns +self+.
- * See {Character IO}[rdoc-ref:io_streams.rdoc@Character+IO].
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.new('t.rus')
* a = []
@@ -4869,8 +4872,8 @@ rb_io_each_codepoint(VALUE io)
* getc -> character or nil
*
* Reads and returns the next 1-character string from the stream;
- * returns +nil+ if already at end-of-file.
- * See {Character IO}[rdoc-ref:io_streams.rdoc@Character+IO].
+ * returns +nil+ if already at end-of-stream.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.open('t.txt')
* f.getc # => "F"
@@ -4902,8 +4905,8 @@ rb_io_getc(VALUE io)
* readchar -> string
*
* Reads and returns the next 1-character string from the stream;
- * raises EOFError if already at end-of-file.
- * See {Character IO}[rdoc-ref:io_streams.rdoc@Character+IO].
+ * raises EOFError if already at end-of-stream.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.open('t.txt')
* f.readchar # => "F"
@@ -4932,8 +4935,8 @@ rb_io_readchar(VALUE io)
* getbyte -> integer or nil
*
* Reads and returns the next byte (in range 0..255) from the stream;
- * returns +nil+ if already at end-of-file.
- * See {Byte IO}[rdoc-ref:io_streams.rdoc@Byte+IO].
+ * returns +nil+ if already at end-of-stream.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.open('t.txt')
* f.getbyte # => 70
@@ -4943,7 +4946,6 @@ rb_io_readchar(VALUE io)
* f.close
*
* Related: IO#readbyte (may raise EOFError).
- *
*/
VALUE
@@ -4977,8 +4979,8 @@ rb_io_getbyte(VALUE io)
* readbyte -> integer
*
* Reads and returns the next byte (in range 0..255) from the stream;
- * raises EOFError if already at end-of-file.
- * See {Byte IO}[rdoc-ref:io_streams.rdoc@Byte+IO].
+ * raises EOFError if already at end-of-stream.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.open('t.txt')
* f.readbyte # => 70
@@ -5009,7 +5011,7 @@ rb_io_readbyte(VALUE io)
*
* Pushes back ("unshifts") the given data onto the stream's buffer,
* placing the data so that it is next to be read; returns +nil+.
- * See {Byte IO}[rdoc-ref:io_streams.rdoc@Byte+IO].
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* Note that:
*
@@ -5070,7 +5072,7 @@ rb_io_ungetbyte(VALUE io, VALUE b)
*
* Pushes back ("unshifts") the given data onto the stream's buffer,
* placing the data so that it is next to be read; returns +nil+.
- * See {Character IO}[rdoc-ref:io_streams.rdoc@Character+IO].
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* Note that:
*
@@ -5155,8 +5157,10 @@ rb_io_ungetc(VALUE io, VALUE c)
* Returns +true+ if the stream is associated with a terminal device (tty),
* +false+ otherwise:
*
- * File.new('t.txt').isatty #=> false
- * File.new('/dev/tty').isatty #=> true
+ * f = File.new('t.txt').isatty #=> false
+ * f.close
+ * f = File.new('/dev/tty').isatty #=> true
+ * f.close
*
* IO#tty? is an alias for IO#isatty.
*
@@ -5639,6 +5643,7 @@ rb_io_close(VALUE io)
*
* Closes the stream for both reading and writing
* if open for either or both; returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
* If the stream is open for writing, flushes any buffered writes
* to the operating system before closing.
@@ -5710,7 +5715,8 @@ io_close(VALUE io)
* closed? -> true or false
*
* Returns +true+ if the stream is closed for both reading and writing,
- * +false+ otherwise:
+ * +false+ otherwise.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
* IO.popen('ruby', 'r+') do |pipe|
* puts pipe.closed?
@@ -5726,8 +5732,6 @@ io_close(VALUE io)
* false
* true
*
- * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams].
- *
* Related: IO#close_read, IO#close_write, IO#close.
*/
@@ -5757,6 +5761,7 @@ rb_io_closed(VALUE io)
*
* Closes the stream for reading if open for reading;
* returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
* If the stream was opened by IO.popen and is also closed for writing,
* sets global variable $? (child exit status).
@@ -5779,8 +5784,6 @@ rb_io_closed(VALUE io)
* pid 14748 exit 0
* true
*
- * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams].
- *
* Related: IO#close, IO#close_write, IO#closed?.
*/
@@ -5830,7 +5833,8 @@ rb_io_close_read(VALUE io)
* close_write -> nil
*
* Closes the stream for writing if open for writing;
- * returns +nil+:
+ * returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
* Flushes any buffered writes to the operating system before closing.
*
@@ -5853,8 +5857,6 @@ rb_io_close_read(VALUE io)
* pid 15044 exit 0
* true
*
- * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams].
- *
* Related: IO#close, IO#close_read, IO#closed?.
*/
@@ -6066,7 +6068,7 @@ pread_internal_call(VALUE arg)
*
* - Reads at the given +offset+ (in bytes).
* - Disregards, and does not modify, the stream's position
- * (see {Position}[rdoc-ref:io_streams.rdoc@Position]).
+ * (see {Position}[rdoc-ref:IO@Position]).
* - Bypasses any user space buffering in the stream.
*
* Because this method does not disturb the stream's state
@@ -6142,7 +6144,7 @@ internal_pwrite_func(void *ptr)
*
* - Writes at the given +offset+ (in bytes).
* - Disregards, and does not modify, the stream's position
- * (see {Position}[rdoc-ref:io_streams.rdoc@Position]).
+ * (see {Position}[rdoc-ref:IO@Position]).
* - Bypasses any user space buffering in the stream.
*
* Because this method does not disturb the stream's state
@@ -8577,7 +8579,7 @@ deprecated_str_setter(VALUE val, ID id, VALUE *var)
* Writes the given objects to the stream; returns +nil+.
* Appends the output record separator $OUTPUT_RECORD_SEPARATOR
* ($\\), if it is not +nil+.
- * See {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO].
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With argument +objects+ given, for each object:
*
@@ -8715,7 +8717,7 @@ rb_f_print(int argc, const VALUE *argv, VALUE _)
* putc(object) -> object
*
* Writes a character to the stream.
- * See {Character IO}[rdoc-ref:io_streams.rdoc@Character+IO].
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* If +object+ is numeric, converts to integer if necessary,
* then writes the character whose code is the
@@ -8819,7 +8821,7 @@ io_puts_ary(VALUE ary, VALUE out, int recur)
* returns +nil+.\
* Writes a newline after each that does not already end with a newline sequence.
* If called without arguments, writes a newline.
- * See {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO].
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* Note that each added newline is the character "\n"/tt>,
* not the output record separator ($\\).
@@ -9424,31 +9426,32 @@ rb_io_set_encoding_by_bom(VALUE io)
*
* Argument +path+ must be a valid file path:
*
- * File.new('/etc/fstab')
- * File.new('t.txt')
+ * f = File.new('/etc/fstab')
+ * f.close
+ * f = File.new('t.txt')
+ * f.close
*
* Optional argument +mode+ (defaults to 'r') must specify a valid mode;
* see {Access Modes}[rdoc-ref:File@Access+Modes]:
*
- * File.new('t.tmp', 'w')
- * File.new('t.tmp', File::RDONLY)
+ * f = File.new('t.tmp', 'w')
+ * f.close
+ * f = File.new('t.tmp', File::RDONLY)
+ * f.close
*
* Optional argument +perm+ (defaults to 0666) must specify valid permissions
* see {File Permissions}[rdoc-ref:File@File+Permissions]:
*
- * File.new('t.tmp', File::CREAT, 0644)
- * File.new('t.tmp', File::CREAT, 0444)
+ * f = File.new('t.tmp', File::CREAT, 0644)
+ * f.close
+ * f = File.new('t.tmp', File::CREAT, 0444)
+ * f.close
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
*
- * Examples:
- *
- * File.new('t.tmp', autoclose: true)
- * File.new('t.tmp', internal_encoding: nil)
- *
*/
static VALUE
@@ -10231,9 +10234,9 @@ static VALUE argf_readline(int, VALUE *, VALUE);
/*
* call-seq:
- * readline(sep = $/, **line_opts) -> string
- * readline(limit, **line_opts) -> string
- * readline(sep, limit, **line_opts) -> string
+ * readline(sep = $/, chomp: false) -> string
+ * readline(limit, chomp: false) -> string
+ * readline(sep, limit, chomp: false) -> string
*
* Equivalent to method Kernel#gets, except that it raises an exception
* if called at end-of-stream:
@@ -10242,6 +10245,8 @@ static VALUE argf_readline(int, VALUE *, VALUE);
* ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
* in `readline': end of file reached (EOFError)
*
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted.
*/
static VALUE
@@ -10290,18 +10295,18 @@ static VALUE argf_readlines(int, VALUE *, VALUE);
/*
* call-seq:
- * readlines(sep = $/, **line_opts) -> array
- * readlines(limit, **line_opts) -> array
- * readlines(sep, limit, **line_opts) -> array
+ * readlines(sep = $/, chomp: false, **enc_opts) -> array
+ * readlines(limit, chomp: false, **enc_opts) -> array
+ * readlines(sep, limit, chomp: false, **enc_opts) -> array
*
* Returns an array containing the lines returned by calling
- * Kernel#gets until the end-of-file is reached;
- * (see {Line IO}[rdoc-ref:io_streams.rdoc@Line+IO]).
+ * Kernel#gets until the end-of-stream is reached;
+ * (see {Line IO}[rdoc-ref:IO@Line+IO]).
*
* With only string argument +sep+ given,
* returns the remaining lines as determined by line separator +sep+,
* or +nil+ if none;
- * see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]:
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator]:
*
* # Default separator.
* $ cat t.txt | ruby -e "p readlines"
@@ -10321,7 +10326,7 @@ static VALUE argf_readlines(int, VALUE *, VALUE);
*
* With only integer argument +limit+ given,
* limits the number of bytes in the line;
- * see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* $cat t.txt | ruby -e "p readlines 10"
* ["First line", "\n", "Second lin", "e\n", "\n", "Fourth lin", "e\n", "Fifth line", "\n"]
@@ -10333,18 +10338,17 @@ static VALUE argf_readlines(int, VALUE *, VALUE);
* ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
*
* With arguments +sep+ and +limit+ given, combines the two behaviors;
- * see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit].
+ * see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit].
*
- * For all forms above, optional keyword arguments specify:
- *
- * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options].
- * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
- *
- * Examples:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* $ cat t.txt | ruby -e "p readlines(chomp: true)"
* ["First line", "Second line", "", "Fourth line", "Fifth line"]
*
+ * Optional keyword arguments +enc_opts+ specify encoding options;
+ * see {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
+ *
*/
static VALUE
@@ -11828,7 +11832,7 @@ io_s_foreach(VALUE v)
* For both forms, command and path, the remaining arguments are the same.
*
* With argument +sep+ given, parses lines as determined by that line separator
- * (see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]):
+ * (see {Line Separator}[rdoc-ref:IO@Line+Separator]):
*
* File.foreach('t.txt', 'li') {|line| p line }
*
@@ -11851,7 +11855,7 @@ io_s_foreach(VALUE v)
*
* With argument +limit+ given, parses lines as determined by the default
* line separator and the given line-length limit
- * (see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]):
+ * (see {Line Limit}[rdoc-ref:IO@Line+Limit]):
*
* File.foreach('t.txt', 7) {|line| p line }
*
@@ -11870,13 +11874,13 @@ io_s_foreach(VALUE v)
* With arguments +sep+ and +limit+ given,
* parses lines as determined by the given
* line separator and the given line-length limit
- * (see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit]):
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]):
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
- * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options].
+ * - {Line Options}[rdoc-ref:IO@Line+Options].
*
* Returns an Enumerator if no block is given.
*
@@ -11946,7 +11950,7 @@ io_s_readlines(VALUE v)
* For both forms, command and path, the remaining arguments are the same.
*
* With argument +sep+ given, parses lines as determined by that line separator
- * (see {Line Separator}[rdoc-ref:io_streams.rdoc@Line+Separator]):
+ * (see {Line Separator}[rdoc-ref:IO@Line+Separator]):
*
* # Ordinary separator.
* IO.readlines('t.txt', 'li')
@@ -11960,7 +11964,7 @@ io_s_readlines(VALUE v)
*
* With argument +limit+ given, parses lines as determined by the default
* line separator and the given line-length limit
- * (see {Line Limit}[rdoc-ref:io_streams.rdoc@Line+Limit]):
+ * (see {Line Limit}[rdoc-ref:IO@Line+Limit]):
*
* IO.readlines('t.txt', 7)
* # => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"]
@@ -11968,13 +11972,13 @@ io_s_readlines(VALUE v)
* With arguments +sep+ and +limit+ given,
* parses lines as determined by the given
* line separator and the given line-length limit
- * (see {Line Separator and Line Limit}[rdoc-ref:io_streams.rdoc@Line+Separator+and+Line+Limit]):
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]):
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
- * - {Line Options}[rdoc-ref:io_streams.rdoc@Line+Options].
+ * - {Line Options}[rdoc-ref:IO@Line+Options].
*
*/
@@ -14687,10 +14691,10 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
*
* - A position, which determines where in the stream the next
* read or write is to occur;
- * see {Position}[rdoc-ref:io_streams.rdoc@Position].
+ * see {Position}[rdoc-ref:IO@Position].
* - A line number, which is a special, line-oriented, "position"
* (different from the position mentioned above);
- * see {Line Number}[rdoc-ref:io_streams.rdoc@Line+Number].
+ * see {Line Number}[rdoc-ref:IO@Line+Number].
*
* == Extension io/console
*
@@ -14725,6 +14729,326 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* Also available are the options offered in String#encode,
* which may control conversion between external internal encoding.
*
+ * == Basic \IO
+ *
+ * You can perform basic stream \IO with these methods,
+ * which typically operate on multi-byte strings:
+ *
+ * - IO#read: Reads and returns some or all of the remaining bytes from the stream.
+ * - IO#write: Writes zero or more strings to the stream;
+ * each given object that is not already a string is converted via +to_s+.
+ *
+ * === Position
+ *
+ * An \IO stream has a nonnegative integer _position_,
+ * which is the byte offset at which the next read or write is to occur.
+ * A new stream has position zero (and line number zero);
+ * method +rewind+ resets the position (and line number) to zero.
+ *
+ * The relevant methods:
+ *
+ * - IO#tell (aliased as +#pos+): Returns the current position (in bytes) in the stream.
+ * - IO#pos=: Sets the position of the stream to a given integer +new_position+ (in bytes).
+ * - IO#seek: Sets the position of the stream to a given integer +offset+ (in bytes),
+ * relative to a given position +whence+
+ * (indicating the beginning, end, or current position).
+ * - IO#rewind: Positions the stream at the beginning (also resetting the line number).
+ *
+ * === Open and Closed Streams
+ *
+ * A new \IO stream may be open for reading, open for writing, or both.
+ *
+ * A stream is automatically closed when claimed by the garbage collector.
+ *
+ * Attempted reading or writing on a closed stream raises an exception.
+ *
+ * The relevant methods:
+ *
+ * - IO#close: Closes the stream for both reading and writing.
+ * - IO#close_read: Closes the stream for reading.
+ * - IO#close_write: Closes the stream for writing.
+ * - IO#closed?: Returns whether the stream is closed.
+ *
+ * === End-of-Stream
+ *
+ * You can query whether a stream is positioned at its end:
+ *
+ * - IO#eof? (also aliased as +#eof+): Returns whether the stream is at end-of-stream.
+ *
+ * You can reposition to end-of-stream by using method IO#seek:
+ *
+ * f = File.new('t.txt')
+ * f.eof? # => false
+ * f.seek(0, :END)
+ * f.eof? # => true
+ * f.close
+ *
+ * Or by reading all stream content (which is slower than using IO#seek):
+ *
+ * f.rewind
+ * f.eof? # => false
+ * f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+ * f.eof? # => true
+ *
+ * == Line \IO
+ *
+ * You can read an \IO stream line-by-line using these methods:
+ *
+ * - IO#each_line: Reads each remaining line, passing it to the given block.
+ * - IO#gets: Returns the next line.
+ * - IO#readline: Like #gets, but raises an exception at end-of-stream.
+ * - IO#readlines: Returns all remaining lines in an array.
+ *
+ * Each of these reader methods accepts:
+ *
+ * - An optional line separator, +sep+;
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator].
+ * - An optional line-size limit, +limit+;
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit].
+ *
+ * For each of these reader methods, reading may begin mid-line,
+ * depending on the stream's position;
+ * see {Position}[rdoc-ref:IO@Position]:
+ *
+ * f = File.new('t.txt')
+ * f.pos = 27
+ * f.each_line {|line| p line }
+ * f.close
+ *
+ * Output:
+ *
+ * "rth line\n"
+ * "Fifth line\n"
+ *
+ * You can write to an \IO stream line-by-line using this method:
+ *
+ * - IO#puts: Writes objects to the stream.
+ *
+ * === Line Separator
+ *
+ * Each of these methods uses a line separator,
+ * which is the string that delimits lines:
+ *
+ * - IO.foreach.
+ * - IO.readlines.
+ * - IO#each_line.
+ * - IO#gets.
+ * - IO#readline.
+ * - IO#readlines.
+ *
+ * The default line separator is the given by the global variable $/,
+ * whose value is by default "\n".
+ * The line to be read next is all data from the current position
+ * to the next line separator:
+ *
+ * f = File.new('t.txt')
+ * f.gets # => "First line\n"
+ * f.gets # => "Second line\n"
+ * f.gets # => "\n"
+ * f.gets # => "Fourth line\n"
+ * f.gets # => "Fifth line\n"
+ * f.close
+ *
+ * You can specify a different line separator:
+ *
+ * f = File.new('t.txt')
+ * f.gets('l') # => "First l"
+ * f.gets('li') # => "ine\nSecond li"
+ * f.gets('lin') # => "ne\n\nFourth lin"
+ * f.gets # => "e\n"
+ * f.close
+ *
+ * There are two special line separators:
+ *
+ * - +nil+: The entire stream is read into a single string:
+ *
+ * f = File.new('t.txt')
+ * f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+ * f.close
+ *
+ * - '' (the empty string): The next "paragraph" is read
+ * (paragraphs being separated by two consecutive line separators):
+ *
+ * f = File.new('t.txt')
+ * f.gets('') # => "First line\nSecond line\n\n"
+ * f.gets('') # => "Fourth line\nFifth line\n"
+ * f.close
+ *
+ * === Line Limit
+ *
+ * Each of these methods uses a line limit,
+ * which specifies that the number of bytes returned may not be (much) longer
+ * than the given +limit+;
+ *
+ * - IO.foreach.
+ * - IO.readlines.
+ * - IO#each_line.
+ * - IO#gets.
+ * - IO#readline.
+ * - IO#readlines.
+ *
+ * A multi-byte character will not be split, and so a line may be slightly longer
+ * than the given limit.
+ *
+ * If +limit+ is not given, the line is determined only by +sep+.
+ *
+ * # Text with 1-byte characters.
+ * File.open('t.txt') {|f| f.gets(1) } # => "F"
+ * File.open('t.txt') {|f| f.gets(2) } # => "Fi"
+ * File.open('t.txt') {|f| f.gets(3) } # => "Fir"
+ * File.open('t.txt') {|f| f.gets(4) } # => "Firs"
+ * # No more than one line.
+ * File.open('t.txt') {|f| f.gets(10) } # => "First line"
+ * File.open('t.txt') {|f| f.gets(11) } # => "First line\n"
+ * File.open('t.txt') {|f| f.gets(12) } # => "First line\n"
+ *
+ * # Text with 2-byte characters, which will not be split.
+ * File.open('t.rus') {|f| f.gets(1).size } # => 1
+ * File.open('t.rus') {|f| f.gets(2).size } # => 1
+ * File.open('t.rus') {|f| f.gets(3).size } # => 2
+ * File.open('t.rus') {|f| f.gets(4).size } # => 2
+ *
+ * === Line Separator and Line Limit
+ *
+ * With arguments +sep+ and +limit+ given,
+ * combines the two behaviors:
+ *
+ * - Returns the next line as determined by line separator +sep+.
+ * - But returns no more bytes than are allowed by the limit.
+ *
+ * Example:
+ *
+ * File.open('t.txt') {|f| f.gets('li', 20) } # => "First li"
+ * File.open('t.txt') {|f| f.gets('li', 2) } # => "Fi"
+ *
+ * === Line Number
+ *
+ * A readable \IO stream has a non-negative integer line number.
+ *
+ * The relevant methods:
+ *
+ * - IO#lineno: Returns the line number.
+ * - IO#lineno=: Resets and returns the line number.
+ *
+ * Unless modified by a call to method IO#lineno=,
+ * the line number is the number of lines read
+ * by certain line-oriented methods,
+ * according to the given line separator +sep+:
+ *
+ * - IO.foreach: Increments the line number on each call to the block.
+ * - IO#each_line: Increments the line number on each call to the block.
+ * - IO#gets: Increments the line number.
+ * - IO#readline: Increments the line number.
+ * - IO#readlines: Increments the line number for each line read.
+ *
+ * A new stream is initially has line number zero (and position zero);
+ * method +rewind+ resets the line number (and position) to zero:
+ *
+ * f = File.new('t.txt')
+ * f.lineno # => 0
+ * f.gets # => "First line\n"
+ * f.lineno # => 1
+ * f.rewind
+ * f.lineno # => 0
+ * f.close
+ *
+ * Reading lines from a stream usually changes its line number:
+ *
+ * f = File.new('t.txt', 'r')
+ * f.lineno # => 0
+ * f.readline # => "This is line one.\n"
+ * f.lineno # => 1
+ * f.readline # => "This is the second line.\n"
+ * f.lineno # => 2
+ * f.readline # => "Here's the third line.\n"
+ * f.lineno # => 3
+ * f.eof? # => true
+ * f.close
+ *
+ * Iterating over lines in a stream usually changes its line number:
+ *
+ * File.open('t.txt') do |f|
+ * f.each_line do |line|
+ * p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}"
+ * end
+ * end
+ *
+ * Output:
+ *
+ * "position=11 eof?=false lineno=1"
+ * "position=23 eof?=false lineno=2"
+ * "position=24 eof?=false lineno=3"
+ * "position=36 eof?=false lineno=4"
+ * "position=47 eof?=true lineno=5"
+ *
+ * Unlike the stream's {position}[rdoc-ref:IO@Position],
+ * the line number does not affect where the next read or write will occur:
+ *
+ * f = File.new('t.txt')
+ * f.lineno = 1000
+ * f.lineno # => 1000
+ * f.gets # => "First line\n"
+ * f.lineno # => 1001
+ * f.close
+ *
+ * Associated with the line number is the global variable $.:
+ *
+ * - When a stream is opened, $. is not set;
+ * its value is left over from previous activity in the process:
+ *
+ * $. = 41
+ * f = File.new('t.txt')
+ * $. = 41
+ * # => 41
+ * f.close
+ *
+ * - When a stream is read, #. is set to the line number for that stream:
+ *
+ * f0 = File.new('t.txt')
+ * f1 = File.new('t.dat')
+ * f0.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
+ * $. # => 5
+ * f1.readlines # => ["\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"]
+ * $. # => 1
+ * f0.close
+ * f1.close
+ *
+ * - Methods IO#rewind and IO#seek do not affect $.:
+ *
+ * f = File.new('t.txt')
+ * f.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
+ * $. # => 5
+ * f.rewind
+ * f.seek(0, :SET)
+ * $. # => 5
+ * f.close
+ *
+ * == Character \IO
+ *
+ * You can process an \IO stream character-by-character using these methods:
+ *
+ * - IO#getc: Reads and returns the next character from the stream.
+ * - IO#readchar: Like #getc, but raises an exception at end-of-stream.
+ * - IO#ungetc: Pushes back ("unshifts") a character or integer onto the stream.
+ * - IO#putc: Writes a character to the stream.
+ * - IO#each_char: Reads each remaining character in the stream,
+ * passing the character to the given block.
+ * == Byte \IO
+ *
+ * You can process an \IO stream byte-by-byte using these methods:
+ *
+ * - IO#getbyte: Returns the next 8-bit byte as an integer in range 0..255.
+ * - IO#readbyte: Like #getbyte, but raises an exception if at end-of-stream.
+ * - IO#ungetbyte: Pushes back ("unshifts") a byte back onto the stream.
+ * - IO#each_byte: Reads each remaining byte in the stream,
+ * passing the byte to the given block.
+ *
+ * == Codepoint \IO
+ *
+ * You can process an \IO stream codepoint-by-codepoint:
+ *
+ * - IO#each_codepoint: Reads each remaining codepoint, passing it to the given block.
+ *
* == What's Here
*
* First, what's elsewhere. \Class \IO:
@@ -14772,11 +15096,11 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - #read_nonblock: the next _n_ bytes read from +self+ for a given _n_,
* in non-block mode.
* - #readbyte: Returns the next byte read from +self+;
- * same as #getbyte, but raises an exception on end-of-file.
+ * same as #getbyte, but raises an exception on end-of-stream.
* - #readchar: Returns the next character read from +self+;
- * same as #getc, but raises an exception on end-of-file.
+ * same as #getc, but raises an exception on end-of-stream.
* - #readline: Returns the next line read from +self+;
- * same as #getline, but raises an exception of end-of-file.
+ * same as #getline, but raises an exception of end-of-stream.
* - #readlines: Returns an array of all lines read read from +self+.
* - #readpartial: Returns up to the given number of bytes from +self+.
*
@@ -14836,7 +15160,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - #binmode?: Returns whether +self+ is in binary mode.
* - #close_on_exec?: Returns the close-on-exec flag for +self+.
* - #closed?: Returns whether +self+ is closed.
- * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-file.
+ * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-stream.
* - #external_encoding: Returns the external encoding object for +self+.
* - #fileno (aliased as #to_i): Returns the integer file descriptor for +self+
* - #internal_encoding: Returns the internal encoding object for +self+.
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 95be7a7e273930..3836841f3fabbf 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -357,7 +357,7 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
"bundle config unset deployment"
end
msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
- "freeze \nby running `#{suggested_command}`."
+ "freeze \nby running `#{suggested_command}`." if suggested_command
end
added = []
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index 0f08e049d8d49d..70967522af29c2 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -7,6 +7,7 @@ class EnvironmentPreserver
BUNDLE_BIN_PATH
BUNDLE_GEMFILE
BUNDLER_VERSION
+ BUNDLER_SETUP
GEM_HOME
GEM_PATH
MANPATH
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index f5fe2e64aec527..949e8264baf831 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -79,9 +79,7 @@ def materialize_for_installation
candidates = if source.is_a?(Source::Path) || !ruby_platform_materializes_to_ruby_platform?
target_platform = ruby_platform_materializes_to_ruby_platform? ? platform : local_platform
- source.specs.search(Dependency.new(name, version)).select do |spec|
- MatchPlatform.platforms_match?(spec.platform, target_platform)
- end
+ GemHelpers.select_best_platform_match(source.specs.search(Dependency.new(name, version)), target_platform)
else
source.specs.search(self)
end
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index d53d6880095853..12d67890650fc6 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -308,6 +308,28 @@ def match_platforms?(platform, platforms)
end
end
+ # On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory.
+ class Specification
+ if /^universal\.(?.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM)
+ local_platform = Platform.local
+ if local_platform.cpu == "universal"
+ ORIGINAL_LOCAL_PLATFORM = local_platform.to_s.freeze
+
+ local_platform.cpu = if arch == "arm64e" # arm64e is only permitted for Apple system binaries
+ "arm64"
+ else
+ arch
+ end
+
+ def extensions_dir
+ Gem.default_ext_dir_for(base_dir) ||
+ File.join(base_dir, "extensions", ORIGINAL_LOCAL_PLATFORM,
+ Gem.extension_api_version)
+ end
+ end
+ end
+ end
+
require "rubygems/util"
Util.singleton_class.module_eval do
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 899eb68e0a86ce..0a6afe0e5a5beb 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -284,6 +284,7 @@ def set_bundle_variables
Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", exe_file
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s
Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION
+ Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__)
end
def set_path
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
index 8eff00bf3db4e8..ef97d52ae72065 100644
--- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
+++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
@@ -425,7 +425,7 @@ def dynamic_width_tput
end
def unix?
- RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
end
def truncate(string, width)
diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb
index 4279b6d05fc9ff..abd85116af8c6b 100644
--- a/lib/error_highlight/version.rb
+++ b/lib/error_highlight/version.rb
@@ -1,3 +1,3 @@
module ErrorHighlight
- VERSION = "0.4.0"
+ VERSION = "0.5.0"
end
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 745170a121c1bf..a33f086a3ea529 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -1165,7 +1165,7 @@ def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
#
# Keyword arguments:
#
- # - force: true - ignores raised exceptions of Errno::ENOENT
+ # - force: true - ignores raised exceptions of StandardError
# and its descendants.
# - noop: true - does not remove files; returns +nil+.
# - verbose: true - prints an equivalent command:
@@ -1248,7 +1248,7 @@ def rm_f(list, noop: nil, verbose: nil)
#
# Keyword arguments:
#
- # - force: true - ignores raised exceptions of Errno::ENOENT
+ # - force: true - ignores raised exceptions of StandardError
# and its descendants.
# - noop: true - does not remove entries; returns +nil+.
# - secure: true - removes +src+ securely;
@@ -1315,7 +1315,7 @@ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
# see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
#
# Optional argument +force+ specifies whether to ignore
- # raised exceptions of Errno::ENOENT and its descendants.
+ # raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
@@ -1384,12 +1384,10 @@ def remove_entry_secure(path, force = false)
ent.remove
rescue
raise unless force
- raise unless Errno::ENOENT === $!
end
end
rescue
raise unless force
- raise unless Errno::ENOENT === $!
end
module_function :remove_entry_secure
@@ -1415,7 +1413,7 @@ def fu_stat_identical_entry?(a, b) #:nodoc:
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
- # raised exceptions of Errno::ENOENT and its descendants.
+ # raised exceptions of StandardError and its descendants.
#
# Related: FileUtils.remove_entry_secure.
#
@@ -1425,12 +1423,10 @@ def remove_entry(path, force = false)
ent.remove
rescue
raise unless force
- raise unless Errno::ENOENT === $!
end
end
rescue
raise unless force
- raise unless Errno::ENOENT === $!
end
module_function :remove_entry
@@ -1441,7 +1437,7 @@ def remove_entry(path, force = false)
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
- # raised exceptions of Errno::ENOENT and its descendants.
+ # raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
@@ -1449,7 +1445,6 @@ def remove_file(path, force = false)
Entry_.new(path).remove_file
rescue
raise unless force
- raise unless Errno::ENOENT === $!
end
module_function :remove_file
@@ -1461,7 +1456,7 @@ def remove_file(path, force = false)
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
- # raised exceptions of Errno::ENOENT and its descendants.
+ # raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
diff --git a/lib/irb.rb b/lib/irb.rb
index 57ec9ebaebc9d9..04009664efd47a 100644
--- a/lib/irb.rb
+++ b/lib/irb.rb
@@ -427,6 +427,7 @@ def initialize(workspace = nil, input_method = nil)
@context = Context.new(self, workspace, input_method)
@context.main.extend ExtendCommandBundle
@context.command_aliases.each do |alias_name, cmd_name|
+ next if @context.symbol_alias(alias_name)
@context.main.install_alias_method(alias_name, cmd_name)
end
@signal_status = :IN_IRB
diff --git a/lib/irb/cmd/ls.rb b/lib/irb/cmd/ls.rb
index f4a7348bd12ad3..77cf071783dbc1 100644
--- a/lib/irb/cmd/ls.rb
+++ b/lib/irb/cmd/ls.rb
@@ -9,6 +9,15 @@ module IRB
module ExtendCommand
class Ls < Nop
+ def self.transform_args(args)
+ if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/)
+ args = match[:args]
+ "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/"
+ else
+ args
+ end
+ end
+
def execute(*arg, grep: nil)
o = Output.new(grep: grep)
diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb
index f8a17822dfe4d7..1fcff3e8979a1a 100644
--- a/lib/irb/cmd/show_source.rb
+++ b/lib/irb/cmd/show_source.rb
@@ -9,6 +9,24 @@ module IRB
module ExtendCommand
class ShowSource < Nop
+ class << self
+ def transform_args(args)
+ # Return a string literal as is for backward compatibility
+ if args.empty? || string_literal?(args)
+ args
+ else # Otherwise, consider the input as a String for convenience
+ args.strip.dump
+ end
+ end
+
+ private
+
+ def string_literal?(args)
+ sexp = Ripper.sexp(args)
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
+ end
+ end
+
def execute(str = nil)
unless str.is_a?(String)
puts "Error: Expected a string but got #{str.inspect}"
diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb
index dbd652769eb34c..b41d5de35e3119 100644
--- a/lib/irb/completion.rb
+++ b/lib/irb/completion.rb
@@ -64,25 +64,27 @@ def self.absolute_path?(p) # TODO Remove this method after 2.6 EOL.
if File.respond_to?(:absolute_path?)
File.absolute_path?(p)
else
- if File.absolute_path(p) == p
- true
- else
- false
- end
+ File.absolute_path(p) == p
end
end
+ GEM_PATHS =
+ if defined?(Gem::Specification)
+ Gem::Specification.latest_specs(true).map { |s|
+ s.require_paths.map { |p|
+ if absolute_path?(p)
+ p
+ else
+ File.join(s.full_gem_path, p)
+ end
+ }
+ }.flatten
+ else
+ []
+ end.freeze
+
def self.retrieve_gem_and_system_load_path
- gem_paths = Gem::Specification.latest_specs(true).map { |s|
- s.require_paths.map { |p|
- if absolute_path?(p)
- p
- else
- File.join(s.full_gem_path, p)
- end
- }
- }.flatten if defined?(Gem::Specification)
- candidates = (gem_paths.to_a | $LOAD_PATH)
+ candidates = (GEM_PATHS | $LOAD_PATH)
candidates.map do |p|
if p.respond_to?(:to_path)
p.to_path
diff --git a/lib/irb/context.rb b/lib/irb/context.rb
index d1ae2cb605b936..72c74f081dca0e 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -484,9 +484,16 @@ def evaluate(line, line_no, exception: nil) # :nodoc:
end
# Transform a non-identifier alias (ex: @, $)
- command = line.split(/\s/, 2).first
+ command, args = line.split(/\s/, 2)
if original = symbol_alias(command)
line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s)
+ command = original
+ end
+
+ # Hook command-specific transformation
+ command_class = ExtendCommandBundle.load_command(command)
+ if command_class&.respond_to?(:transform_args)
+ line = "#{command} #{command_class.transform_args(args)}"
end
set_last_value(@workspace.evaluate(self, line, irb_path, line_no))
diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb
index 08a258fc53212c..acc23c9920f1ca 100644
--- a/lib/irb/extend-command.rb
+++ b/lib/irb/extend-command.rb
@@ -147,6 +147,20 @@ def irb_context
]
+ # Convert a command name to its implementation class if such command exists
+ def self.load_command(command)
+ command = command.to_sym
+ @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases|
+ next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command }
+
+ if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false)
+ require_relative load_file
+ end
+ return ExtendCommand.const_get(cmd_class, false)
+ end
+ nil
+ end
+
# Installs the default irb commands:
#
# +irb_current_working_workspace+:: Context#main
diff --git a/lib/irb/init.rb b/lib/irb/init.rb
index 09099f88b77346..8c9d473b74d84c 100644
--- a/lib/irb/init.rb
+++ b/lib/irb/init.rb
@@ -159,7 +159,10 @@ def IRB.init_config(ap_path)
@CONF[:AT_EXIT] = []
- @CONF[:COMMAND_ALIASES] = {}
+ @CONF[:COMMAND_ALIASES] = {
+ :'$' => :show_source,
+ :'@' => :whereami,
+ }
end
def IRB.set_measure_callback(type = nil, arg = nil, &block)
diff --git a/lib/mjit/compiler.rb b/lib/mjit/compiler.rb
index 55fcee6b877fcf..9e6ea0a1196d48 100644
--- a/lib/mjit/compiler.rb
+++ b/lib/mjit/compiler.rb
@@ -353,10 +353,20 @@ def compile_ivar(insn_name, stack_size, pos, status, operands, body)
ic_copy = (status.is_entries + (C.iseq_inline_storage_entry.new(operands[1]) - body.is_entries)).iv_cache
dest_shape_id = ic_copy.value >> C.SHAPE_FLAG_SHIFT
attr_index = ic_copy.value & ((1 << C.SHAPE_FLAG_SHIFT) - 1)
+
+ capa = nil
source_shape_id = if dest_shape_id == C.INVALID_SHAPE_ID
dest_shape_id
else
- C.rb_shape_get_shape_by_id(dest_shape_id).parent_id
+ parent_id = C.rb_shape_get_shape_by_id(dest_shape_id).parent_id
+ parent = C.rb_shape_get_shape_by_id(parent_id)
+
+ if parent.type == C.SHAPE_CAPACITY_CHANGE
+ capa = parent.capacity
+ parent.parent_id
+ else
+ parent_id
+ end
end
src = +''
@@ -374,9 +384,9 @@ def compile_ivar(insn_name, stack_size, pos, status, operands, body)
src << " const shape_id_t dest_shape_id = (shape_id_t)#{dest_shape_id};\n"
src << " if (source_shape_id == ROBJECT_SHAPE_ID(obj) && \n"
src << " dest_shape_id != ROBJECT_SHAPE_ID(obj)) {\n"
- src << " if (UNLIKELY(index >= ROBJECT_NUMIV(obj))) {\n"
- src << " rb_init_iv_list(obj);\n"
- src << " }\n"
+ # Conditionally generate a capacity change if there is one
+ # between the destination and the parent IV set
+ src << " rb_ensure_iv_list_size(obj, ROBJECT_IV_CAPACITY(obj), #{capa});\n" if capa
src << " ROBJECT_SET_SHAPE_ID(obj, dest_shape_id);\n"
src << " VALUE *ptr = ROBJECT_IVPTR(obj);\n"
src << " RB_OBJ_WRITE(obj, &ptr[index], stack[#{stack_size - 1}]);\n"
diff --git a/lib/net/http.rb b/lib/net/http.rb
index b602d2d0b0f1ca..16137cebf22d12 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -298,98 +298,110 @@ class HTTPHeaderSyntaxError < StandardError; end
#
# Compression can be disabled through the Accept-Encoding: identity header.
#
- # == HTTP Request Classes
- #
- # Here is the HTTP request class hierarchy.
- #
- # * Net::HTTPRequest
- # * Net::HTTP::Get
- # * Net::HTTP::Head
- # * Net::HTTP::Post
- # * Net::HTTP::Patch
- # * Net::HTTP::Put
- # * Net::HTTP::Proppatch
- # * Net::HTTP::Lock
- # * Net::HTTP::Unlock
- # * Net::HTTP::Options
- # * Net::HTTP::Propfind
- # * Net::HTTP::Delete
- # * Net::HTTP::Move
- # * Net::HTTP::Copy
- # * Net::HTTP::Mkcol
- # * Net::HTTP::Trace
- #
- # == HTTP Response Classes
- #
- # Here is HTTP response class hierarchy. All classes are defined in Net
- # module and are subclasses of Net::HTTPResponse.
- #
- # HTTPUnknownResponse:: For unhandled HTTP extensions
- # HTTPInformation:: 1xx
- # HTTPContinue:: 100
- # HTTPSwitchProtocol:: 101
- # HTTPProcessing:: 102
- # HTTPEarlyHints:: 103
- # HTTPSuccess:: 2xx
- # HTTPOK:: 200
- # HTTPCreated:: 201
- # HTTPAccepted:: 202
- # HTTPNonAuthoritativeInformation:: 203
- # HTTPNoContent:: 204
- # HTTPResetContent:: 205
- # HTTPPartialContent:: 206
- # HTTPMultiStatus:: 207
- # HTTPAlreadyReported:: 208
- # HTTPIMUsed:: 226
- # HTTPRedirection:: 3xx
- # HTTPMultipleChoices:: 300
- # HTTPMovedPermanently:: 301
- # HTTPFound:: 302
- # HTTPSeeOther:: 303
- # HTTPNotModified:: 304
- # HTTPUseProxy:: 305
- # HTTPTemporaryRedirect:: 307
- # HTTPPermanentRedirect:: 308
- # HTTPClientError:: 4xx
- # HTTPBadRequest:: 400
- # HTTPUnauthorized:: 401
- # HTTPPaymentRequired:: 402
- # HTTPForbidden:: 403
- # HTTPNotFound:: 404
- # HTTPMethodNotAllowed:: 405
- # HTTPNotAcceptable:: 406
- # HTTPProxyAuthenticationRequired:: 407
- # HTTPRequestTimeOut:: 408
- # HTTPConflict:: 409
- # HTTPGone:: 410
- # HTTPLengthRequired:: 411
- # HTTPPreconditionFailed:: 412
- # HTTPRequestEntityTooLarge:: 413
- # HTTPRequestURITooLong:: 414
- # HTTPUnsupportedMediaType:: 415
- # HTTPRequestedRangeNotSatisfiable:: 416
- # HTTPExpectationFailed:: 417
- # HTTPMisdirectedRequest:: 421
- # HTTPUnprocessableEntity:: 422
- # HTTPLocked:: 423
- # HTTPFailedDependency:: 424
- # HTTPUpgradeRequired:: 426
- # HTTPPreconditionRequired:: 428
- # HTTPTooManyRequests:: 429
- # HTTPRequestHeaderFieldsTooLarge:: 431
- # HTTPUnavailableForLegalReasons:: 451
- # HTTPServerError:: 5xx
- # HTTPInternalServerError:: 500
- # HTTPNotImplemented:: 501
- # HTTPBadGateway:: 502
- # HTTPServiceUnavailable:: 503
- # HTTPGatewayTimeOut:: 504
- # HTTPVersionNotSupported:: 505
- # HTTPVariantAlsoNegotiates:: 506
- # HTTPInsufficientStorage:: 507
- # HTTPLoopDetected:: 508
- # HTTPNotExtended:: 510
- # HTTPNetworkAuthenticationRequired:: 511
+ # == \HTTP Request Classes
+ #
+ # Here is the hierarchy of \HTTP request classes
+ #
+ # - Net::HTTPRequest
+ #
+ # - Net::HTTP::Get
+ # - Net::HTTP::Head
+ # - Net::HTTP::Post
+ # - Net::HTTP::Patch
+ # - Net::HTTP::Put
+ # - Net::HTTP::Proppatch
+ # - Net::HTTP::Lock
+ # - Net::HTTP::Unlock
+ # - Net::HTTP::Options
+ # - Net::HTTP::Propfind
+ # - Net::HTTP::Delete
+ # - Net::HTTP::Move
+ # - Net::HTTP::Copy
+ # - Net::HTTP::Mkcol
+ # - Net::HTTP::Trace
+ #
+ # == \HTTP Response Classes
+ #
+ # Here is the \HTTP response class hierarchy (with status codes):
+ #
+ # - Net::HTTPResponse:
+ #
+ # - Net::HTTPUnknownResponse (for unhandled \HTTP extensions).
+ #
+ # - Net::HTTPInformation:
+ #
+ # - Net::HTTPContinue (100)
+ # - Net::HTTPSwitchProtocol (101)
+ # - Net::HTTPProcessing (102)
+ # - Net::HTTPEarlyHints (103)
+ #
+ # - Net::HTTPSuccess:
+ #
+ # - Net::HTTPOK (200)
+ # - Net::HTTPCreated (201)
+ # - Net::HTTPAccepted (202)
+ # - Net::HTTPNonAuthoritativeInformation (203)
+ # - Net::HTTPNoContent (204)
+ # - Net::HTTPResetContent (205)
+ # - Net::HTTPPartialContent (206)
+ # - Net::HTTPMultiStatus (207)
+ # - Net::HTTPAlreadyReported (208)
+ # - Net::HTTPIMUsed (226)
+ #
+ # - Net::HTTPRedirection:
+ #
+ # - Net::HTTPMultipleChoices (300)
+ # - Net::HTTPMovedPermanently (301)
+ # - Net::HTTPFound (302)
+ # - Net::HTTPSeeOther (303)
+ # - Net::HTTPNotModified (304)
+ # - Net::HTTPUseProxy (305)
+ # - Net::HTTPTemporaryRedirect (307)
+ # - Net::HTTPPermanentRedirect (308)
+ #
+ # - Net::HTTPClientError:
+ #
+ # - Net::HTTPBadRequest (400)
+ # - Net::HTTPUnauthorized (401)
+ # - Net::HTTPPaymentRequired (402)
+ # - Net::HTTPForbidden (403)
+ # - Net::HTTPNotFound (404)
+ # - Net::HTTPMethodNotAllowed (405)
+ # - Net::HTTPNotAcceptable (406)
+ # - Net::HTTPProxyAuthenticationRequired (407)
+ # - Net::HTTPRequestTimeOut (408)
+ # - Net::HTTPConflict (409)
+ # - Net::HTTPGone (410)
+ # - Net::HTTPLengthRequired (411)
+ # - Net::HTTPPreconditionFailed (412)
+ # - Net::HTTPRequestEntityTooLarge (413)
+ # - Net::HTTPRequestURITooLong (414)
+ # - Net::HTTPUnsupportedMediaType (415)
+ # - Net::HTTPRequestedRangeNotSatisfiable (416)
+ # - Net::HTTPExpectationFailed (417)
+ # - Net::HTTPMisdirectedRequest (421)
+ # - Net::HTTPUnprocessableEntity (422)
+ # - Net::HTTPLocked (423)
+ # - Net::HTTPFailedDependency (424)
+ # - Net::HTTPUpgradeRequired (426)
+ # - Net::HTTPPreconditionRequired (428)
+ # - Net::HTTPTooManyRequests (429)
+ # - Net::HTTPRequestHeaderFieldsTooLarge (431)
+ # - Net::HTTPUnavailableForLegalReasons (451)
+ #
+ # - Net::HTTPServerError:
+ #
+ # - Net::HTTPInternalServerError (500)
+ # - Net::HTTPNotImplemented (501)
+ # - Net::HTTPBadGateway (502)
+ # - Net::HTTPServiceUnavailable (503)
+ # - Net::HTTPGatewayTimeOut (504)
+ # - Net::HTTPVersionNotSupported (505)
+ # - Net::HTTPVariantAlsoNegotiates (506)
+ # - Net::HTTPInsufficientStorage (507)
+ # - Net::HTTPLoopDetected (508)
+ # - Net::HTTPNotExtended (510)
+ # - Net::HTTPNetworkAuthenticationRequired (511)
#
# There is also the Net::HTTPBadResponse exception which is raised when
# there is a protocol error.
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 915a899f38edfb..43b1c4528871bf 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -1348,3 +1348,5 @@ def default_gem_load_paths
require_relative "rubygems/core_ext/kernel_gem"
require_relative "rubygems/core_ext/kernel_require"
require_relative "rubygems/core_ext/kernel_warn"
+
+require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"]
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 071687c63fe46a..c04c01f2585018 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -5,6 +5,7 @@
require_relative "../local_remote_options"
require_relative "../validator"
require_relative "../version_option"
+require_relative "../update_suggestion"
##
# Gem installer command line tool
@@ -17,6 +18,7 @@ class Gem::Commands::InstallCommand < Gem::Command
include Gem::VersionOption
include Gem::LocalRemoteOptions
include Gem::InstallUpdateOptions
+ include Gem::UpdateSuggestion
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
@@ -168,6 +170,8 @@ def execute
show_installed
+ say update_suggestion if eglible_for_update?
+
terminate_interaction exit_code
end
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index c53e209ae8ace7..4aa8b4d33ac50a 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -371,11 +371,44 @@ def backtrace
@backtrace || $DEBUG
end
+ # Check state file is writable. Creates empty file if not present to ensure we can write to it.
+ def state_file_writable?
+ if File.exist?(state_file_name)
+ File.writable?(state_file_name)
+ else
+ require "fileutils"
+ FileUtils.mkdir_p File.dirname(state_file_name)
+ File.open(state_file_name, "w") {}
+ true
+ end
+ rescue Errno::EACCES
+ false
+ end
+
# The name of the configuration file.
def config_file_name
@config_file_name || Gem.config_file
end
+ # The name of the state file.
+ def state_file_name
+ Gem.state_file
+ end
+
+ # Reads time of last update check from state file
+ def last_update_check
+ if File.readable?(state_file_name)
+ File.read(state_file_name).to_i
+ else
+ 0
+ end
+ end
+
+ # Writes time of last update check to state file
+ def last_update_check=(timestamp)
+ File.write(state_file_name, timestamp.to_s) if state_file_writable?
+ end
+
# Delegates to @hash
def each(&block)
hash = @hash.dup
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index e12c13cb46d4f6..8daff0bc30a723 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -133,6 +133,13 @@ def self.config_file
@config_file ||= find_config_file.tap(&Gem::UNTAINT)
end
+ ##
+ # The path to standard location of the user's state file.
+
+ def self.state_file
+ @state_file ||= File.join(Gem.state_home, "gem", "last_update_check").tap(&Gem::UNTAINT)
+ end
+
##
# The path to standard location of the user's cache directory.
@@ -147,6 +154,13 @@ def self.data_home
@data_home ||= (ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, ".local", "share"))
end
+ ##
+ # The path to standard location of the user's state directory.
+
+ def self.state_home
+ @data_home ||= (ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state"))
+ end
+
##
# How String Gem paths should be split. Overridable for esoteric platforms.
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
index e33b07a8a23356..24c1d3ae6e0667 100644
--- a/lib/rubygems/ext/cargo_builder.rb
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -37,6 +37,7 @@ def build_crate(dest_path, results, args, cargo_dir)
def build_env
build_env = rb_config_env
build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC")
+ build_env["RUSTFLAGS"] = "#{ENV["RUSTFLAGS"]} --cfg=rb_sys_gem".strip
build_env
end
@@ -92,6 +93,9 @@ def platform_specific_rustc_args(dest_dir, flags = [])
# run on one that isn't the missing libraries will cause the extension
# to fail on start.
flags += ["-C", "link-arg=-static-libgcc"]
+ elsif darwin_target?
+ # Ventura does not always have this flag enabled
+ flags += ["-C", "link-arg=-Wl,-undefined,dynamic_lookup"]
end
flags
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 6f4ead1af81639..1e7a5c503abb31 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -97,7 +97,6 @@ def initialize(arch)
when /darwin(\d+)?/ then [ "darwin", $1 ]
when /^macruby$/ then [ "macruby", nil ]
when /freebsd(\d+)?/ then [ "freebsd", $1 ]
- when /hpux(\d+)?/ then [ "hpux", $1 ]
when /^java$/, /^jruby$/ then [ "java", nil ]
when /^java([\d.]*)/ then [ "java", $1 ]
when /^dalvik(\d+)?$/ then [ "dalvik", $1 ]
@@ -112,7 +111,6 @@ def initialize(arch)
[os, version]
when /netbsdelf/ then [ "netbsdelf", nil ]
when /openbsd(\d+\.\d+)?/ then [ "openbsd", $1 ]
- when /bitrig(\d+\.\d+)?/ then [ "bitrig", $1 ]
when /solaris(\d+\.\d+)?/ then [ "solaris", $1 ]
# test
when /^(\w+_platform)(\d+)?/ then [ $1, $2 ]
diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb
index 693cd2793a986a..ad6e45005bc44a 100644
--- a/lib/rubygems/request_set/gem_dependency_api.rb
+++ b/lib/rubygems/request_set/gem_dependency_api.rb
@@ -214,7 +214,7 @@ def initialize(set, path)
git_source :github do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/"
- "git://github.com/#{repo_name}.git"
+ "https://github.com/#{repo_name}.git"
end
git_source :bitbucket do |repo_name|
diff --git a/lib/rubygems/update_suggestion.rb b/lib/rubygems/update_suggestion.rb
new file mode 100644
index 00000000000000..c2e81b2374389e
--- /dev/null
+++ b/lib/rubygems/update_suggestion.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+##
+# Mixin methods for Gem::Command to promote available RubyGems update
+
+module Gem::UpdateSuggestion
+ # list taken from https://github.com/watson/ci-info/blob/7a3c30d/index.js#L56-L66
+ CI_ENV_VARS = [
+ "CI", # Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari
+ "CONTINUOUS_INTEGRATION", # Travis CI, Cirrus CI
+ "BUILD_NUMBER", # Jenkins, TeamCity
+ "CI_APP_ID", "CI_BUILD_ID", "CI_BUILD_NUMBER", # Applfow
+ "RUN_ID" # TaskCluster, dsari
+ ].freeze
+
+ ONE_WEEK = 7 * 24 * 60 * 60
+
+ ##
+ # Message to promote available RubyGems update with related gem update command.
+
+ def update_suggestion
+ <<-MESSAGE
+
+A new release of RubyGems is available: #{Gem.rubygems_version} → #{Gem.latest_rubygems_version}!
+Run `gem update --system #{Gem.latest_rubygems_version}` to update your installation.
+
+ MESSAGE
+ end
+
+ ##
+ # Determines if current environment is eglible for update suggestion.
+
+ def eglible_for_update?
+ # explicit opt-out
+ return false if Gem.configuration[:prevent_update_suggestion]
+ return false if ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"]
+
+ # focus only on human usage of final RubyGems releases
+ return false unless Gem.ui.tty?
+ return false if Gem.rubygems_version.prerelease?
+ return false if Gem.disable_system_update_message
+ return false if ci?
+
+ # check makes sense only when we can store timestamp of last try
+ # otherwise we will not be able to prevent "annoying" update message
+ # on each command call
+ return unless Gem.configuration.state_file_writable?
+
+ # load time of last check, ensure the difference is enough to repeat the suggestion
+ check_time = Time.now.to_i
+ last_update_check = Gem.configuration.last_update_check
+ return false if (check_time - last_update_check) < ONE_WEEK
+
+ # compare current and latest version, this is the part where
+ # latest rubygems spec is fetched from remote
+ (Gem.rubygems_version < Gem.latest_rubygems_version).tap do |eglible|
+ # store the time of last successful check into state file
+ Gem.configuration.last_update_check = check_time
+
+ return eglible
+ end
+ rescue # don't block install command on any problem
+ false
+ end
+
+ def ci?
+ CI_ENV_VARS.any? {|var| ENV.include?(var) }
+ end
+end
diff --git a/mjit_c.rb b/mjit_c.rb
index 7684755b6be454..533c97285dd203 100644
--- a/mjit_c.rb
+++ b/mjit_c.rb
@@ -13,6 +13,30 @@ def SHAPE_FLAG_SHIFT
Primitive.cexpr! 'UINT2NUM(SHAPE_FLAG_SHIFT)'
end
+ def SHAPE_ROOT
+ Primitive.cexpr! 'UINT2NUM(SHAPE_ROOT)'
+ end
+
+ def SHAPE_IVAR
+ Primitive.cexpr! 'UINT2NUM(SHAPE_IVAR)'
+ end
+
+ def SHAPE_FROZEN
+ Primitive.cexpr! 'UINT2NUM(SHAPE_FROZEN)'
+ end
+
+ def SHAPE_CAPACITY_CHANGE
+ Primitive.cexpr! 'UINT2NUM(SHAPE_CAPACITY_CHANGE)'
+ end
+
+ def SHAPE_IVAR_UNDEF
+ Primitive.cexpr! 'UINT2NUM(SHAPE_IVAR_UNDEF)'
+ end
+
+ def SHAPE_INITIAL_CAPACITY
+ Primitive.cexpr! 'UINT2NUM(SHAPE_INITIAL_CAPACITY)'
+ end
+
def ROBJECT_EMBED_LEN_MAX
Primitive.cexpr! 'INT2NUM(RBIMPL_EMBED_LEN_MAX_OF(VALUE))'
end
@@ -598,7 +622,9 @@ def C.rb_shape
edges: [CType::Pointer.new { self.rb_id_table }, Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), edges)")],
edge_name: [self.ID, Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), edge_name)")],
next_iv_index: [self.attr_index_t, Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), next_iv_index)")],
+ capacity: [CType::Immediate.parse("uint32_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), capacity)")],
type: [CType::Immediate.parse("uint8_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), type)")],
+ size_pool_index: [CType::Immediate.parse("uint8_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), size_pool_index)")],
parent_id: [self.shape_id_t, Primitive.cexpr!("OFFSETOF((*((struct rb_shape *)NULL)), parent_id)")],
)
end
diff --git a/object.c b/object.c
index 307ee866c75b08..6251dacae22dd9 100644
--- a/object.c
+++ b/object.c
@@ -33,6 +33,7 @@
#include "internal/string.h"
#include "internal/symbol.h"
#include "internal/variable.h"
+#include "variable.h"
#include "probes.h"
#include "ruby/encoding.h"
#include "ruby/st.h"
@@ -272,21 +273,63 @@ rb_obj_singleton_class(VALUE obj)
MJIT_FUNC_EXPORTED void
rb_obj_copy_ivar(VALUE dest, VALUE obj)
{
- uint32_t dest_len = ROBJECT_NUMIV(dest);
- uint32_t src_len = ROBJECT_NUMIV(obj);
+ RUBY_ASSERT(!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE));
- if (dest_len < src_len) {
- rb_ensure_iv_list_size(dest, dest_len, src_len);
- RUBY_ASSERT(!(RBASIC(dest)->flags & ROBJECT_EMBED));
+ RUBY_ASSERT(BUILTIN_TYPE(dest) == BUILTIN_TYPE(obj));
+ uint32_t src_num_ivs = RBASIC_IV_COUNT(obj);
+ rb_shape_t * src_shape = rb_shape_get_shape(obj);
+ rb_shape_t * shape_to_set_on_dest = src_shape;
+ VALUE * src_buf;
+ VALUE * dest_buf;
+
+ if (!src_num_ivs) {
+ return;
}
- else {
- RUBY_ASSERT((RBASIC(dest)->flags & ROBJECT_EMBED));
+
+ // The copy should be mutable, so we don't want the frozen shape
+ if (rb_shape_frozen_shape_p(src_shape)) {
+ shape_to_set_on_dest = rb_shape_get_parent(src_shape);
+ }
+
+ src_buf = ROBJECT_IVPTR(obj);
+ dest_buf = ROBJECT_IVPTR(dest);
+
+ rb_shape_t * initial_shape = rb_shape_get_shape(dest);
+
+ if (initial_shape->size_pool_index != src_shape->size_pool_index) {
+ RUBY_ASSERT(initial_shape->parent_id == ROOT_SHAPE_ID || initial_shape->type == SHAPE_ROOT);
+
+ shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
}
- VALUE * dest_buf = ROBJECT_IVPTR(dest);
- VALUE * src_buf = ROBJECT_IVPTR(obj);
+ RUBY_ASSERT(src_num_ivs <= shape_to_set_on_dest->capacity);
+ if (initial_shape->capacity < shape_to_set_on_dest->capacity) {
+ rb_ensure_iv_list_size(dest, initial_shape->capacity, shape_to_set_on_dest->capacity);
+ dest_buf = ROBJECT_IVPTR(dest);
+
+ rb_shape_t * initial_shape = rb_shape_get_shape(dest);
+
+ if (initial_shape->size_pool_index != src_shape->size_pool_index) {
+ RUBY_ASSERT(initial_shape->parent_id == ROOT_SHAPE_ID || initial_shape->type == SHAPE_ROOT);
+
+ shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
+ }
- MEMCPY(dest_buf, src_buf, VALUE, ROBJECT_IV_COUNT(obj));
+ RUBY_ASSERT(src_num_ivs <= shape_to_set_on_dest->capacity);
+ if (initial_shape->capacity < shape_to_set_on_dest->capacity) {
+ rb_ensure_iv_list_size(dest, initial_shape->capacity, shape_to_set_on_dest->capacity);
+ dest_buf = ROBJECT_IVPTR(dest);
+ }
+ }
+
+ MEMCPY(dest_buf, src_buf, VALUE, src_num_ivs);
+
+ // Fire write barriers
+ for (uint32_t i = 0; i < src_num_ivs; i++) {
+ RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
+ }
+
+ rb_shape_set_shape(dest, shape_to_set_on_dest);
}
static void
@@ -311,19 +354,6 @@ init_copy(VALUE dest, VALUE obj)
if (RB_TYPE_P(obj, T_OBJECT)) {
rb_obj_copy_ivar(dest, obj);
}
-
- if (!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE)) {
- rb_shape_t *shape_to_set = rb_shape_get_shape(obj);
-
- // If the object is frozen, the "dup"'d object will *not* be frozen,
- // so we need to copy the frozen shape's parent to the new object.
- if (rb_shape_frozen_shape_p(shape_to_set)) {
- shape_to_set = rb_shape_get_shape_by_id(shape_to_set->parent_id);
- }
-
- // shape ids are different
- rb_shape_set_shape(dest, shape_to_set);
- }
}
static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze);
diff --git a/pack.c b/pack.c
index 2817491b77453d..294d7dfa350ff7 100644
--- a/pack.c
+++ b/pack.c
@@ -217,6 +217,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
else {
if (!RB_TYPE_P(buffer, T_STRING))
rb_raise(rb_eTypeError, "buffer must be String, not %s", rb_obj_classname(buffer));
+ rb_str_modify(buffer);
res = buffer;
}
diff --git a/parse.y b/parse.y
index f4b4b8f3d15055..526a25ac3b861d 100644
--- a/parse.y
+++ b/parse.y
@@ -9,6 +9,8 @@
**********************************************************************/
+%require "3.0"
+
%{
#if !YYPURE
@@ -1140,6 +1142,14 @@ static int looking_at_eol_p(struct parser_params *p);
%define api.pure
%define parse.error verbose
%printer {
+#ifndef RIPPER
+ if ($$) {
+ rb_parser_printf(p, "%s", ruby_node_name(nd_type($$)));
+ }
+#else
+#endif
+}
+%printer {
#ifndef RIPPER
rb_parser_printf(p, "%"PRIsVALUE, rb_id2str($$));
#else
diff --git a/random.c b/random.c
index f8879cfb88a836..759e15335ae9b6 100644
--- a/random.c
+++ b/random.c
@@ -371,11 +371,14 @@ rand_init(const rb_random_interface_t *rng, rb_random_t *rnd, VALUE seed)
INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER);
if (sign < 0)
sign = -sign;
- if (len > 1) {
+ if (len <= 1) {
+ rng->init_int32(rnd, len ? buf[0] : 0);
+ }
+ else {
if (sign != 2 && buf[len-1] == 1) /* remove leading-zero-guard */
len--;
+ rng->init(rnd, buf, len);
}
- rng->init(rnd, buf, len);
explicit_bzero(buf, len * sizeof(*buf));
ALLOCV_END(buf0);
return seed;
@@ -400,6 +403,15 @@ random_init(int argc, VALUE *argv, VALUE obj)
rb_raise(rb_eTypeError, "undefined random interface: %s",
RTYPEDDATA_TYPE(obj)->wrap_struct_name);
}
+
+ unsigned int major = rng->version.major;
+ unsigned int minor = rng->version.minor;
+ if (major != RUBY_RANDOM_INTERFACE_VERSION_MAJOR) {
+ rb_raise(rb_eTypeError, "Random interface version "
+ STRINGIZE(RUBY_RANDOM_INTERFACE_VERSION_MAJOR) "."
+ STRINGIZE(RUBY_RANDOM_INTERFACE_VERSION_MINOR) " "
+ "expected: %d.%d", major, minor);
+ }
argc = rb_check_arity(argc, 0, 1);
rb_check_frozen(obj);
if (argc == 0) {
@@ -882,16 +894,18 @@ rand_mt_load(VALUE obj, VALUE dump)
return obj;
}
+static void
+rand_mt_init_int32(rb_random_t *rnd, uint32_t data)
+{
+ struct MT *mt = &((rb_random_mt_t *)rnd)->mt;
+ init_genrand(mt, data);
+}
+
static void
rand_mt_init(rb_random_t *rnd, const uint32_t *buf, size_t len)
{
struct MT *mt = &((rb_random_mt_t *)rnd)->mt;
- if (len <= 1) {
- init_genrand(mt, len ? buf[0] : 0);
- }
- else {
- init_by_array(mt, buf, (int)len);
- }
+ init_by_array(mt, buf, (int)len);
}
static unsigned int
diff --git a/re.c b/re.c
index c65e4a58eb1ef7..1b8314832156a5 100644
--- a/re.c
+++ b/re.c
@@ -1066,12 +1066,13 @@ update_char_offset(VALUE match)
}
}
-static void
+static VALUE
match_check(VALUE match)
{
if (!RMATCH(match)->regexp) {
rb_raise(rb_eTypeError, "uninitialized MatchData");
}
+ return match;
}
/* :nodoc: */
@@ -2268,16 +2269,16 @@ match_values_at(int argc, VALUE *argv, VALUE match)
static VALUE
match_to_s(VALUE match)
{
- VALUE str = rb_reg_last_match(match);
+ VALUE str = rb_reg_last_match(match_check(match));
- match_check(match);
if (NIL_P(str)) str = rb_str_new(0,0);
return str;
}
static int
match_named_captures_iter(const OnigUChar *name, const OnigUChar *name_end,
- int back_num, int *back_refs, OnigRegex regex, void *arg) {
+ int back_num, int *back_refs, OnigRegex regex, void *arg)
+{
struct MEMO *memo = MEMO_CAST(arg);
VALUE hash = memo->v1;
VALUE match = memo->v2;
diff --git a/regexec.c b/regexec.c
index c77d48b1d9e921..75ffb86c4510dc 100644
--- a/regexec.c
+++ b/regexec.c
@@ -231,6 +231,448 @@ onig_get_capture_tree(OnigRegion* region)
}
#endif /* USE_CAPTURE_HISTORY */
+#ifdef USE_CACHE_MATCH_OPT
+
+/* count number of jump-like opcodes for allocation of cache memory. */
+/* return -1 if we cannot optimize the regex matching by using cache. */
+static long count_num_cache_opcode(regex_t* reg, long* table_size)
+{
+ long num = 0;
+ UChar* p = reg->p;
+ UChar* pend = p + reg->used;
+ LengthType len;
+ MemNumType mem;
+ MemNumType current_mem = -1;
+ long current_mem_num = 0;
+ OnigEncoding enc = reg->enc;
+
+ while (p < pend) {
+ switch (*p++) {
+ case OP_FINISH:
+ case OP_END:
+ break;
+
+ case OP_EXACT1: p++; break;
+ case OP_EXACT2: p += 2; break;
+ case OP_EXACT3: p += 3; break;
+ case OP_EXACT4: p += 4; break;
+ case OP_EXACT5: p += 5; break;
+ case OP_EXACTN:
+ GET_LENGTH_INC(len, p); p += len; break;
+ case OP_EXACTMB2N1: p += 2; break;
+ case OP_EXACTMB2N2: p += 4; break;
+ case OP_EXACTMB2N3: p += 6; break;
+ case OP_EXACTMB2N:
+ GET_LENGTH_INC(len, p); p += len * 2; break;
+ case OP_EXACTMB3N:
+ GET_LENGTH_INC(len, p); p += len * 3; break;
+ case OP_EXACTMBN:
+ {
+ int mb_len;
+ GET_LENGTH_INC(mb_len, p);
+ GET_LENGTH_INC(len, p);
+ p += mb_len * len;
+ }
+ break;
+
+ case OP_EXACT1_IC:
+ len = enclen(enc, p, pend); p += len; break;
+ case OP_EXACTN_IC:
+ GET_LENGTH_INC(len, p); p += len; break;
+
+ case OP_CCLASS:
+ case OP_CCLASS_NOT:
+ p += SIZE_BITSET; break;
+ case OP_CCLASS_MB:
+ case OP_CCLASS_MB_NOT:
+ GET_LENGTH_INC(len, p); p += len; break;
+ case OP_CCLASS_MIX:
+ case OP_CCLASS_MIX_NOT:
+ p += SIZE_BITSET;
+ GET_LENGTH_INC(len, p);
+ p += len;
+ break;
+
+ case OP_ANYCHAR:
+ case OP_ANYCHAR_ML:
+ break;
+ case OP_ANYCHAR_STAR:
+ case OP_ANYCHAR_ML_STAR:
+ num++; *table_size += 1; break;
+ case OP_ANYCHAR_STAR_PEEK_NEXT:
+ case OP_ANYCHAR_ML_STAR_PEEK_NEXT:
+ p++; num++; *table_size += 1; break;
+
+ case OP_WORD:
+ case OP_NOT_WORD:
+ case OP_WORD_BOUND:
+ case OP_NOT_WORD_BOUND:
+ case OP_WORD_BEGIN:
+ case OP_WORD_END:
+ break;
+
+ case OP_ASCII_WORD:
+ case OP_NOT_ASCII_WORD:
+ case OP_ASCII_WORD_BOUND:
+ case OP_NOT_ASCII_WORD_BOUND:
+ case OP_ASCII_WORD_BEGIN:
+ case OP_ASCII_WORD_END:
+ break;
+
+ case OP_BEGIN_BUF:
+ case OP_END_BUF:
+ case OP_BEGIN_LINE:
+ case OP_END_LINE:
+ case OP_SEMI_END_BUF:
+ case OP_BEGIN_POSITION:
+ break;
+
+ case OP_BACKREF1:
+ case OP_BACKREF2:
+ case OP_BACKREFN:
+ case OP_BACKREFN_IC:
+ case OP_BACKREF_MULTI:
+ case OP_BACKREF_MULTI_IC:
+ case OP_BACKREF_WITH_LEVEL:
+ return NUM_CACHE_OPCODE_FAIL;
+
+ case OP_MEMORY_START:
+ case OP_MEMORY_START_PUSH:
+ case OP_MEMORY_END_PUSH:
+ case OP_MEMORY_END_PUSH_REC:
+ case OP_MEMORY_END:
+ case OP_MEMORY_END_REC:
+ p += SIZE_MEMNUM; break;
+
+ case OP_KEEP:
+ break;
+
+ case OP_FAIL:
+ break;
+ case OP_JUMP:
+ p += SIZE_RELADDR;
+ break;
+ case OP_PUSH:
+ p += SIZE_RELADDR;
+ num++;
+ *table_size += 1;
+ break;
+ case OP_POP:
+ break;
+ case OP_PUSH_OR_JUMP_EXACT1:
+ case OP_PUSH_IF_PEEK_NEXT:
+ p += SIZE_RELADDR + 1; num++; *table_size += 1; break;
+ case OP_REPEAT:
+ case OP_REPEAT_NG:
+ if (current_mem != -1) {
+ // A nested OP_REPEAT is not yet supported.
+ return NUM_CACHE_OPCODE_FAIL;
+ }
+ GET_MEMNUM_INC(mem, p);
+ p += SIZE_RELADDR;
+ if (reg->repeat_range[mem].lower == 0) {
+ num++;
+ *table_size += 1;
+ }
+ reg->repeat_range[mem].base_num = num;
+ current_mem = mem;
+ current_mem_num = num;
+ break;
+ case OP_REPEAT_INC:
+ case OP_REPEAT_INC_NG:
+ GET_MEMNUM_INC(mem, p);
+ if (mem != current_mem) {
+ // A lone or invalid OP_REPEAT_INC is found.
+ return NUM_CACHE_OPCODE_FAIL;
+ }
+ {
+ long inner_num = num - current_mem_num;
+ OnigRepeatRange *repeat_range = ®->repeat_range[mem];
+ repeat_range->inner_num = inner_num;
+ num -= inner_num;
+ num += inner_num * repeat_range->lower + (inner_num + 1) * (repeat_range->upper == 0x7fffffff ? 1 : repeat_range->upper - repeat_range->lower);
+ if (repeat_range->lower < repeat_range->upper) {
+ *table_size += 1;
+ }
+ current_mem = -1;
+ current_mem_num = 0;
+ }
+ break;
+ case OP_REPEAT_INC_SG:
+ case OP_REPEAT_INC_NG_SG:
+ // TODO: Support nested OP_REPEAT.
+ return NUM_CACHE_OPCODE_FAIL;
+ case OP_NULL_CHECK_START:
+ case OP_NULL_CHECK_END:
+ case OP_NULL_CHECK_END_MEMST:
+ case OP_NULL_CHECK_END_MEMST_PUSH:
+ p += SIZE_MEMNUM; break;
+
+ case OP_PUSH_POS:
+ case OP_POP_POS:
+ case OP_PUSH_POS_NOT:
+ case OP_FAIL_POS:
+ case OP_PUSH_STOP_BT:
+ case OP_POP_STOP_BT:
+ case OP_LOOK_BEHIND:
+ case OP_PUSH_LOOK_BEHIND_NOT:
+ case OP_FAIL_LOOK_BEHIND_NOT:
+ case OP_PUSH_ABSENT_POS:
+ case OP_ABSENT_END:
+ case OP_ABSENT:
+ return NUM_CACHE_OPCODE_FAIL;
+
+ case OP_CALL:
+ case OP_RETURN:
+ return NUM_CACHE_OPCODE_FAIL;
+
+ case OP_CONDITION:
+ return NUM_CACHE_OPCODE_FAIL;
+
+ case OP_STATE_CHECK_PUSH:
+ case OP_STATE_CHECK_PUSH_OR_JUMP:
+ case OP_STATE_CHECK:
+ case OP_STATE_CHECK_ANYCHAR_STAR:
+ case OP_STATE_CHECK_ANYCHAR_ML_STAR:
+ return NUM_CACHE_OPCODE_FAIL;
+
+ case OP_SET_OPTION_PUSH:
+ case OP_SET_OPTION:
+ p += SIZE_OPTION;
+ break;
+ }
+ }
+
+ return num;
+}
+
+static void init_cache_index_table(regex_t* reg, OnigCacheIndex *table)
+{
+ UChar* pbegin;
+ UChar* p = reg->p;
+ UChar* pend = p + reg->used;
+ LengthType len;
+ MemNumType mem;
+ MemNumType current_mem = -1;
+ long num = 0;
+ long current_mem_num = 0;
+ OnigEncoding enc = reg->enc;
+
+ while (p < pend) {
+ pbegin = p;
+ switch (*p++) {
+ case OP_FINISH:
+ case OP_END:
+ break;
+
+ case OP_EXACT1: p++; break;
+ case OP_EXACT2: p += 2; break;
+ case OP_EXACT3: p += 3; break;
+ case OP_EXACT4: p += 4; break;
+ case OP_EXACT5: p += 5; break;
+ case OP_EXACTN:
+ GET_LENGTH_INC(len, p); p += len; break;
+ case OP_EXACTMB2N1: p += 2; break;
+ case OP_EXACTMB2N2: p += 4; break;
+ case OP_EXACTMB2N3: p += 6; break;
+ case OP_EXACTMB2N:
+ GET_LENGTH_INC(len, p); p += len * 2; break;
+ case OP_EXACTMB3N:
+ GET_LENGTH_INC(len, p); p += len * 3; break;
+ case OP_EXACTMBN:
+ {
+ int mb_len;
+ GET_LENGTH_INC(mb_len, p);
+ GET_LENGTH_INC(len, p);
+ p += mb_len * len;
+ }
+ break;
+
+ case OP_EXACT1_IC:
+ len = enclen(enc, p, pend); p += len; break;
+ case OP_EXACTN_IC:
+ GET_LENGTH_INC(len, p); p += len; break;
+
+ case OP_CCLASS:
+ case OP_CCLASS_NOT:
+ p += SIZE_BITSET; break;
+ case OP_CCLASS_MB_NOT:
+ GET_LENGTH_INC(len, p); p += len; break;
+ case OP_CCLASS_MIX:
+ case OP_CCLASS_MIX_NOT:
+ p += SIZE_BITSET;
+ GET_LENGTH_INC(len, p);
+ p += len;
+ break;
+
+ case OP_ANYCHAR:
+ case OP_ANYCHAR_ML:
+ break;
+ case OP_ANYCHAR_STAR:
+ case OP_ANYCHAR_ML_STAR:
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = current_mem;
+ num++;
+ table++;
+ break;
+ case OP_ANYCHAR_STAR_PEEK_NEXT:
+ case OP_ANYCHAR_ML_STAR_PEEK_NEXT:
+ p++;
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = current_mem;
+ num++;
+ table++;
+ break;
+
+ case OP_WORD:
+ case OP_NOT_WORD:
+ case OP_WORD_BOUND:
+ case OP_NOT_WORD_BOUND:
+ case OP_WORD_BEGIN:
+ case OP_WORD_END:
+ break;
+
+ case OP_ASCII_WORD:
+ case OP_NOT_ASCII_WORD:
+ case OP_ASCII_WORD_BOUND:
+ case OP_NOT_ASCII_WORD_BOUND:
+ case OP_ASCII_WORD_BEGIN:
+ case OP_ASCII_WORD_END:
+ break;
+
+ case OP_BEGIN_BUF:
+ case OP_END_BUF:
+ case OP_BEGIN_LINE:
+ case OP_END_LINE:
+ case OP_SEMI_END_BUF:
+ case OP_BEGIN_POSITION:
+ break;
+
+ case OP_BACKREF1:
+ case OP_BACKREF2:
+ case OP_BACKREFN:
+ case OP_BACKREFN_IC:
+ case OP_BACKREF_MULTI:
+ case OP_BACKREF_MULTI_IC:
+ case OP_BACKREF_WITH_LEVEL:
+ return;
+
+ case OP_MEMORY_START:
+ case OP_MEMORY_START_PUSH:
+ case OP_MEMORY_END_PUSH:
+ case OP_MEMORY_END_PUSH_REC:
+ case OP_MEMORY_END:
+ case OP_MEMORY_END_REC:
+ p += SIZE_MEMNUM; break;
+
+ case OP_KEEP:
+ break;
+
+ case OP_FAIL:
+ break;
+ case OP_JUMP:
+ p += SIZE_RELADDR;
+ break;
+ case OP_PUSH:
+ p += SIZE_RELADDR;
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = current_mem;
+ num++;
+ table++;
+ break;
+ case OP_POP:
+ break;
+ case OP_PUSH_OR_JUMP_EXACT1:
+ case OP_PUSH_IF_PEEK_NEXT:
+ p += SIZE_RELADDR + 1;
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = current_mem;
+ num++;
+ table++;
+ break;
+ case OP_REPEAT:
+ case OP_REPEAT_NG:
+ GET_MEMNUM_INC(mem, p);
+ p += SIZE_RELADDR;
+ if (reg->repeat_range[mem].lower == 0) {
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = mem;
+ num++;
+ table++;
+ }
+ current_mem = mem;
+ current_mem_num = num;
+ break;
+ case OP_REPEAT_INC:
+ case OP_REPEAT_INC_NG:
+ GET_MEMNUM_INC(mem, p);
+ {
+ long inner_num = num - current_mem_num;
+ OnigRepeatRange *repeat_range = ®->repeat_range[mem];
+ if (repeat_range->lower < repeat_range->upper) {
+ table->addr = pbegin;
+ table->num = num - current_mem_num;
+ table->outer_repeat = mem;
+ table++;
+ }
+ num -= inner_num;
+ num += inner_num * repeat_range->lower + (inner_num + 1) * (repeat_range->upper == 0x7fffffff ? 1 : repeat_range->upper - repeat_range->lower);
+ current_mem = -1;
+ current_mem_num = 0;
+ }
+ break;
+ case OP_REPEAT_INC_SG:
+ case OP_REPEAT_INC_NG_SG:
+ // TODO: support OP_REPEAT opcodes.
+ return;
+ case OP_NULL_CHECK_START:
+ case OP_NULL_CHECK_END:
+ case OP_NULL_CHECK_END_MEMST:
+ case OP_NULL_CHECK_END_MEMST_PUSH:
+ p += SIZE_MEMNUM; break;
+
+ case OP_PUSH_POS:
+ case OP_POP_POS:
+ case OP_PUSH_POS_NOT:
+ case OP_FAIL_POS:
+ case OP_PUSH_STOP_BT:
+ case OP_POP_STOP_BT:
+ case OP_LOOK_BEHIND:
+ case OP_PUSH_LOOK_BEHIND_NOT:
+ case OP_FAIL_LOOK_BEHIND_NOT:
+ case OP_PUSH_ABSENT_POS:
+ case OP_ABSENT_END:
+ case OP_ABSENT:
+ return;
+
+ case OP_CALL:
+ case OP_RETURN:
+ return;
+
+ case OP_CONDITION:
+ return;
+
+ case OP_STATE_CHECK_PUSH:
+ case OP_STATE_CHECK_PUSH_OR_JUMP:
+ case OP_STATE_CHECK:
+ case OP_STATE_CHECK_ANYCHAR_STAR:
+ case OP_STATE_CHECK_ANYCHAR_ML_STAR:
+ return;
+
+ case OP_SET_OPTION_PUSH:
+ case OP_SET_OPTION:
+ p += SIZE_OPTION;
+ break;
+ }
+ }
+}
+#endif /* USE_MATCH_CACHE */
+
extern void
onig_region_clear(OnigRegion* region)
{
@@ -413,6 +855,23 @@ onig_region_copy(OnigRegion* to, const OnigRegion* from)
#define STK_MASK_TO_VOID_TARGET 0x10ff
#define STK_MASK_MEM_END_OR_MARK 0x8000 /* MEM_END or MEM_END_MARK */
+#ifdef USE_CACHE_MATCH_OPT
+#define MATCH_ARG_INIT_CACHE_MATCH_OPT(msa) do {\
+ (msa).enable_cache_match_opt = 0;\
+ (msa).num_fail = 0;\
+ (msa).num_cache_opcode = NUM_CACHE_OPCODE_UNINIT;\
+ (msa).cache_index_table = (OnigCacheIndex *)0;\
+ (msa).match_cache = (uint8_t *)0;\
+} while(0)
+#define MATCH_ARG_FREE_CACHE_MATCH_OPT(msa) do {\
+ if ((msa).cache_index_table) xfree((msa).cache_index_table);\
+ if ((msa).match_cache) xfree((msa).match_cache);\
+} while(0)
+#else
+#define MATCH_ARG_INIT_CACHE_MATCH_OPT(msa)
+#define MATCH_ARG_FREE_CACHE_MATCH_OPT(msa)
+#endif
+
#ifdef USE_FIND_LONGEST_SEARCH_ALL_OF_RANGE
# define MATCH_ARG_INIT(msa, arg_option, arg_region, arg_start, arg_gpos) do {\
(msa).stack_p = (void* )0;\
@@ -423,6 +882,7 @@ onig_region_copy(OnigRegion* to, const OnigRegion* from)
(msa).best_len = ONIG_MISMATCH;\
(msa).counter = 0;\
(msa).end_time = 0;\
+ MATCH_ARG_INIT_CACHE_MATCH_OPT(msa);\
} while(0)
#else
# define MATCH_ARG_INIT(msa, arg_option, arg_region, arg_start, arg_gpos) do {\
@@ -433,6 +893,7 @@ onig_region_copy(OnigRegion* to, const OnigRegion* from)
(msa).gpos = (arg_gpos);\
(msa).counter = 0;\
(msa).end_time = 0;\
+ MATCH_ARG_INIT_CACHE_MATCH_OPT(msa);\
} while(0)
#endif
@@ -471,9 +932,13 @@ onig_region_copy(OnigRegion* to, const OnigRegion* from)
if ((msa).state_check_buff_size >= STATE_CHECK_BUFF_MALLOC_THRESHOLD_SIZE) { \
if ((msa).state_check_buff) xfree((msa).state_check_buff);\
}\
+ MATCH_ARG_FREE_CACHE_MATCH_OPT(msa);\
} while(0)
#else /* USE_COMBINATION_EXPLOSION_CHECK */
-# define MATCH_ARG_FREE(msa) if ((msa).stack_p) xfree((msa).stack_p)
+# define MATCH_ARG_FREE(msa) do {\
+ if ((msa).stack_p) xfree((msa).stack_p);\
+ MATCH_ARG_FREE_CACHE_MATCH_OPT(msa);\
+} while (0)
#endif /* USE_COMBINATION_EXPLOSION_CHECK */
@@ -595,6 +1060,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_TYPE(stack_type) do {\
STACK_ENSURE(1);\
stk->type = (stack_type);\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
STACK_INC;\
} while(0)
@@ -664,6 +1130,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
# define STACK_PUSH(stack_type,pat,s,sprev,keep) do {\
STACK_ENSURE(1);\
stk->type = (stack_type);\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.state.pcode = (pat);\
stk->u.state.pstr = (s);\
stk->u.state.pstr_prev = (sprev);\
@@ -673,6 +1140,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
# define STACK_PUSH_ENSURED(stack_type,pat) do {\
stk->type = (stack_type);\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.state.pcode = (pat);\
STACK_INC;\
} while(0)
@@ -686,9 +1154,130 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_LOOK_BEHIND_NOT(pat,s,sprev,keep) \
STACK_PUSH(STK_LOOK_BEHIND_NOT,pat,s,sprev,keep)
+#ifdef USE_CACHE_MATCH_OPT
+
+#define DO_CACHE_MATCH_OPT(reg,stk,repeat_stk,enable,p,num_cache_table,num_cache_size,table,pos,match_cache) do {\
+ if (enable) {\
+ long cache_index = find_cache_index_table((reg), (stk), (repeat_stk), (table), (num_cache_table), (p));\
+ if (cache_index >= 0) {\
+ long key = (num_cache_size) * (long)(pos) + cache_index;\
+ long index = key >> 3;\
+ long mask = 1 << (key & 7);\
+ if ((match_cache)[index] & mask) {\
+ goto fail;\
+ }\
+ (match_cache)[index] |= mask;\
+ }\
+ }\
+} while (0)
+
+static long find_cache_index_table(regex_t* reg, OnigStackType *stk, OnigStackIndex *repeat_stk, OnigCacheIndex* table, long num_cache_table, UChar* p)
+{
+ long l = 0, r = num_cache_table - 1, m = 0;
+ OnigCacheIndex* item;
+ OnigRepeatRange* range;
+ OnigStackType *stkp;
+ int count = 0;
+ int is_inc = *p == OP_REPEAT_INC || *p == OP_REPEAT_INC_NG;
+
+ while (l <= r) {
+ m = (l + r) / 2;
+ if (table[m].addr == p) break;
+ if (table[m].addr < p) l = m + 1;
+ else r = m - 1;
+ }
+
+ if (!(0 <= m && m < num_cache_table && table[m].addr == p)) {
+ return -1;
+ }
+
+ item = &table[m];
+ if (item->outer_repeat == -1) {
+ return item->num;
+ }
+
+ range = ®->repeat_range[item->outer_repeat];
+
+ stkp = &stk[repeat_stk[item->outer_repeat]];
+ count = is_inc ? stkp->u.repeat.count - 1 : stkp->u.repeat.count;
+
+ if (count < range->lower) {
+ return range->base_num + range->inner_num * count + item->num;
+ }
+
+ if (range->upper == 0x7fffffff) {
+ return range->base_num + range->inner_num * range->lower + (is_inc ? 0 : 1) + item->num;
+ }
+
+ return range->base_num + range->inner_num * range->lower + (range->inner_num + 1) * (count - range->lower) + item->num;
+}
+
+static void reset_match_cache(regex_t* reg, UChar* pbegin, UChar* pend, long pos, uint8_t* match_cache, OnigCacheIndex *table, long num_cache_size, long num_cache_table) {
+ long l = 0, r = num_cache_table - 1, m1 = 0, m2 = 0;
+ int is_inc = *pend == OP_REPEAT_INC || *pend == OP_REPEAT_INC_NG;
+ OnigCacheIndex *item1, *item2;
+ long k1, k2, base;
+
+ while (l <= r) {
+ m1 = (l + r) / 2;
+ if (table[m1].addr == pbegin) break;
+ if (table[m1].addr < pbegin) l = m1 + 1;
+ else r = m1 - 1;
+ }
+
+ l = 0, r = num_cache_table - 1;
+ while (l <= r) {
+ m2 = (l + r) / 2;
+ if (table[m2].addr == pend) break;
+ if (table[m2].addr < pend) l = m2 + 1;
+ else r = m2 - 1;
+ }
+
+ if (table[m1].addr < pbegin && m1 + 1 < num_cache_table) m1++;
+ if (table[m2].addr > pend && m2 - 1 > 0) m2--;
+
+ item1 = &table[m1];
+ item2 = &table[m2];
+
+ if (item1->outer_repeat < 0) k1 = item1->num;
+ else k1 = reg->repeat_range[item1->outer_repeat].base_num + item1->num;
+
+ if (item2->outer_repeat < 0) k2 = item2->num;
+ else {
+ OnigRepeatRange *range = ®->repeat_range[item2->outer_repeat];
+ if (range->upper == 0x7fffffff) k2 = range->base_num + range->inner_num * range->lower + (is_inc ? 0 : 1) + item2->num;
+ else k2 = range->base_num + range->inner_num * range->lower + (range->inner_num + 1) * (range->upper - range->lower - (is_inc ? 1 : 0)) + item2->num;
+ }
+
+ base = pos * num_cache_size;
+ k1 += base;
+ k2 += base;
+
+ if ((k1 >> 3) == (k2 >> 3)) {
+ match_cache[k1 >> 3] &= (((1 << (8 - (k2 & 7) - 1)) - 1) << ((k2 & 7) + 1)) | ((1 << (k1 & 7)) - 1);
+ } else {
+ long i = k1 >> 3;
+ if (k1 & 7) {
+ match_cache[k1 >> 3] &= (1 << ((k1 & 7) - 1)) - 1;
+ i++;
+ }
+ if (i < (k2 >> 3)) {
+ xmemset(&match_cache[i], 0, (k2 >> 3) - i);
+ if (k2 & 7) {
+ match_cache[k2 >> 3] &= (((1 << (8 - (k2 & 7) - 1)) - 1) << ((k2 & 7) + 1));
+ }
+ }
+ }
+}
+
+#else
+#define DO_CACHE_MATCH_OPT(reg,stk,repeat_stk,enable,p,num_cache_table,num_cache_size,table,pos,match_cache)
+#endif /* USE_CACHE_MATCH_OPT */
+
#define STACK_PUSH_REPEAT(id, pat) do {\
STACK_ENSURE(1);\
stk->type = STK_REPEAT;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.repeat.num = (id);\
stk->u.repeat.pcode = (pat);\
stk->u.repeat.count = 0;\
@@ -698,6 +1287,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_REPEAT_INC(sindex) do {\
STACK_ENSURE(1);\
stk->type = STK_REPEAT_INC;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.repeat_inc.si = (sindex);\
STACK_INC;\
} while(0)
@@ -705,6 +1295,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_MEM_START(mnum, s) do {\
STACK_ENSURE(1);\
stk->type = STK_MEM_START;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.mem.num = (mnum);\
stk->u.mem.pstr = (s);\
stk->u.mem.start = mem_start_stk[mnum];\
@@ -717,6 +1308,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_MEM_END(mnum, s) do {\
STACK_ENSURE(1);\
stk->type = STK_MEM_END;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.mem.num = (mnum);\
stk->u.mem.pstr = (s);\
stk->u.mem.start = mem_start_stk[mnum];\
@@ -728,6 +1320,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_MEM_END_MARK(mnum) do {\
STACK_ENSURE(1);\
stk->type = STK_MEM_END_MARK;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.mem.num = (mnum);\
STACK_INC;\
} while(0)
@@ -769,6 +1362,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_NULL_CHECK_START(cnum, s) do {\
STACK_ENSURE(1);\
stk->type = STK_NULL_CHECK_START;\
+ stk->null_check = (OnigStackIndex)(stk - stk_base);\
stk->u.null_check.num = (cnum);\
stk->u.null_check.pstr = (s);\
STACK_INC;\
@@ -777,6 +1371,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_NULL_CHECK_END(cnum) do {\
STACK_ENSURE(1);\
stk->type = STK_NULL_CHECK_END;\
+ stk->null_check = (OnigStackIndex)(stk - stk_base);\
stk->u.null_check.num = (cnum);\
STACK_INC;\
} while(0)
@@ -784,6 +1379,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_CALL_FRAME(pat) do {\
STACK_ENSURE(1);\
stk->type = STK_CALL_FRAME;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.call_frame.ret_addr = (pat);\
STACK_INC;\
} while(0)
@@ -791,12 +1387,14 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_PUSH_RETURN do {\
STACK_ENSURE(1);\
stk->type = STK_RETURN;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
STACK_INC;\
} while(0)
#define STACK_PUSH_ABSENT_POS(start, end) do {\
STACK_ENSURE(1);\
stk->type = STK_ABSENT_POS;\
+ stk->null_check = stk == stk_base ? 0 : (stk-1)->null_check;\
stk->u.absent_pos.abs_pstr = (start);\
stk->u.absent_pos.end_pstr = (end);\
STACK_INC;\
@@ -960,7 +1558,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
} while(0)
#define STACK_NULL_CHECK(isnull,id,s) do {\
- OnigStackType* k = stk;\
+ OnigStackType* k = STACK_AT((stk-1)->null_check)+1;\
while (1) {\
k--;\
STACK_BASE_CHECK(k, "STACK_NULL_CHECK"); \
@@ -975,7 +1573,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_NULL_CHECK_REC(isnull,id,s) do {\
int level = 0;\
- OnigStackType* k = stk;\
+ OnigStackType* k = STACK_AT((stk-1)->null_check)+1;\
while (1) {\
k--;\
STACK_BASE_CHECK(k, "STACK_NULL_CHECK_REC"); \
@@ -994,8 +1592,8 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
}\
} while(0)
-#define STACK_NULL_CHECK_MEMST(isnull,id,s,reg) do {\
- OnigStackType* k = stk;\
+#define STACK_NULL_CHECK_MEMST(isnull,ischange,id,s,reg) do {\
+ OnigStackType* k = STACK_AT((stk-1)->null_check)+1;\
while (1) {\
k--;\
STACK_BASE_CHECK(k, "STACK_NULL_CHECK_MEMST"); \
@@ -1011,14 +1609,14 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
while (k < stk) {\
if (k->type == STK_MEM_START) {\
if (k->u.mem.end == INVALID_STACK_INDEX) {\
- (isnull) = 0; break;\
+ (isnull) = 0; (ischange) = 1; break;\
}\
if (BIT_STATUS_AT(reg->bt_mem_end, k->u.mem.num))\
endp = STACK_AT(k->u.mem.end)->u.mem.pstr;\
else\
endp = (UChar* )k->u.mem.end;\
if (STACK_AT(k->u.mem.start)->u.mem.pstr != endp) {\
- (isnull) = 0; break;\
+ (isnull) = 0; (ischange) = 1; break;\
}\
else if (endp != s) {\
(isnull) = -1; /* empty, but position changed */ \
@@ -1035,7 +1633,7 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end,
#define STACK_NULL_CHECK_MEMST_REC(isnull,id,s,reg) do {\
int level = 0;\
- OnigStackType* k = stk;\
+ OnigStackType* k = STACK_AT((stk-1)->null_check)+1;\
while (1) {\
k--;\
STACK_BASE_CHECK(k, "STACK_NULL_CHECK_MEMST_REC"); \
@@ -1448,6 +2046,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
OnigCaseFoldType case_fold_flag = reg->case_fold_flag;
UChar *s, *q, *sbegin;
UChar *p = reg->p;
+ UChar *pbegin = p;
UChar *pkeep;
char *alloca_base;
char *xmalloc_base = NULL;
@@ -1469,7 +2068,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
# define CASE(x) L_##x: sbegin = s; OPCODE_EXEC_HOOK;
# define DEFAULT L_DEFAULT:
# define NEXT sprev = sbegin; JUMP
-# define JUMP RB_GNUC_EXTENSION_BLOCK(goto *oplabels[*p++])
+# define JUMP pbegin = p; RB_GNUC_EXTENSION_BLOCK(goto *oplabels[*p++])
RB_GNUC_EXTENSION static const void *oplabels[] = {
&&L_OP_FINISH, /* matching process terminator (no more alternative) */
@@ -1645,6 +2244,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
# define VM_LOOP \
while (1) { \
OPCODE_EXEC_HOOK; \
+ pbegin = p; \
sbegin = s; \
switch (*p++) {
# define VM_LOOP_END } sprev = sbegin; }
@@ -2159,6 +2759,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_ANYCHAR_STAR) MOP_IN(OP_ANYCHAR_STAR);
while (DATA_ENSURE_CHECK1) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p, s, sprev, pkeep);
n = enclen(encode, s, end);
DATA_ENSURE(n);
@@ -2171,6 +2772,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_ANYCHAR_ML_STAR) MOP_IN(OP_ANYCHAR_ML_STAR);
while (DATA_ENSURE_CHECK1) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p, s, sprev, pkeep);
n = enclen(encode, s, end);
if (n > 1) {
@@ -2189,6 +2791,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_ANYCHAR_STAR_PEEK_NEXT) MOP_IN(OP_ANYCHAR_STAR_PEEK_NEXT);
while (DATA_ENSURE_CHECK1) {
if (*p == *s) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, end - s, msa->match_cache);
STACK_PUSH_ALT(p + 1, s, sprev, pkeep);
}
n = enclen(encode, s, end);
@@ -2204,6 +2807,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_ANYCHAR_ML_STAR_PEEK_NEXT)MOP_IN(OP_ANYCHAR_ML_STAR_PEEK_NEXT);
while (DATA_ENSURE_CHECK1) {
if (*p == *s) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p + 1, s, sprev, pkeep);
}
n = enclen(encode, s, end);
@@ -2790,9 +3394,10 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_NULL_CHECK_END_MEMST) MOP_IN(OP_NULL_CHECK_END_MEMST);
{
int isnull;
+ int ischanged = 0; // set 1 when a loop is empty but memory status is changed.
GET_MEMNUM_INC(mem, p); /* mem: null check id */
- STACK_NULL_CHECK_MEMST(isnull, mem, s, reg);
+ STACK_NULL_CHECK_MEMST(isnull, ischanged, mem, s, reg);
if (isnull) {
# ifdef ONIG_DEBUG_MATCH
fprintf(stderr, "NULL_CHECK_END_MEMST: skip id:%d, s:%"PRIuPTR" (%p)\n",
@@ -2801,6 +3406,29 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
if (isnull == -1) goto fail;
goto null_check_found;
}
+# ifdef USE_CACHE_MATCH_OPT
+ if (ischanged && msa->enable_cache_match_opt) {
+ RelAddrType rel;
+ OnigUChar *addr;
+ int mem;
+ UChar* tmp = p;
+ switch (*tmp++) {
+ case OP_JUMP:
+ case OP_PUSH:
+ GET_RELADDR_INC(rel, tmp);
+ addr = tmp + rel;
+ break;
+ case OP_REPEAT_INC:
+ case OP_REPEAT_INC_NG:
+ GET_MEMNUM_INC(mem, tmp);
+ addr = STACK_AT(repeat_stk[mem])->u.repeat.pcode;
+ break;
+ default:
+ goto unexpected_bytecode_error;
+ }
+ reset_match_cache(reg, addr, pbegin, (long)(s - str), msa->match_cache, msa->cache_index_table, msa->num_cache_table ,msa->num_cache_opcode);
+ }
+# endif
}
MOP_OUT;
JUMP;
@@ -2843,6 +3471,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_PUSH) MOP_IN(OP_PUSH);
GET_RELADDR_INC(addr, p);
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p + addr, s, sprev, pkeep);
MOP_OUT;
JUMP;
@@ -2883,6 +3512,11 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
CASE(OP_POP) MOP_IN(OP_POP);
STACK_POP_ONE;
+ /* We need to increment num_fail here, for invoking a cache optimization correctly, */
+ /* because Onigmo makes a loop, which is pairwise disjoint to the following set, as atomic. */
+#ifdef USE_CACHE_MATCH_OPT
+ msa->num_fail++;
+#endif
MOP_OUT;
JUMP;
@@ -2891,6 +3525,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
GET_RELADDR_INC(addr, p);
if (*p == *s && DATA_ENSURE_CHECK1) {
p++;
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p + addr, s, sprev, pkeep);
MOP_OUT;
JUMP;
@@ -2904,6 +3539,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
GET_RELADDR_INC(addr, p);
if (*p == *s) {
p++;
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p + addr, s, sprev, pkeep);
MOP_OUT;
JUMP;
@@ -2922,6 +3558,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
STACK_PUSH_REPEAT(mem, p);
if (reg->repeat_range[mem].lower == 0) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, end - s, msa->match_cache);
STACK_PUSH_ALT(p + addr, s, sprev, pkeep);
}
}
@@ -2938,6 +3575,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
STACK_PUSH_REPEAT(mem, p);
if (reg->repeat_range[mem].lower == 0) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
STACK_PUSH_ALT(p, s, sprev, pkeep);
p += addr;
}
@@ -2956,6 +3594,9 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
/* end of repeat. Nothing to do. */
}
else if (stkp->u.repeat.count >= reg->repeat_range[mem].lower) {
+ if (*pbegin == OP_REPEAT_INC) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
+ }
STACK_PUSH_ALT(p, s, sprev, pkeep);
p = STACK_AT(si)->u.repeat.pcode; /* Don't use stkp after PUSH. */
}
@@ -2986,6 +3627,9 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
UChar* pcode = stkp->u.repeat.pcode;
STACK_PUSH_REPEAT_INC(si);
+ if (*pbegin == OP_REPEAT_INC_NG) {
+ DO_CACHE_MATCH_OPT(reg, stk_base, repeat_stk, msa->enable_cache_match_opt, pbegin, msa->num_cache_table, msa->num_cache_opcode, msa->cache_index_table, s - str, msa->match_cache);
+ }
STACK_PUSH_ALT(pcode, s, sprev, pkeep);
}
else {
@@ -3173,6 +3817,46 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
sprev = stk->u.state.pstr_prev;
pkeep = stk->u.state.pkeep;
+#ifdef USE_CACHE_MATCH_OPT
+ if (++msa->num_fail >= (long)(end - str) + 1 && msa->num_cache_opcode == NUM_CACHE_OPCODE_UNINIT) {
+ long table_size = 0;
+ msa->enable_cache_match_opt = 1;
+ if (msa->num_cache_opcode == NUM_CACHE_OPCODE_UNINIT) {
+ msa->num_cache_opcode = count_num_cache_opcode(reg, &table_size);
+ }
+ if (msa->num_cache_opcode == NUM_CACHE_OPCODE_FAIL || msa->num_cache_opcode == 0) {
+ msa->enable_cache_match_opt = 0;
+ goto fail_match_cache_opt;
+ }
+ if (msa->cache_index_table == NULL) {
+ OnigCacheIndex *table = (OnigCacheIndex *)xmalloc(table_size * sizeof(OnigCacheIndex));
+ if (table == NULL) {
+ return ONIGERR_MEMORY;
+ }
+ init_cache_index_table(reg, table);
+ msa->cache_index_table = table;
+ msa->num_cache_table = table_size;
+ }
+ size_t len = (end - str) + 1;
+ size_t match_cache_size8 = (size_t)msa->num_cache_opcode * len;
+ /* overflow check */
+ if (match_cache_size8 / len != (size_t)msa->num_cache_opcode) {
+ return ONIGERR_MEMORY;
+ }
+ /* Currently, int is used for the key of match_cache */
+ if (match_cache_size8 >= LONG_MAX_LIMIT) {
+ return ONIGERR_MEMORY;
+ }
+ size_t match_cache_size = (match_cache_size8 >> 3) + (match_cache_size8 & 7 ? 1 : 0);
+ msa->match_cache = (uint8_t*)xmalloc(match_cache_size * sizeof(uint8_t));
+ if (msa->match_cache == NULL) {
+ return ONIGERR_MEMORY;
+ }
+ xmemset(msa->match_cache, 0, match_cache_size * sizeof(uint8_t));
+ }
+ fail_match_cache_opt:
+#endif
+
#ifdef USE_COMBINATION_EXPLOSION_CHECK
if (stk->u.state.state_check != 0) {
stk->type = STK_STATE_CHECK_MARK;
diff --git a/regint.h b/regint.h
index 00b4b6ed9b16f9..75073e6377a48e 100644
--- a/regint.h
+++ b/regint.h
@@ -41,6 +41,14 @@
/* for byte-code statistical data. */
/* #define ONIG_DEBUG_STATISTICS */
+/* enable matching optimization by using cache. */
+#define USE_CACHE_MATCH_OPT
+
+#ifdef USE_CACHE_MATCH_OPT
+# define NUM_CACHE_OPCODE_FAIL -1
+# define NUM_CACHE_OPCODE_UNINIT -2
+#endif
+
#if defined(ONIG_DEBUG_PARSE_TREE) || defined(ONIG_DEBUG_MATCH) || \
defined(ONIG_DEBUG_SEARCH) || defined(ONIG_DEBUG_COMPILE) || \
defined(ONIG_DEBUG_STATISTICS) || defined(ONIG_DEBUG_MEMLEAK)
@@ -379,6 +387,7 @@ typedef unsigned int BitStatusType;
#define INT_MAX_LIMIT ((1UL << (SIZEOF_INT * 8 - 1)) - 1)
+#define LONG_MAX_LIMIT ((1UL << (SIZEOF_LONG * 8 - 1)) - 1)
#define DIGITVAL(code) ((code) - '0')
#define ODIGITVAL(code) DIGITVAL(code)
@@ -820,6 +829,7 @@ typedef intptr_t OnigStackIndex;
typedef struct _OnigStackType {
unsigned int type;
+ OnigStackIndex null_check;
union {
struct {
UChar *pcode; /* byte code position */
@@ -863,6 +873,14 @@ typedef struct _OnigStackType {
} u;
} OnigStackType;
+#ifdef USE_CACHE_MATCH_OPT
+typedef struct {
+ UChar *addr;
+ long num;
+ int outer_repeat;
+} OnigCacheIndex;
+#endif
+
typedef struct {
void* stack_p;
size_t stack_n;
@@ -885,6 +903,14 @@ typedef struct {
#else
uint64_t end_time;
#endif
+#ifdef USE_CACHE_MATCH_OPT
+ long num_fail;
+ int enable_cache_match_opt;
+ long num_cache_opcode;
+ long num_cache_table;
+ OnigCacheIndex* cache_index_table;
+ uint8_t* match_cache;
+#endif
} OnigMatchArg;
diff --git a/shape.c b/shape.c
index 1de89d3f8f2b42..b20ac10a14ce81 100644
--- a/shape.c
+++ b/shape.c
@@ -1,15 +1,19 @@
#include "vm_core.h"
#include "vm_sync.h"
#include "shape.h"
+#include "gc.h"
#include "internal/class.h"
#include "internal/symbol.h"
#include "internal/variable.h"
#include
+static ID id_frozen;
+static ID size_pool_edge_names[SIZE_POOL_COUNT];
+
/*
* Shape getters
*/
-static rb_shape_t*
+rb_shape_t *
rb_shape_get_root_shape(void)
{
return GET_VM()->root_shape;
@@ -21,12 +25,6 @@ rb_shape_id(rb_shape_t * shape)
return (shape_id_t)(shape - GET_VM()->shape_list);
}
-static rb_shape_t*
-rb_shape_get_frozen_root_shape(void)
-{
- return GET_VM()->frozen_root_shape;
-}
-
bool
rb_shape_root_shape_p(rb_shape_t* shape)
{
@@ -53,6 +51,12 @@ rb_shape_get_shape_by_id_without_assertion(shape_id_t shape_id)
return shape;
}
+rb_shape_t *
+rb_shape_get_parent(rb_shape_t * shape)
+{
+ return rb_shape_get_shape_by_id(shape->parent_id);
+}
+
#if !SHAPE_IN_BASIC_FLAGS
shape_id_t
rb_rclass_shape_id(VALUE obj)
@@ -68,7 +72,7 @@ shape_id_t
rb_shape_get_shape_id(VALUE obj)
{
if (RB_SPECIAL_CONST_P(obj)) {
- return FROZEN_ROOT_SHAPE_ID;
+ return SPECIAL_CONST_SHAPE_ID;
}
#if SHAPE_IN_BASIC_FLAGS
@@ -107,18 +111,15 @@ rb_shape_lookup_id(rb_shape_t* shape, ID id, enum shape_type shape_type)
return NULL;
}
}
- shape = rb_shape_get_shape_by_id(shape->parent_id);
+ shape = rb_shape_get_parent(shape);
}
return NULL;
}
static rb_shape_t*
-get_next_shape_internal(rb_shape_t* shape, ID id, VALUE obj, enum shape_type shape_type)
+get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type)
{
rb_shape_t *res = NULL;
-
- RUBY_ASSERT(SHAPE_FROZEN != (enum shape_type)shape->type || RB_TYPE_P(obj, T_MODULE) || RB_TYPE_P(obj, T_CLASS));
-
RB_VM_LOCK_ENTER();
{
if (rb_shape_lookup_id(shape, id, shape_type)) {
@@ -142,23 +143,18 @@ get_next_shape_internal(rb_shape_t* shape, ID id, VALUE obj, enum shape_type sha
rb_shape_t * new_shape = rb_shape_alloc(id, shape);
new_shape->type = (uint8_t)shape_type;
+ new_shape->capacity = shape->capacity;
switch (shape_type) {
case SHAPE_IVAR:
- new_shape->next_iv_index = rb_shape_get_shape_by_id(new_shape->parent_id)->next_iv_index + 1;
-
- // Check if we should update next_iv_index on the object's class
- if (BUILTIN_TYPE(obj) == T_OBJECT) {
- VALUE klass = rb_obj_class(obj);
- if (new_shape->next_iv_index > RCLASS_EXT(klass)->max_iv_count) {
- RCLASS_EXT(klass)->max_iv_count = new_shape->next_iv_index;
- }
- }
+ new_shape->next_iv_index = shape->next_iv_index + 1;
break;
+ case SHAPE_CAPACITY_CHANGE:
case SHAPE_IVAR_UNDEF:
case SHAPE_FROZEN:
- new_shape->next_iv_index = rb_shape_get_shape_by_id(new_shape->parent_id)->next_iv_index;
+ new_shape->next_iv_index = shape->next_iv_index;
break;
+ case SHAPE_INITIAL_CAPACITY:
case SHAPE_ROOT:
rb_bug("Unreachable");
break;
@@ -183,7 +179,7 @@ rb_shape_frozen_shape_p(rb_shape_t* shape)
void
rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape)
{
- rb_shape_t* next_shape = get_next_shape_internal(shape, id, obj, SHAPE_IVAR_UNDEF);
+ rb_shape_t * next_shape = get_next_shape_internal(shape, id, SHAPE_IVAR_UNDEF);
if (shape == next_shape) {
return;
@@ -206,16 +202,11 @@ rb_shape_transition_shape_frozen(VALUE obj)
rb_shape_t* next_shape;
if (shape == rb_shape_get_root_shape()) {
- next_shape = rb_shape_get_frozen_root_shape();
+ rb_shape_set_shape_id(obj, SPECIAL_CONST_SHAPE_ID);
+ return;
}
- else {
- static ID id_frozen;
- if (!id_frozen) {
- id_frozen = rb_make_internal_id();
- }
- next_shape = get_next_shape_internal(shape, (ID)id_frozen, obj, SHAPE_FROZEN);
- }
+ next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN);
RUBY_ASSERT(next_shape);
rb_shape_set_shape(obj, next_shape);
@@ -231,10 +222,39 @@ rb_shape_transition_shape(VALUE obj, ID id, rb_shape_t *shape)
rb_shape_set_shape(obj, next_shape);
}
-rb_shape_t*
+/*
+ * This function is used for assertions where we don't want to increment
+ * max_iv_count
+ */
+rb_shape_t *
+rb_shape_get_next_iv_shape(rb_shape_t* shape, ID id)
+{
+ return get_next_shape_internal(shape, id, SHAPE_IVAR);
+}
+
+rb_shape_t *
rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
{
- return get_next_shape_internal(shape, id, obj, SHAPE_IVAR);
+ rb_shape_t * new_shape = rb_shape_get_next_iv_shape(shape, id);
+
+ // Check if we should update max_iv_count on the object's class
+ if (BUILTIN_TYPE(obj) == T_OBJECT) {
+ VALUE klass = rb_obj_class(obj);
+ if (new_shape->next_iv_index > RCLASS_EXT(klass)->max_iv_count) {
+ RCLASS_EXT(klass)->max_iv_count = new_shape->next_iv_index;
+ }
+ }
+
+ return new_shape;
+}
+
+rb_shape_t *
+rb_shape_transition_shape_capa(rb_shape_t* shape, uint32_t new_capacity)
+{
+ ID edge_name = rb_make_temporary_id(new_capacity);
+ rb_shape_t * new_shape = get_next_shape_internal(shape, edge_name, SHAPE_CAPACITY_CHANGE);
+ new_shape->capacity = new_capacity;
+ return new_shape;
}
bool
@@ -250,14 +270,16 @@ rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
RUBY_ASSERT(shape->next_iv_index > 0);
*value = shape->next_iv_index - 1;
return true;
+ case SHAPE_CAPACITY_CHANGE:
case SHAPE_IVAR_UNDEF:
case SHAPE_ROOT:
+ case SHAPE_INITIAL_CAPACITY:
return false;
case SHAPE_FROZEN:
- rb_bug("Ivar should not exist on frozen transition\n");
+ rb_bug("Ivar should not exist on transition\n");
}
}
- shape = rb_shape_get_shape_by_id(shape->parent_id);
+ shape = rb_shape_get_parent(shape);
}
return false;
}
@@ -289,10 +311,19 @@ rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id)
return shape;
}
+rb_shape_t *
+rb_shape_alloc_with_size_pool_index(ID edge_name, rb_shape_t * parent, uint8_t size_pool_index)
+{
+ rb_shape_t * shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
+ shape->size_pool_index = size_pool_index;
+ return shape;
+}
+
+
rb_shape_t *
rb_shape_alloc(ID edge_name, rb_shape_t * parent)
{
- return rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
+ return rb_shape_alloc_with_size_pool_index(edge_name, parent, parent->size_pool_index);
}
MJIT_FUNC_EXPORTED void
@@ -307,6 +338,39 @@ rb_shape_flags_mask(void)
return SHAPE_FLAG_MASK;
}
+rb_shape_t *
+rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
+{
+ rb_shape_t * midway_shape;
+
+ if (dest_shape->type != SHAPE_ROOT) {
+ midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape));
+ }
+ else {
+ midway_shape = initial_shape;
+ }
+
+ switch (dest_shape->type) {
+ case SHAPE_IVAR:
+ if (midway_shape->capacity < midway_shape->next_iv_index) {
+ // There isn't enough room to write this IV, so we need to increase the capacity
+ midway_shape = rb_shape_transition_shape_capa(midway_shape, midway_shape->capacity * 2);
+ }
+
+ midway_shape = rb_shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
+ break;
+ case SHAPE_IVAR_UNDEF:
+ midway_shape = get_next_shape_internal(midway_shape, dest_shape->edge_name, SHAPE_IVAR_UNDEF);
+ break;
+ case SHAPE_ROOT:
+ case SHAPE_FROZEN:
+ case SHAPE_CAPACITY_CHANGE:
+ break;
+ }
+
+ return midway_shape;
+}
+
#if VM_CHECK_MODE > 0
VALUE rb_cShape;
@@ -335,6 +399,14 @@ rb_shape_type(VALUE self)
return INT2NUM(shape->type);
}
+static VALUE
+rb_shape_capacity(VALUE self)
+{
+ rb_shape_t * shape;
+ TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
+ return INT2NUM(shape->capacity);
+}
+
static VALUE
rb_shape_parent_id(VALUE self)
{
@@ -398,11 +470,16 @@ rb_shape_edge_name(VALUE self)
rb_shape_t* shape;
TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
- if (shape->edge_name) {
- return ID2SYM(shape->edge_name);
+ if ((shape->edge_name & (ID_INTERNAL)) == ID_INTERNAL) {
+ return INT2NUM(shape->capacity);
}
else {
- return Qnil;
+ if (shape->edge_name) {
+ return ID2SYM(shape->edge_name);
+ }
+ else {
+ return Qnil;
+ }
}
}
@@ -415,6 +492,15 @@ rb_shape_next_iv_index(VALUE self)
return INT2NUM(shape->next_iv_index);
}
+static VALUE
+rb_shape_size_pool_index(VALUE self)
+{
+ rb_shape_t * shape;
+ TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
+
+ return INT2NUM(shape->size_pool_index);
+}
+
static VALUE
rb_shape_export_depth(VALUE self)
{
@@ -424,7 +510,7 @@ rb_shape_export_depth(VALUE self)
unsigned int depth = 0;
while (shape->parent_id != INVALID_SHAPE_ID) {
depth++;
- shape = rb_shape_get_shape_by_id(shape->parent_id);
+ shape = rb_shape_get_parent(shape);
}
return INT2NUM(depth);
}
@@ -435,7 +521,7 @@ rb_shape_parent(VALUE self)
rb_shape_t * shape;
TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
if (shape->parent_id != INVALID_SHAPE_ID) {
- return rb_shape_t_to_rb_cShape(rb_shape_get_shape_by_id(shape->parent_id));
+ return rb_shape_t_to_rb_cShape(rb_shape_get_parent(shape));
}
else {
return Qnil;
@@ -454,12 +540,6 @@ rb_shape_root_shape(VALUE self)
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
}
-static VALUE
-rb_shape_frozen_root_shape(VALUE self)
-{
- return rb_shape_t_to_rb_cShape(rb_shape_get_frozen_root_shape());
-}
-
VALUE rb_obj_shape(rb_shape_t* shape);
static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
@@ -518,6 +598,43 @@ rb_shape_find_by_id(VALUE mod, VALUE id)
}
#endif
+void
+Init_default_shapes(void)
+{
+ id_frozen = rb_make_internal_id();
+
+ // Shapes by size pool
+ for (int i = 0; i < SIZE_POOL_COUNT; i++) {
+ size_pool_edge_names[i] = rb_make_internal_id();
+ }
+
+ // Root shape
+ rb_shape_t * root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
+ root->capacity = (uint32_t)((rb_size_pool_slot_size(0) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
+ root->type = SHAPE_ROOT;
+ root->size_pool_index = 0;
+ GET_VM()->root_shape = root;
+ RUBY_ASSERT(rb_shape_id(GET_VM()->root_shape) == ROOT_SHAPE_ID);
+
+ // Shapes by size pool
+ for (int i = 1; i < SIZE_POOL_COUNT; i++) {
+ uint32_t capa = (uint32_t)((rb_size_pool_slot_size(i) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
+ rb_shape_t * new_shape = rb_shape_transition_shape_capa(root, capa);
+ new_shape->type = SHAPE_INITIAL_CAPACITY;
+ new_shape->size_pool_index = i;
+ RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i);
+ }
+
+ // Special const shape
+#if RUBY_DEBUG
+ rb_shape_t * special_const_shape =
+#endif
+ get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN);
+ RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID);
+ RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_VM()->next_shape_id - 1));
+ RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape));
+}
+
void
Init_shape(void)
{
@@ -530,21 +647,23 @@ Init_shape(void)
rb_define_method(rb_cShape, "edges", rb_shape_edges, 0);
rb_define_method(rb_cShape, "edge_name", rb_shape_edge_name, 0);
rb_define_method(rb_cShape, "next_iv_index", rb_shape_next_iv_index, 0);
+ rb_define_method(rb_cShape, "size_pool_index", rb_shape_size_pool_index, 0);
rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0);
rb_define_method(rb_cShape, "id", rb_wrapped_shape_id, 0);
rb_define_method(rb_cShape, "type", rb_shape_type, 0);
+ rb_define_method(rb_cShape, "capacity", rb_shape_capacity, 0);
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
rb_define_const(rb_cShape, "SHAPE_IVAR_UNDEF", INT2NUM(SHAPE_IVAR_UNDEF));
rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN));
rb_define_const(rb_cShape, "SHAPE_BITS", INT2NUM(SHAPE_BITS));
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
+ rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
rb_define_singleton_method(rb_cShape, "transition_tree", shape_transition_tree, 0);
rb_define_singleton_method(rb_cShape, "find_by_id", rb_shape_find_by_id, 1);
rb_define_singleton_method(rb_cShape, "next_shape_id", next_shape_id, 0);
rb_define_singleton_method(rb_cShape, "of", rb_shape_debug_shape, 1);
rb_define_singleton_method(rb_cShape, "root_shape", rb_shape_root_shape, 0);
- rb_define_singleton_method(rb_cShape, "frozen_root_shape", rb_shape_frozen_root_shape, 0);
#endif
}
diff --git a/shape.h b/shape.h
index 8e1bf46ec9cd53..417a01369702a8 100644
--- a/shape.h
+++ b/shape.h
@@ -40,13 +40,17 @@ typedef uint16_t shape_id_t;
# define MAX_SHAPE_ID (SHAPE_MASK - 1)
# define INVALID_SHAPE_ID SHAPE_MASK
# define ROOT_SHAPE_ID 0x0
-# define FROZEN_ROOT_SHAPE_ID 0x1
+// We use SIZE_POOL_COUNT number of shape IDs for transitions out of different size pools
+// The next available shapd ID will be the SPECIAL_CONST_SHAPE_ID
+# define SPECIAL_CONST_SHAPE_ID SIZE_POOL_COUNT
struct rb_shape {
struct rb_id_table * edges; // id_table from ID (ivar) to next shape
ID edge_name; // ID (ivar) for transition from parent to rb_shape
attr_index_t next_iv_index;
+ uint32_t capacity; // Total capacity of the object with this shape
uint8_t type;
+ uint8_t size_pool_index;
shape_id_t parent_id;
};
@@ -56,7 +60,9 @@ enum shape_type {
SHAPE_ROOT,
SHAPE_IVAR,
SHAPE_FROZEN,
+ SHAPE_CAPACITY_CHANGE,
SHAPE_IVAR_UNDEF,
+ SHAPE_INITIAL_CAPACITY,
};
#if SHAPE_IN_BASIC_FLAGS
@@ -124,8 +130,10 @@ static inline shape_id_t RCLASS_SHAPE_ID(VALUE obj) {
#endif
bool rb_shape_root_shape_p(rb_shape_t* shape);
+rb_shape_t * rb_shape_get_root_shape(void);
rb_shape_t* rb_shape_get_shape_by_id_without_assertion(shape_id_t shape_id);
+rb_shape_t * rb_shape_get_parent(rb_shape_t * shape);
MJIT_SYMBOL_EXPORT_BEGIN
rb_shape_t* rb_shape_get_shape_by_id(shape_id_t shape_id);
@@ -135,21 +143,37 @@ rb_shape_t* rb_shape_get_shape(VALUE obj);
int rb_shape_frozen_shape_p(rb_shape_t* shape);
void rb_shape_transition_shape_frozen(VALUE obj);
void rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape);
+rb_shape_t * rb_shape_transition_shape_capa(rb_shape_t * shape, uint32_t new_capacity);
void rb_shape_transition_shape(VALUE obj, ID id, rb_shape_t *shape);
+rb_shape_t * rb_shape_get_next_iv_shape(rb_shape_t * shape, ID id);
rb_shape_t* rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id);
bool rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t * value);
shape_id_t rb_shape_id(rb_shape_t * shape);
MJIT_SYMBOL_EXPORT_END
+rb_shape_t * rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape);
+
+static inline uint32_t
+ROBJECT_IV_CAPACITY(VALUE obj)
+{
+ RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
+ return rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj))->capacity;
+}
+
static inline uint32_t
ROBJECT_IV_COUNT(VALUE obj)
{
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
uint32_t ivc = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj))->next_iv_index;
- RUBY_ASSERT(ivc <= ROBJECT_NUMIV(obj));
return ivc;
}
+static inline uint32_t
+RBASIC_IV_COUNT(VALUE obj)
+{
+ return rb_shape_get_shape_by_id(rb_shape_get_shape_id(obj))->next_iv_index;
+}
+
static inline uint32_t
RCLASS_IV_COUNT(VALUE obj)
{
@@ -159,6 +183,7 @@ RCLASS_IV_COUNT(VALUE obj)
}
rb_shape_t * rb_shape_alloc(ID edge_name, rb_shape_t * parent);
+rb_shape_t * rb_shape_alloc_with_size_pool_index(ID edge_name, rb_shape_t * parent, uint8_t size_pool_index);
rb_shape_t * rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id);
bool rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id);
diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb
index 68a24be31c6914..43ae9cdcebf0bf 100644
--- a/spec/bundler/bundler/shared_helpers_spec.rb
+++ b/spec/bundler/bundler/shared_helpers_spec.rb
@@ -246,6 +246,13 @@
end
end
+ shared_examples_for "ENV['BUNDLER_SETUP'] gets set correctly" do
+ it "ensures bundler/setup is set in ENV['BUNDLER_SETUP']" do
+ subject.set_bundle_environment
+ expect(ENV["BUNDLER_SETUP"]).to eq("#{source_lib_dir}/bundler/setup")
+ end
+ end
+
shared_examples_for "ENV['RUBYLIB'] gets set correctly" do
let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
index 11ff49bf89a3da..1ad5f76466e886 100644
--- a/spec/bundler/commands/update_spec.rb
+++ b/spec/bundler/commands/update_spec.rb
@@ -613,6 +613,12 @@
expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m).
and match(/freeze \nby running `bundle config unset deployment`./m)
end
+
+ it "should not suggest any command to unfreeze bundler if frozen is set through ENV" do
+ bundle "update", :all => true, :raise_on_error => false, :env => { "BUNDLE_FROZEN" => "true" }
+ expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m)
+ expect(err).not_to match(/by running/)
+ end
end
describe "with --source option" do
diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb
index 1bae0bb3715d40..69918cf9f9b563 100644
--- a/spec/bundler/install/gemfile/platform_spec.rb
+++ b/spec/bundler/install/gemfile/platform_spec.rb
@@ -75,6 +75,81 @@
expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
end
+ context "on universal Rubies" do
+ before do
+ build_repo4 do
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "ruby"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 RUBY'"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "arm64-darwin"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 arm64-darwin'"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "x86_64-darwin"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 x86_64-darwin'"
+ end
+ end
+ end
+
+ it "pulls in the correct architecture gem" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ darwin_single_arch (1.0)
+ darwin_single_arch (1.0-arm64-darwin)
+ darwin_single_arch (1.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ darwin_single_arch
+ G
+
+ simulate_platform "universal-darwin-21"
+ simulate_ruby_platform "universal.x86_64-darwin21" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "darwin_single_arch"
+ G
+
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin"
+ end
+ end
+
+ it "pulls in the correct architecture gem on arm64e macOS Ruby" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ darwin_single_arch (1.0)
+ darwin_single_arch (1.0-arm64-darwin)
+ darwin_single_arch (1.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ darwin_single_arch
+ G
+
+ simulate_platform "universal-darwin-21"
+ simulate_ruby_platform "universal.arm64e-darwin21" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "darwin_single_arch"
+ G
+
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin"
+ end
+ end
+ end
+
it "works with gems that have different dependencies" do
simulate_platform "java"
install_gemfile <<-G
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
index 699672f357e087..98efec396cce58 100644
--- a/spec/bundler/install/gemfile/specific_platform_spec.rb
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -148,6 +148,38 @@
expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)")
end
+ it "chooses platform specific gems even when resolving upon materialization and the API returns more specific plaforms first" do
+ build_repo4 do
+ build_gem("grpc", "1.50.0")
+ build_gem("grpc", "1.50.0") {|s| s.platform = "universal-darwin" }
+ end
+
+ gemfile <<-G
+ source "https://localgemserver.test"
+ gem "grpc"
+ G
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ grpc (1.50.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ grpc
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose", :artifice => "compact_index_precompiled_before", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ expect(out).to include("Installing grpc 1.50.0 (universal-darwin)")
+ end
+
it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do
setup_multiplatform_gem
gemfile(google_protobuf)
diff --git a/spec/bundler/support/artifice/compact_index_precompiled_before.rb b/spec/bundler/support/artifice/compact_index_precompiled_before.rb
new file mode 100644
index 00000000000000..9f310e653b2293
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_precompiled_before.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require_relative "compact_index"
+
+Artifice.deactivate
+
+class CompactIndexPrecompiledBefore < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ move_ruby_variant_to_the_end(CompactIndex.info(gem ? gem.versions : []))
+ end
+ end
+
+ private
+
+ def move_ruby_variant_to_the_end(response)
+ lines = response.split("\n")
+ ruby = lines.find {|line| /\A\d+\.\d+\.\d* \|/.match(line) }
+ lines.delete(ruby)
+ lines.push(ruby).join("\n")
+ end
+end
+
+Artifice.activate_with(CompactIndexPrecompiledBefore)
diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb
index da67e8c5d1bfc0..76e3b05ee12af6 100644
--- a/spec/bundler/support/hax.rb
+++ b/spec/bundler/support/hax.rb
@@ -1,5 +1,10 @@
# frozen_string_literal: true
+if ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+ Object.send(:remove_const, :RUBY_PLATFORM)
+ RUBY_PLATFORM = ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+end
+
module Gem
def self.ruby=(ruby)
@ruby = ruby
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
index dfc358796af539..1541f903c7fd7b 100644
--- a/spec/bundler/support/helpers.rb
+++ b/spec/bundler/support/helpers.rb
@@ -432,6 +432,14 @@ def simulate_new_machine
pristine_system_gems :bundler
end
+ def simulate_ruby_platform(ruby_platform)
+ old = ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+ ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = ruby_platform.to_s
+ yield
+ ensure
+ ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = old
+ end
+
def simulate_platform(platform)
old = ENV["BUNDLER_SPEC_PLATFORM"]
ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s
diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb
index ebe13434c2d738..23a5dc0fb333d2 100755
--- a/spec/mspec/tool/tag_from_output.rb
+++ b/spec/mspec/tool/tag_from_output.rb
@@ -11,6 +11,11 @@
output = ARGF.readlines
+# Automatically strip datetime of GitHub Actions
+if output.first =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z /
+ output = output.map { |line| line.split(' ', 2).last }
+end
+
NUMBER = /^\d+\)$/
ERROR_OR_FAILED = / (ERROR|FAILED)$/
SPEC_FILE = /^(\/.+_spec\.rb)\:\d+/
@@ -22,11 +27,24 @@
description = error_line.match(ERROR_OR_FAILED).pre_match
spec_file = rest.find { |line| line =~ SPEC_FILE }
- unless spec_file
- warn "Could not find file for:\n#{error_line}"
- next
+ if spec_file
+ spec_file = spec_file[SPEC_FILE, 1] or raise
+ else
+ if error_line =~ /^(\w+)#(\w+) /
+ module_method = error_line.split(' ', 2).first
+ file = "#{$1.downcase}/#{$2}_spec.rb"
+ spec_file = ['spec/ruby/core', 'spec/ruby/library', *Dir.glob('spec/ruby/library/*')].find { |dir|
+ path = "#{dir}/#{file}"
+ break path if File.exist?(path)
+ }
+ end
+
+ unless spec_file
+ warn "Could not find file for:\n#{error_line}"
+ next
+ end
end
- spec_file = spec_file[SPEC_FILE, 1]
+
prefix = spec_file.index('spec/ruby/') || spec_file.index('spec/truffle/')
spec_file = spec_file[prefix..-1]
diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml
index 3a16fc43f8ee07..82733c4b4d9205 100644
--- a/spec/ruby/.rubocop.yml
+++ b/spec/ruby/.rubocop.yml
@@ -115,6 +115,10 @@ Lint/EmptyWhen:
- language/case_spec.rb
- optional/capi/spec_helper.rb
+Lint/ErbNewArguments:
+ Exclude:
+ - 'library/erb/new_spec.rb'
+
Lint/FormatParameterMismatch:
Exclude:
- 'core/kernel/shared/sprintf.rb'
diff --git a/spec/ruby/README.md b/spec/ruby/README.md
index 24b4719fdda515..018bf0ca3e12f4 100644
--- a/spec/ruby/README.md
+++ b/spec/ruby/README.md
@@ -1,7 +1,6 @@
# The Ruby Spec Suite
[](https://github.com/ruby/spec/actions)
-[](https://gitter.im/ruby/spec)
The Ruby Spec Suite, abbreviated `ruby/spec`, is a test suite for the behavior of the Ruby programming language.
diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb
index 2b1a84abcab802..c6364af12da7f2 100644
--- a/spec/ruby/core/array/pack/m_spec.rb
+++ b/spec/ruby/core/array/pack/m_spec.rb
@@ -80,8 +80,16 @@
].should be_computed_by(:pack, "M")
end
- it "encodes a tab followed by a newline with an encoded newline" do
+ it "encodes a tab at the end of a line with an encoded newline" do
+ ["\t"].pack("M").should == "\t=\n"
["\t\n"].pack("M").should == "\t=\n\n"
+ ["abc\t\nxyz"].pack("M").should == "abc\t=\n\nxyz=\n"
+ end
+
+ it "encodes a space at the end of a line with an encoded newline" do
+ [" "].pack("M").should == " =\n"
+ [" \n"].pack("M").should == " =\n\n"
+ ["abc \nxyz"].pack("M").should == "abc =\n\nxyz=\n"
end
it "encodes 127..255 in hex format" do
diff --git a/spec/ruby/core/enumerable/each_cons_spec.rb b/spec/ruby/core/enumerable/each_cons_spec.rb
index ba658203a26598..8fb31fb9257738 100644
--- a/spec/ruby/core/enumerable/each_cons_spec.rb
+++ b/spec/ruby/core/enumerable/each_cons_spec.rb
@@ -56,6 +56,12 @@
multi.each_cons(2).to_a.should == [[[1, 2], [3, 4, 5]], [[3, 4, 5], [6, 7, 8, 9]]]
end
+ ruby_version_is "3.1" do
+ it "returns self when a block is given" do
+ @enum.each_cons(3){}.should == @enum
+ end
+ end
+
describe "when no block is given" do
it "returns an enumerator" do
e = @enum.each_cons(3)
diff --git a/spec/ruby/core/enumerable/each_slice_spec.rb b/spec/ruby/core/enumerable/each_slice_spec.rb
index 2ea89f5e72e43f..a57a1dba81fbad 100644
--- a/spec/ruby/core/enumerable/each_slice_spec.rb
+++ b/spec/ruby/core/enumerable/each_slice_spec.rb
@@ -57,6 +57,12 @@
e.to_a.should == @sliced
end
+ ruby_version_is "3.1" do
+ it "returns self when a block is given" do
+ @enum.each_slice(3){}.should == @enum
+ end
+ end
+
it "gathers whole arrays as elements when each yields multiple" do
multi = EnumerableSpecs::YieldsMulti.new
multi.each_slice(2).to_a.should == [[[1, 2], [3, 4, 5]], [[6, 7, 8, 9]]]
diff --git a/spec/ruby/core/enumerator/lazy/compact_spec.rb b/spec/ruby/core/enumerator/lazy/compact_spec.rb
new file mode 100644
index 00000000000000..80b6f9481d4fc5
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/compact_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is '3.1' do
+ describe "Enumerator::Lazy#compact" do
+ it 'returns array without nil elements' do
+ arr = [1, nil, 3, false, 5].to_enum.lazy.compact
+ arr.should be_an_instance_of(Enumerator::Lazy)
+ arr.force.should == [1, 3, false, 5]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/lazy_spec.rb b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
index 683dfb81d76d7a..0fb104e25ab7cf 100644
--- a/spec/ruby/core/enumerator/lazy/lazy_spec.rb
+++ b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
@@ -30,13 +30,3 @@
lazy.lazy.should equal(lazy)
end
end
-
-ruby_version_is '3.1' do
- describe "Enumerator::Lazy#compact" do
- it 'returns array without nil elements' do
- arr = [1, nil, 3, false, 5].to_enum.lazy.compact
- arr.should be_an_instance_of(Enumerator::Lazy)
- arr.force.should == [1, 3, false, 5]
- end
- end
-end
diff --git a/spec/ruby/core/false/case_compare_spec.rb b/spec/ruby/core/false/case_compare_spec.rb
new file mode 100644
index 00000000000000..0bd0ab44aec906
--- /dev/null
+++ b/spec/ruby/core/false/case_compare_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#===" do
+ it "returns true for false" do
+ (false === false).should == true
+ end
+
+ it "returns false for non-false object" do
+ (false === 0).should == false
+ (false === "").should == false
+ (false === Object).should == false
+ (false === nil).should == false
+ end
+end
diff --git a/spec/ruby/core/fiber/blocking_spec.rb b/spec/ruby/core/fiber/blocking_spec.rb
index 852861d12f1ec0..eeee5a71c13abb 100644
--- a/spec/ruby/core/fiber/blocking_spec.rb
+++ b/spec/ruby/core/fiber/blocking_spec.rb
@@ -66,8 +66,8 @@
context "when fiber is non-blocking" do
it "can become blocking" do
fiber = Fiber.new(blocking: false) do
- Fiber.blocking do |fiber|
- fiber.blocking? ? :blocking : :non_blocking
+ Fiber.blocking do |f|
+ f.blocking? ? :blocking : :non_blocking
end
end
diff --git a/spec/ruby/core/float/shared/to_i.rb b/spec/ruby/core/float/shared/to_i.rb
index 960295f09585e7..33b32ca5332d8d 100644
--- a/spec/ruby/core/float/shared/to_i.rb
+++ b/spec/ruby/core/float/shared/to_i.rb
@@ -7,4 +7,8 @@
-9223372036854775808.1.send(@method).should eql(-9223372036854775808)
9223372036854775808.1.send(@method).should eql(9223372036854775808)
end
+
+ it "raises a FloatDomainError for NaN" do
+ -> { nan_value.send(@method) }.should raise_error(FloatDomainError)
+ end
end
diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb
index 3649d4d8de0f62..2ccb4831208d2c 100644
--- a/spec/ruby/core/hash/hash_spec.rb
+++ b/spec/ruby/core/hash/hash_spec.rb
@@ -41,4 +41,13 @@
h.hash.should == {x: [h]}.hash
# Like above, because h.eql?(x: [h])
end
+
+ ruby_version_is "3.1" do
+ it "allows ommiting values" do
+ a = 1
+ b = 2
+
+ eval('{a:, b:}.should == { a: 1, b: 2 }')
+ end
+ end
end
diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb
index d34f7bd0eb5022..529afbf0ffc385 100644
--- a/spec/ruby/core/io/read_spec.rb
+++ b/spec/ruby/core/io/read_spec.rb
@@ -402,13 +402,6 @@
xE2 = [226].pack('C*')
result.should == ("abc" + xE2 + "def").force_encoding(Encoding::BINARY)
end
-
- it "does not transcode file contents when an internal encoding is specified" do
- result = File.open(@name, "r:binary:utf-8") { |f| f.read }.chomp
- result.encoding.should == Encoding::BINARY
- xE2 = [226].pack('C*')
- result.should == ("abc" + xE2 + "def").force_encoding(Encoding::BINARY)
- end
end
describe "IO#read in text mode" do
diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb
index 324ae0b6e6e5ee..2901b429c25181 100644
--- a/spec/ruby/core/io/readpartial_spec.rb
+++ b/spec/ruby/core/io/readpartial_spec.rb
@@ -93,10 +93,12 @@
@rd.readpartial(0).should == ""
end
- it "clears and returns the given buffer if the length argument is 0" do
- buffer = "existing content"
- @rd.readpartial(0, buffer).should == buffer
- buffer.should == ""
+ ruby_bug "#18421", ""..."3.0.4" do
+ it "clears and returns the given buffer if the length argument is 0" do
+ buffer = "existing content"
+ @rd.readpartial(0, buffer).should == buffer
+ buffer.should == ""
+ end
end
it "preserves the encoding of the given buffer" do
diff --git a/spec/ruby/core/io/set_encoding_spec.rb b/spec/ruby/core/io/set_encoding_spec.rb
index bc448acfceef94..22d9017635709f 100644
--- a/spec/ruby/core/io/set_encoding_spec.rb
+++ b/spec/ruby/core/io/set_encoding_spec.rb
@@ -1,7 +1,7 @@
require_relative '../../spec_helper'
describe :io_set_encoding_write, shared: true do
- it "sets the encodings to nil" do
+ it "sets the encodings to nil when they were set previously" do
@io = new_io @name, "#{@object}:ibm437:ibm866"
@io.set_encoding nil, nil
@@ -9,6 +9,19 @@
@io.internal_encoding.should be_nil
end
+ it "sets the encodings to nil when the IO is built with no explicit encoding" do
+ @io = new_io @name, @object
+
+ # Checking our assumptions first
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+ end
+
it "prevents the encodings from changing when Encoding defaults are changed" do
@io = new_io @name, "#{@object}:utf-8:us-ascii"
@io.set_encoding nil, nil
@@ -38,6 +51,7 @@
@external = Encoding.default_external
@internal = Encoding.default_internal
+ # The defaults
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = nil
@@ -113,6 +127,22 @@
describe "with 'a+' mode" do
it_behaves_like :io_set_encoding_write, nil, "a+"
end
+
+ describe "with standard IOs" do
+ it "correctly resets them" do
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+
+ begin
+ STDOUT.set_encoding(Encoding::US_ASCII, Encoding::ISO_8859_1)
+ ensure
+ STDOUT.set_encoding(nil, nil)
+ end
+
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+ end
+ end
end
describe "IO#set_encoding" do
diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb
index 4f043526b8fe85..cc8177fa02b28e 100644
--- a/spec/ruby/core/kernel/Complex_spec.rb
+++ b/spec/ruby/core/kernel/Complex_spec.rb
@@ -1,4 +1,6 @@
require_relative '../../spec_helper'
+require_relative '../../shared/kernel/complex'
+require_relative 'fixtures/Complex'
describe "Kernel.Complex()" do
describe "when passed [Complex, Complex]" do
@@ -58,7 +60,92 @@
end
end
- describe "when passed a String" do
+ describe "when passed [String]" do
+ it_behaves_like :kernel_complex, :Complex_method, KernelSpecs
+
+ context "invalid argument" do
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ Complex("79+4i".encode("UTF-16"))
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+
+ it "raises ArgumentError for unrecognised Strings" do
+ -> {
+ Complex("ruby")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "ruby"')
+ end
+
+ it "raises ArgumentError for trailing garbage" do
+ -> {
+ Complex("79+4iruby")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "79+4iruby"')
+ end
+
+ it "does not understand Float::INFINITY" do
+ -> {
+ Complex("Infinity")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "Infinity"')
+
+ -> {
+ Complex("-Infinity")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "-Infinity"')
+ end
+
+ it "does not understand Float::NAN" do
+ -> {
+ Complex("NaN")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "NaN"')
+ end
+
+ it "does not understand a sequence of _" do
+ -> {
+ Complex("7__9+4__0i")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "7__9+4__0i"')
+ end
+
+ it "does not allow null-byte" do
+ -> {
+ Complex("1-2i\0")
+ }.should raise_error(ArgumentError, "string contains null byte")
+ end
+ end
+
+ context "invalid argument and exception: false passed" do
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ Complex("79+4i".encode("UTF-16"), exception: false)
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+
+ it "returns nil for unrecognised Strings" do
+ Complex("ruby", exception: false).should == nil
+ end
+
+ it "returns nil when trailing garbage" do
+ Complex("79+4iruby", exception: false).should == nil
+ end
+
+ it "returns nil for Float::INFINITY" do
+ Complex("Infinity", exception: false).should == nil
+ Complex("-Infinity", exception: false).should == nil
+ end
+
+ it "returns nil for Float::NAN" do
+ Complex("NaN", exception: false).should == nil
+ end
+
+ it "returns nil when there is a sequence of _" do
+ Complex("7__9+4__0i", exception: false).should == nil
+ end
+
+ it "returns nil when String contains null-byte" do
+ Complex("1-2i\0", exception: false).should == nil
+ end
+ end
+ end
+
+ describe "when passes [String, String]" do
it "needs to be reviewed for spec completeness"
end
diff --git a/spec/ruby/core/kernel/fixtures/Complex.rb b/spec/ruby/core/kernel/fixtures/Complex.rb
new file mode 100644
index 00000000000000..bf14d55ad57a5c
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/Complex.rb
@@ -0,0 +1,5 @@
+module KernelSpecs
+ def self.Complex_method(string)
+ Complex(string)
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb
index 120619abef6e93..cc84daeb886605 100644
--- a/spec/ruby/core/kernel/shared/load.rb
+++ b/spec/ruby/core/kernel/shared/load.rb
@@ -154,6 +154,22 @@
end
end
+ describe "when passed a module for 'wrap'" do
+ ruby_version_is "3.1" do
+ it "sets the enclosing scope to the supplied module" do
+ path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR
+ mod = Module.new
+ @object.load(path, mod)
+
+ Object.const_defined?(:LoadSpecWrap).should be_false
+ mod.const_defined?(:LoadSpecWrap).should be_true
+
+ wrap_module = ScratchPad.recorded[1]
+ wrap_module.should == mod
+ end
+ end
+ end
+
describe "(shell expansion)" do
before :each do
@env_home = ENV["HOME"]
diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb
index 8965f902a0d8ec..7c0f089bb4b0d9 100644
--- a/spec/ruby/core/matchdata/element_reference_spec.rb
+++ b/spec/ruby/core/matchdata/element_reference_spec.rb
@@ -26,6 +26,21 @@
it "supports ranges [start..end]" do
/(.)(.)(\d+)(\d)/.match("THX1138.")[1..3].should == %w|H X 113|
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..10].should == %w|113 8|
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[-30..2].should == nil
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..1].should == []
+ end
+
+ it "supports endless ranges [start..]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..].should == %w|113 8|
+ end
+
+ it "supports beginningless ranges [..end]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[..1].should == %w|HX1138 H|
+ end
+
+ it "supports beginningless endless ranges [nil..nil]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[nil..nil].should == %w|HX1138 H X 113 8|
end
ruby_version_is "3.0" do
diff --git a/spec/ruby/core/method/fixtures/classes.rb b/spec/ruby/core/method/fixtures/classes.rb
index 50daa773e1179e..464a519aeacbac 100644
--- a/spec/ruby/core/method/fixtures/classes.rb
+++ b/spec/ruby/core/method/fixtures/classes.rb
@@ -84,6 +84,12 @@ def one_req_one_opt_with_splat_and_block(a, b=nil, *c, &blk); end
def two_req_one_opt_with_splat_and_block(a, b, c=nil, *d, &blk); end
def one_req_two_opt_with_splat_and_block(a, b=nil, c=nil, *d, &blk); end
+ def my_public_method; end
+ def my_protected_method; end
+ def my_private_method; end
+ protected :my_protected_method
+ private :my_private_method
+
define_method(:zero_defined_method, Proc.new {||})
define_method(:zero_with_splat_defined_method, Proc.new {|*x|})
define_method(:one_req_defined_method, Proc.new {|x|})
diff --git a/spec/ruby/core/method/private_spec.rb b/spec/ruby/core/method/private_spec.rb
new file mode 100644
index 00000000000000..230a4e9e81185b
--- /dev/null
+++ b/spec/ruby/core/method/private_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#private?" do
+ it "returns false when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).private?.should == false
+ end
+
+ it "returns false when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).private?.should == false
+ end
+
+ it "returns true when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).private?.should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/method/protected_spec.rb b/spec/ruby/core/method/protected_spec.rb
new file mode 100644
index 00000000000000..6ee85f77387931
--- /dev/null
+++ b/spec/ruby/core/method/protected_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#protected?" do
+ it "returns false when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).protected?.should == false
+ end
+
+ it "returns true when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).protected?.should == true
+ end
+
+ it "returns false when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).protected?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/method/public_spec.rb b/spec/ruby/core/method/public_spec.rb
new file mode 100644
index 00000000000000..3988468551da56
--- /dev/null
+++ b/spec/ruby/core/method/public_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#public?" do
+ it "returns true when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).public?.should == true
+ end
+
+ it "returns false when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).public?.should == false
+ end
+
+ it "returns false when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).public?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb
index c63a7aaa0fc4af..f9a18f38785861 100644
--- a/spec/ruby/core/method/super_method_spec.rb
+++ b/spec/ruby/core/method/super_method_spec.rb
@@ -55,10 +55,12 @@ def overridden; end
end
end
- context "after aliasing an inherited method" do
- it "returns the expected super_method" do
- method = MethodSpecs::InheritedMethods::C.new.method(:meow)
- method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ ruby_version_is "2.7.3" do
+ context "after aliasing an inherited method" do
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.new.method(:meow)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
end
end
end
diff --git a/spec/ruby/core/process/_fork_spec.rb b/spec/ruby/core/process/_fork_spec.rb
new file mode 100644
index 00000000000000..6f711ad2dd7d0f
--- /dev/null
+++ b/spec/ruby/core/process/_fork_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "Process._fork" do
+ it "for #respond_to? returns the same as Process.respond_to?(:fork)" do
+ Process.respond_to?(:_fork).should == Process.respond_to?(:fork)
+ end
+
+ guard_not -> { Process.respond_to?(:fork) } do
+ it "raises a NotImplementedError when called" do
+ -> { Process._fork }.should raise_error(NotImplementedError)
+ end
+ end
+
+ guard -> { Process.respond_to?(:fork) } do
+ it "is called by Process#fork" do
+ Process.should_receive(:_fork).once.and_return(42)
+
+ pid = Process.fork {}
+ pid.should equal(42)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb
index 9aa8da81251c5b..ad4800d4f8a3bb 100644
--- a/spec/ruby/core/process/spawn_spec.rb
+++ b/spec/ruby/core/process/spawn_spec.rb
@@ -567,6 +567,24 @@ def child_pids(pid)
end
end
+ platform_is_not :windows do
+ it "redirects non-default file descriptor to itself" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{file.fileno}, 'w'); f.print(:bang); f.flush"), file.fileno => file.fileno)
+ end.should output_to_fd("bang", file)
+ end
+ end
+ end
+
+ it "redirects default file descriptor to itself" do
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{STDOUT.fileno}, 'w'); f.print(:bang); f.flush"), STDOUT.fileno => STDOUT.fileno)
+ end.should output_to_fd("bang", STDOUT)
+ end
+
# :close_others
platform_is_not :windows do
diff --git a/spec/ruby/core/regexp/timeout_spec.rb b/spec/ruby/core/regexp/timeout_spec.rb
new file mode 100644
index 00000000000000..6fce261814ef48
--- /dev/null
+++ b/spec/ruby/core/regexp/timeout_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.2" do
+ describe "Regexp.timeout" do
+ after :each do
+ Regexp.timeout = nil
+ end
+
+ it "returns global timeout" do
+ Regexp.timeout = 3
+ Regexp.timeout.should == 3
+ end
+
+ it "raises Regexp::TimeoutError after global timeout elapsed" do
+ Regexp.timeout = 0.001
+ Regexp.timeout.should == 0.001
+
+ -> {
+ # A typical ReDoS case
+ /^(a*)*$/ =~ "a" * 1000000 + "x"
+ }.should raise_error(Regexp::TimeoutError, "regexp match timeout")
+ end
+
+ it "raises Regexp::TimeoutError after timeout keyword value elapsed" do
+ Regexp.timeout = 3 # This should be ignored
+ Regexp.timeout.should == 3
+
+ re = Regexp.new("^a*b?a*$", timeout: 0.001)
+
+ -> {
+ re =~ "a" * 1000000 + "x"
+ }.should raise_error(Regexp::TimeoutError, "regexp match timeout")
+ end
+ end
+end
diff --git a/spec/ruby/core/string/element_set_spec.rb b/spec/ruby/core/string/element_set_spec.rb
index 881b4343d4f27c..fa041fa31da2f9 100644
--- a/spec/ruby/core/string/element_set_spec.rb
+++ b/spec/ruby/core/string/element_set_spec.rb
@@ -357,11 +357,11 @@
end
it "raises a RangeError if negative Range begin is out of range" do
- -> { "abc"[-4..-2] = "x" }.should raise_error(RangeError)
+ -> { "abc"[-4..-2] = "x" }.should raise_error(RangeError, "-4..-2 out of range")
end
it "raises a RangeError if positive Range begin is greater than String size" do
- -> { "abc"[4..2] = "x" }.should raise_error(RangeError)
+ -> { "abc"[4..2] = "x" }.should raise_error(RangeError, "4..2 out of range")
end
it "uses the Range end as an index rather than a count" do
diff --git a/spec/ruby/core/string/fixtures/to_c.rb b/spec/ruby/core/string/fixtures/to_c.rb
new file mode 100644
index 00000000000000..77769332637515
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/to_c.rb
@@ -0,0 +1,5 @@
+module StringSpecs
+ def self.to_c_method(string)
+ string.to_c
+ end
+end
diff --git a/spec/ruby/core/string/gsub_spec.rb b/spec/ruby/core/string/gsub_spec.rb
index 3211ebbd0ac8ce..c87a56659105c0 100644
--- a/spec/ruby/core/string/gsub_spec.rb
+++ b/spec/ruby/core/string/gsub_spec.rb
@@ -210,8 +210,6 @@ def replacement.to_str() "hello_replacement" end
end
end
- # Note: $~ cannot be tested because mspec messes with it
-
it "sets $~ to MatchData of last match and nil when there's none" do
'hello.'.gsub('hello', 'x')
$~[0].should == 'hello'
@@ -225,6 +223,18 @@ def replacement.to_str() "hello_replacement" end
'hello.'.gsub(/not/, 'x')
$~.should == nil
end
+
+ it "handles a pattern in a superset encoding" do
+ result = 'abc'.force_encoding(Encoding::US_ASCII).gsub('é', 'è')
+ result.should == 'abc'
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ result = 'été'.gsub('t'.force_encoding(Encoding::US_ASCII), 'u')
+ result.should == 'éué'
+ result.encoding.should == Encoding::UTF_8
+ end
end
describe "String#gsub with pattern and Hash" do
@@ -521,6 +531,27 @@ def obj.to_s() "ok" end
-> { s.gsub!(/e/, "e") }.should raise_error(FrozenError)
-> { s.gsub!(/[aeiou]/, '*') }.should raise_error(FrozenError)
end
+
+ it "handles a pattern in a superset encoding" do
+ string = 'abc'.force_encoding(Encoding::US_ASCII)
+
+ result = string.gsub!('é', 'è')
+
+ result.should == nil
+ string.should == 'abc'
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ string = 'été'
+ pattern = 't'.force_encoding(Encoding::US_ASCII)
+
+ result = string.gsub!(pattern, 'u')
+
+ result.should == string
+ string.should == 'éué'
+ string.encoding.should == Encoding::UTF_8
+ end
end
describe "String#gsub! with pattern and block" do
diff --git a/spec/ruby/core/string/index_spec.rb b/spec/ruby/core/string/index_spec.rb
index 5d77a88e4e000d..2eeee9be8741b6 100644
--- a/spec/ruby/core/string/index_spec.rb
+++ b/spec/ruby/core/string/index_spec.rb
@@ -159,6 +159,14 @@
"あれ".index char
end.should raise_error(Encoding::CompatibilityError)
end
+
+ it "handles a substring in a superset encoding" do
+ 'abc'.force_encoding(Encoding::US_ASCII).index('é').should == nil
+ end
+
+ it "handles a substring in a subset encoding" do
+ 'été'.index('t'.force_encoding(Encoding::US_ASCII)).should == 1
+ end
end
describe "String#index with Regexp" do
diff --git a/spec/ruby/core/string/partition_spec.rb b/spec/ruby/core/string/partition_spec.rb
index 98311f2be4fdf0..9cb3672881ff02 100644
--- a/spec/ruby/core/string/partition_spec.rb
+++ b/spec/ruby/core/string/partition_spec.rb
@@ -38,4 +38,26 @@
it "takes precedence over a given block" do
"hello world".partition("o") { true }.should == ["hell", "o", " world"]
end
+
+ it "handles a pattern in a superset encoding" do
+ string = "hello".force_encoding(Encoding::US_ASCII)
+
+ result = string.partition("é")
+
+ result.should == ["hello", "", ""]
+ result[0].encoding.should == Encoding::US_ASCII
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ pattern = "o".force_encoding(Encoding::US_ASCII)
+
+ result = "héllo world".partition(pattern)
+
+ result.should == ["héll", "o", " world"]
+ result[0].encoding.should == Encoding::UTF_8
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::UTF_8
+ end
end
diff --git a/spec/ruby/core/string/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb
index a3b437a1e478c6..e795105e1da113 100644
--- a/spec/ruby/core/string/rindex_spec.rb
+++ b/spec/ruby/core/string/rindex_spec.rb
@@ -196,6 +196,14 @@ def obj.method_missing(*args) 5 end
it "raises a TypeError when given offset is nil" do
-> { "str".rindex("st", nil) }.should raise_error(TypeError)
end
+
+ it "handles a substring in a superset encoding" do
+ 'abc'.force_encoding(Encoding::US_ASCII).rindex('é').should == nil
+ end
+
+ it "handles a substring in a subset encoding" do
+ 'été'.rindex('t'.force_encoding(Encoding::US_ASCII)).should == 1
+ end
end
describe "String#rindex with Regexp" do
diff --git a/spec/ruby/core/string/rpartition_spec.rb b/spec/ruby/core/string/rpartition_spec.rb
index c8f9afaee9225a..21e87f530a8013 100644
--- a/spec/ruby/core/string/rpartition_spec.rb
+++ b/spec/ruby/core/string/rpartition_spec.rb
@@ -46,4 +46,26 @@
->{ "hello".rpartition(5) }.should raise_error(TypeError)
->{ "hello".rpartition(nil) }.should raise_error(TypeError)
end
+
+ it "handles a pattern in a superset encoding" do
+ string = "hello".force_encoding(Encoding::US_ASCII)
+
+ result = string.rpartition("é")
+
+ result.should == ["", "", "hello"]
+ result[0].encoding.should == Encoding::US_ASCII
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ pattern = "o".force_encoding(Encoding::US_ASCII)
+
+ result = "héllo world".rpartition(pattern)
+
+ result.should == ["héllo w", "o", "rld"]
+ result[0].encoding.should == Encoding::UTF_8
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::UTF_8
+ end
end
diff --git a/spec/ruby/core/string/sub_spec.rb b/spec/ruby/core/string/sub_spec.rb
index 9effe88c27891f..99dd7b45a83e06 100644
--- a/spec/ruby/core/string/sub_spec.rb
+++ b/spec/ruby/core/string/sub_spec.rb
@@ -214,6 +214,17 @@
"ababa".sub(/(b)/, '\\\\\1').should == "a\\baba"
end
+ it "handles a pattern in a superset encoding" do
+ result = 'abc'.force_encoding(Encoding::US_ASCII).sub('é', 'è')
+ result.should == 'abc'
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ result = 'été'.sub('t'.force_encoding(Encoding::US_ASCII), 'u')
+ result.should == 'éué'
+ result.encoding.should == Encoding::UTF_8
+ end
end
describe "String#sub with pattern and block" do
@@ -299,6 +310,27 @@
-> { s.sub!(/e/, "e") }.should raise_error(FrozenError)
-> { s.sub!(/[aeiou]/, '*') }.should raise_error(FrozenError)
end
+
+ it "handles a pattern in a superset encoding" do
+ string = 'abc'.force_encoding(Encoding::US_ASCII)
+
+ result = string.sub!('é', 'è')
+
+ result.should == nil
+ string.should == 'abc'
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ string = 'été'
+ pattern = 't'.force_encoding(Encoding::US_ASCII)
+
+ result = string.sub!(pattern, 'u')
+
+ result.should == string
+ string.should == 'éué'
+ string.encoding.should == Encoding::UTF_8
+ end
end
describe "String#sub! with pattern and block" do
diff --git a/spec/ruby/core/string/to_c_spec.rb b/spec/ruby/core/string/to_c_spec.rb
index 9c84b14f4d7ca5..994bdf99f661b9 100644
--- a/spec/ruby/core/string/to_c_spec.rb
+++ b/spec/ruby/core/string/to_c_spec.rb
@@ -1,99 +1,42 @@
require_relative '../../spec_helper'
+require_relative '../../shared/kernel/complex'
+require_relative 'fixtures/to_c'
describe "String#to_c" do
- it "returns a Complex object" do
- '9'.to_c.should be_an_instance_of(Complex)
- end
-
- it "understands integers" do
- '20'.to_c.should == Complex(20)
- end
-
- it "understands negative integers" do
- '-3'.to_c.should == Complex(-3)
- end
-
- it "understands fractions (numerator/denominator) for the real part" do
- '2/3'.to_c.should == Complex(Rational(2, 3))
- end
-
- it "understands fractions (numerator/denominator) for the imaginary part" do
- '4+2/3i'.to_c.should == Complex(4, Rational(2, 3))
- end
-
- it "understands negative fractions (-numerator/denominator) for the real part" do
- '-2/3'.to_c.should == Complex(Rational(-2, 3))
- end
-
- it "understands negative fractions (-numerator/denominator) for the imaginary part" do
- '7-2/3i'.to_c.should == Complex(7, Rational(-2, 3))
- end
-
- it "understands floats (a.b) for the real part" do
- '2.3'.to_c.should == Complex(2.3)
- end
-
- it "understands floats (a.b) for the imaginary part" do
- '4+2.3i'.to_c.should == Complex(4, 2.3)
- end
-
- it "understands negative floats (-a.b) for the real part" do
- '-2.33'.to_c.should == Complex(-2.33)
- end
-
- it "understands negative floats (-a.b) for the imaginary part" do
- '7-28.771i'.to_c.should == Complex(7, -28.771)
- end
-
- it "understands an integer followed by 'i' to mean that integer is the imaginary part" do
- '35i'.to_c.should == Complex(0,35)
- end
-
- it "understands a negative integer followed by 'i' to mean that negative integer is the imaginary part" do
- '-29i'.to_c.should == Complex(0,-29)
- end
-
- it "understands an 'i' by itself as denoting a complex number with an imaginary part of 1" do
- 'i'.to_c.should == Complex(0,1)
- end
-
- it "understands a '-i' by itself as denoting a complex number with an imaginary part of -1" do
- '-i'.to_c.should == Complex(0,-1)
- end
-
- it "understands 'a+bi' to mean a complex number with 'a' as the real part, 'b' as the imaginary" do
- '79+4i'.to_c.should == Complex(79,4)
- end
-
- it "understands 'a-bi' to mean a complex number with 'a' as the real part, '-b' as the imaginary" do
- '79-4i'.to_c.should == Complex(79,-4)
- end
+ it_behaves_like :kernel_complex, :to_c_method, StringSpecs
+end
- it "understands scientific notation for the real part" do
- '2e3+4i'.to_c.should == Complex(2e3,4)
+describe "String#to_c" do
+ it "returns a complex number with 0 as the real part, 0 as the imaginary part for unrecognised Strings" do
+ 'ruby'.to_c.should == Complex(0, 0)
end
- it "understands negative scientific notation for the real part" do
- '-2e3+4i'.to_c.should == Complex(-2e3,4)
+ it "ignores trailing garbage" do
+ '79+4iruby'.to_c.should == Complex(79, 4)
end
- it "understands scientific notation for the imaginary part" do
- '4+2e3i'.to_c.should == Complex(4, 2e3)
+ it "understands Float::INFINITY" do
+ 'Infinity'.to_c.should == Complex(0, 1)
+ '-Infinity'.to_c.should == Complex(0, -1)
end
- it "understands negative scientific notation for the imaginary part" do
- '4-2e3i'.to_c.should == Complex(4, -2e3)
+ it "understands Float::NAN" do
+ 'NaN'.to_c.should == Complex(0, 0)
end
- it "understands scientific notation for the real and imaginary part in the same String" do
- '2e3+2e4i'.to_c.should == Complex(2e3,2e4)
+ it "understands a sequence of _" do
+ '7__9+4__0i'.to_c.should == Complex(79, 40)
end
- it "understands negative scientific notation for the real and imaginary part in the same String" do
- '-2e3-2e4i'.to_c.should == Complex(-2e3,-2e4)
+ it "allows null-byte" do
+ "1-2i\0".to_c.should == Complex(1, -2)
+ "1\0-2i".to_c.should == Complex(1, 0)
+ "\01-2i".to_c.should == Complex(0, 0)
end
- it "returns a complex number with 0 as the real part, 0 as the imaginary part for unrecognised Strings" do
- 'ruby'.to_c.should == Complex(0,0)
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ '79+4i'.encode("UTF-16").to_c
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
end
end
diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb
index 1a838d6c7c3c20..fcabc99731f761 100644
--- a/spec/ruby/core/string/unpack/b_spec.rb
+++ b/spec/ruby/core/string/unpack/b_spec.rb
@@ -93,6 +93,11 @@
it "ignores spaces between directives" do
"\x80\x00".unpack("B B").should == ["1", "0"]
end
+
+ it "decodes into US-ASCII string values" do
+ str = "s".force_encoding('UTF-8').unpack("B*")[0]
+ str.encoding.name.should == 'US-ASCII'
+ end
end
describe "String#unpack with format 'b'" do
@@ -189,5 +194,4 @@
str = "s".force_encoding('UTF-8').unpack("b*")[0]
str.encoding.name.should == 'US-ASCII'
end
-
end
diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb
index 21134514a19cca..c551c755d16ee9 100644
--- a/spec/ruby/core/string/unpack/m_spec.rb
+++ b/spec/ruby/core/string/unpack/m_spec.rb
@@ -97,6 +97,11 @@
["=FF=\n", ["\xff"]]
].should be_computed_by(:unpack, "M")
end
+
+ it "unpacks incomplete escape sequences as literal characters" do
+ "foo=".unpack("M").should == ["foo="]
+ "foo=4".unpack("M").should == ["foo=4"]
+ end
end
describe "String#unpack with format 'm'" do
diff --git a/spec/ruby/core/struct/initialize_spec.rb b/spec/ruby/core/struct/initialize_spec.rb
index e82289071ae085..cfb302209e6f6e 100644
--- a/spec/ruby/core/struct/initialize_spec.rb
+++ b/spec/ruby/core/struct/initialize_spec.rb
@@ -40,4 +40,12 @@
it "can be overridden" do
StructClasses::SubclassX.new(:y).new.key.should == :value
end
+
+ ruby_version_is "3.1"..."3.2" do
+ it "warns about passing only keyword arguments" do
+ -> {
+ StructClasses::Ruby.new(version: "3.1", platform: "OS")
+ }.should complain(/warning: Passing only keyword arguments/)
+ end
+ end
end
diff --git a/spec/ruby/core/struct/keyword_init_spec.rb b/spec/ruby/core/struct/keyword_init_spec.rb
new file mode 100644
index 00000000000000..061f4c56e0df8a
--- /dev/null
+++ b/spec/ruby/core/struct/keyword_init_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ # See https://bugs.ruby-lang.org/issues/18008
+ describe "StructClass#keyword_init?" do
+ it "returns true for a struct that accepts keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: true)
+ struct.keyword_init?.should be_true
+ end
+
+ it "returns false for a struct that does not accept keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: false)
+ struct.keyword_init?.should be_false
+ end
+
+ it "returns nil for a struct that did not explicitly specify keyword_init" do
+ struct = Struct.new(:arg)
+ struct.keyword_init?.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/limit_spec.rb b/spec/ruby/core/thread/backtrace/limit_spec.rb
new file mode 100644
index 00000000000000..26a87a806c174d
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/limit_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "Thread::Backtrace.limit" do
+ it "returns maximum backtrace length set by --backtrace-limit command-line option" do
+ out = ruby_exe("print Thread::Backtrace.limit", options: "--backtrace-limit=2")
+ out.should == "2"
+ end
+
+ it "returns -1 when --backtrace-limit command-line option is not set" do
+ out = ruby_exe("print Thread::Backtrace.limit")
+ out.should == "-1"
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/native_thread_id_spec.rb b/spec/ruby/core/thread/native_thread_id_spec.rb
new file mode 100644
index 00000000000000..d6cc332bf6b533
--- /dev/null
+++ b/spec/ruby/core/thread/native_thread_id_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+if ruby_version_is "3.1" and Thread.method_defined?(:native_thread_id)
+ # This method is very platform specific
+
+ describe "Thread#native_thread_id" do
+ it "returns an integer when the thread is alive" do
+ Thread.current.native_thread_id.should be_kind_of(Integer)
+ end
+
+ it "returns nil when the thread is not running" do
+ t = Thread.new {}
+ t.join
+ t.native_thread_id.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb
index 2cc46ab8c966b1..74b1962a95fdca 100644
--- a/spec/ruby/core/time/at_spec.rb
+++ b/spec/ruby/core/time/at_spec.rb
@@ -266,5 +266,10 @@
time.zone.should == zone
time.to_i.should == @epoch_time
end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.at(@epoch_time, in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.at(@epoch_time, in: "ABC") }.should raise_error(ArgumentError)
+ end
end
end
diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb
index 09b4d03a44db2d..aabf28e71286e5 100644
--- a/spec/ruby/core/time/new_spec.rb
+++ b/spec/ruby/core/time/new_spec.rb
@@ -332,4 +332,55 @@ def zone.local_to_utc(t)
end
end
end
+
+ ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485
+ describe ":in keyword argument" do
+ it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: "+05:00")
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00")
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be UTC offset as a number of seconds" do
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: 5*60*60)
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: -9*60*60)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be a timezone object" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: zone)
+
+ time.utc_offset.should == 5*3600+30*60
+ time.zone.should == zone
+
+ zone = TimeSpecs::TimezoneWithName.new(name: "PST")
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: zone)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == zone
+ end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if two offset arguments are given" do
+ -> { Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") }.should raise_error(ArgumentError)
+ end
+ end
+ end
end
diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb
index 7dc79519967ab0..2b2e53a17c83ed 100644
--- a/spec/ruby/core/time/now_spec.rb
+++ b/spec/ruby/core/time/now_spec.rb
@@ -3,4 +3,49 @@
describe "Time.now" do
it_behaves_like :time_now, :now
+
+ describe ":in keyword argument" do
+ it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
+ time = Time.now(in: "+05:00")
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.now(in: "-09:00")
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be UTC offset as a number of seconds" do
+ time = Time.now(in: 5*60*60)
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.now(in: -9*60*60)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be a timezone object" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.now(in: zone)
+
+ time.utc_offset.should == 5*3600+30*60
+ time.zone.should == zone
+
+ zone = TimeSpecs::TimezoneWithName.new(name: "PST")
+ time = Time.now(in: zone)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == zone
+ end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.now(in: "ABC") }.should raise_error(ArgumentError)
+ end
+ end
end
diff --git a/spec/ruby/core/time/shared/local.rb b/spec/ruby/core/time/shared/local.rb
index c4aa7a7ea9b4d2..068e31499911a6 100644
--- a/spec/ruby/core/time/shared/local.rb
+++ b/spec/ruby/core/time/shared/local.rb
@@ -7,12 +7,10 @@
end
platform_is_not :windows do
- describe "timezone changes" do
- it "correctly adjusts the timezone change to 'CET' on 'Europe/Amsterdam'" do
- with_timezone("Europe/Amsterdam") do
- Time.send(@method, 1970, 5, 16).to_a.should ==
- [0, 0, 0, 16, 5, 1970, 6, 136, false, "CET"]
- end
+ it "uses the 'CET' timezone with TZ=Europe/Amsterdam in 1970" do
+ with_timezone("Europe/Amsterdam") do
+ Time.send(@method, 1970, 5, 16).to_a.should ==
+ [0, 0, 0, 16, 5, 1970, 6, 136, false, "CET"]
end
end
end
@@ -41,5 +39,4 @@
end
end
end
-
end
diff --git a/spec/ruby/core/time/strftime_spec.rb b/spec/ruby/core/time/strftime_spec.rb
index 1bd24b05385edd..c133e220082626 100644
--- a/spec/ruby/core/time/strftime_spec.rb
+++ b/spec/ruby/core/time/strftime_spec.rb
@@ -49,4 +49,13 @@
time = @new_time_with_offset[2012, 1, 1, 0, 0, 0, Rational(36645, 10)]
time.strftime("%::z").should == "+01:01:05"
end
+
+ ruby_version_is "3.1" do
+ it "supports RFC 3339 UTC for unknown offset local time, -0000, as %-z" do
+ @time.strftime("%z").should == "+0000"
+ @time.strftime("%-z").should == "-0000"
+ @time.strftime("%-:z").should == "-00:00"
+ @time.strftime("%-::z").should == "-00:00:00"
+ end
+ end
end
diff --git a/spec/ruby/core/tracepoint/allow_reentry_spec.rb b/spec/ruby/core/tracepoint/allow_reentry_spec.rb
new file mode 100644
index 00000000000000..6bff1bed7649ff
--- /dev/null
+++ b/spec/ruby/core/tracepoint/allow_reentry_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1" do
+ describe 'TracePoint.allow_reentry' do
+ it 'allows the reentrance in a given block' do
+ event_lines = []
+ l1 = l2 = l3 = l4 = nil
+ TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+
+ event_lines << tp.lineno
+ next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno)
+ TracePoint.allow_reentry do
+ a = 1; l3 = __LINE__
+ b = 2; l4 = __LINE__
+ end
+ end.enable do
+ c = 3; l1 = __LINE__
+ d = 4; l2 = __LINE__
+ end
+
+ event_lines.should == [l1, l3, l4, l2, l3, l4]
+ end
+
+ it 'raises RuntimeError when not called inside a TracePoint' do
+ -> {
+ TracePoint.allow_reentry{}
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/fixtures/classes.rb b/spec/ruby/core/unboundmethod/fixtures/classes.rb
index 1f466e39d86e8d..6ab958d447b9cc 100644
--- a/spec/ruby/core/unboundmethod/fixtures/classes.rb
+++ b/spec/ruby/core/unboundmethod/fixtures/classes.rb
@@ -53,6 +53,12 @@ def neg_four(a, b, *c, &d); end
def discard_1(); :discard; end
def discard_2(); :discard; end
+
+ def my_public_method; end
+ def my_protected_method; end
+ def my_private_method; end
+ protected :my_protected_method
+ private :my_private_method
end
class Parent
diff --git a/spec/ruby/core/unboundmethod/private_spec.rb b/spec/ruby/core/unboundmethod/private_spec.rb
new file mode 100644
index 00000000000000..fa735846bbac3c
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/private_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#private?" do
+ it "returns false when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.private?.should == false
+ end
+
+ it "returns false when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.private?.should == false
+ end
+
+ it "returns true when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.private?.should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/protected_spec.rb b/spec/ruby/core/unboundmethod/protected_spec.rb
new file mode 100644
index 00000000000000..db00e7ef43b45f
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/protected_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#protected?" do
+ it "returns false when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.protected?.should == false
+ end
+
+ it "returns true when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.protected?.should == true
+ end
+
+ it "returns false when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.protected?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/public_spec.rb b/spec/ruby/core/unboundmethod/public_spec.rb
new file mode 100644
index 00000000000000..7b87a03b1584a2
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/public_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#public?" do
+ it "returns true when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.public?.should == true
+ end
+
+ it "returns false when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.public?.should == false
+ end
+
+ it "returns false when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.public?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb
index aa7c1293772782..101c83b8b333eb 100644
--- a/spec/ruby/core/unboundmethod/super_method_spec.rb
+++ b/spec/ruby/core/unboundmethod/super_method_spec.rb
@@ -40,10 +40,12 @@
end
end
- context "after aliasing an inherited method" do
- it "returns the expected super_method" do
- method = MethodSpecs::InheritedMethods::C.instance_method(:meow)
- method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ ruby_version_is "2.7.3" do
+ context "after aliasing an inherited method" do
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.instance_method(:meow)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
end
end
end
diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb
index d918c12beb2ec9..8488b945d5df3e 100644
--- a/spec/ruby/language/block_spec.rb
+++ b/spec/ruby/language/block_spec.rb
@@ -263,12 +263,55 @@ def m(a) yield a end
m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
end
+ it "receives the object if it does not respond to #to_ary" do
+ obj = Object.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #respond_to? to check if object has method #to_ary" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true)
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "receives the object if it does not respond to #respond_to?" do
+ obj = BasicObject.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #to_ary on the object when it is defined dynamically" do
+ obj = Object.new
+ def obj.method_missing(name, *args, &block)
+ if name == :to_ary
+ [1, 2]
+ else
+ super
+ end
+ end
+ def obj.respond_to_missing?(name, include_private)
+ name == :to_ary
+ end
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
it "raises a TypeError if #to_ary does not return an Array" do
obj = mock("destructure block arguments")
obj.should_receive(:to_ary).and_return(1)
-> { m(obj) { |a, b| } }.should raise_error(TypeError)
end
+
+ it "raises error transparently if #to_ary raises error on its own" do
+ obj = Object.new
+ def obj.to_ary; raise "Exception raised in #to_ary" end
+
+ -> { m(obj) { |a, b| } }.should raise_error(RuntimeError, "Exception raised in #to_ary")
+ end
end
end
diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb
index 8771c5806c0c36..c47b7b0ae95145 100644
--- a/spec/ruby/language/keyword_arguments_spec.rb
+++ b/spec/ruby/language/keyword_arguments_spec.rb
@@ -321,6 +321,21 @@ def m(*args)
m({a: 1}).should == [[{a: 1}], {}]
end
+ ruby_version_is "3.1" do
+ describe "omitted values" do
+ it "accepts short notation 'key' for 'key: value' syntax" do
+ def m(a:, b:)
+ [a, b]
+ end
+
+ a = 1
+ b = 2
+
+ eval('m(a:, b:).should == [1, 2]')
+ end
+ end
+ end
+
ruby_version_is "3.2" do
it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do
class << self
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb
index acca074974743e..b80b314f6f40e4 100644
--- a/spec/ruby/language/method_spec.rb
+++ b/spec/ruby/language/method_spec.rb
@@ -1679,6 +1679,15 @@ def m() = 42
m.should == 42
end
+
+ context "without parenthesis" do
+ evaluate <<-ruby do
+ def m = 42
+ ruby
+
+ m.should == 42
+ end
+ end
end
context "with arguments" do
@@ -1716,6 +1725,16 @@ def m(...) = mm(...) + mm(...)
m("meow", num: 2).should == "meow" * 4
end
end
+
+ ruby_version_is ""..."3.0" do
+ context "inside 'endless' method definitions" do
+ it "does not allow method calls without parenthesis" do
+ -> {
+ eval("def greet(person) = 'Hi, '.concat person")
+ }.should raise_error(SyntaxError)
+ end
+ end
+ end
end
describe "Keyword arguments are now separated from positional arguments" do
@@ -1824,4 +1843,14 @@ def foo(val)
end
end
end
+
+ describe "Inside 'endless' method definitions" do
+ it "allows method calls without parenthesis" do
+ eval <<-ruby
+ def greet(person) = "Hi, ".concat person
+ ruby
+
+ greet("Homer").should == "Hi, Homer"
+ end
+ end
end
diff --git a/spec/ruby/library/cmath/math/acos_spec.rb b/spec/ruby/library/cmath/math/acos_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/acos_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/acosh_spec.rb b/spec/ruby/library/cmath/math/acosh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/acosh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/asin_spec.rb b/spec/ruby/library/cmath/math/asin_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/asin_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/asinh_spec.rb b/spec/ruby/library/cmath/math/asinh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/asinh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/atan2_spec.rb b/spec/ruby/library/cmath/math/atan2_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/atan2_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/atan_spec.rb b/spec/ruby/library/cmath/math/atan_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/atan_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/atanh_spec.rb b/spec/ruby/library/cmath/math/atanh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/atanh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/cos_spec.rb b/spec/ruby/library/cmath/math/cos_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/cos_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/cosh_spec.rb b/spec/ruby/library/cmath/math/cosh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/cosh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/exp_spec.rb b/spec/ruby/library/cmath/math/exp_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/exp_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/fixtures/classes.rb b/spec/ruby/library/cmath/math/fixtures/classes.rb
deleted file mode 100644
index 443c1a9ace3007..00000000000000
--- a/spec/ruby/library/cmath/math/fixtures/classes.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'cmath'
-class IncludesMath
- include CMath
-end
diff --git a/spec/ruby/library/cmath/math/log10_spec.rb b/spec/ruby/library/cmath/math/log10_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/log10_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/log_spec.rb b/spec/ruby/library/cmath/math/log_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/log_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/shared/acos.rb b/spec/ruby/library/cmath/math/shared/acos.rb
deleted file mode 100644
index 65637fa838d188..00000000000000
--- a/spec/ruby/library/cmath/math/shared/acos.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_acos, shared: true do
- it "returns the arccosine of the passed argument" do
- @object.send(:acos, 1).should be_close(0.0, TOLERANCE)
- @object.send(:acos, 0).should be_close(1.5707963267949, TOLERANCE)
- @object.send(:acos, -1).should be_close(Math::PI,TOLERANCE)
- end
-
- it "returns the arccosine for Complex numbers" do
- @object.send(:acos, Complex(3, 4)).should be_close(Complex(0.93681246115572, -2.30550903124348), TOLERANCE)
- end
-
- it "returns the arccosine for numbers greater than 1.0 as a Complex number" do
- @object.send(:acos, 1.0001).should be_close(Complex(0.0, 0.0141420177752494), TOLERANCE)
- end
-
- it "returns the arccosine for numbers less than -1.0 as a Complex number" do
- @object.send(:acos, -1.0001).should be_close(Complex(3.14159265358979, -0.0141420177752495), TOLERANCE)
- end
-end
-
-describe :complex_math_acos_bang, shared: true do
- it "returns the arccosine of the argument" do
- @object.send(:acos!, 1).should be_close(0.0, TOLERANCE)
- @object.send(:acos!, 0).should be_close(1.5707963267949, TOLERANCE)
- @object.send(:acos!, -1).should be_close(Math::PI,TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:acos!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-
- it "raises an Errno::EDOM for numbers greater than 1.0" do
- -> { @object.send(:acos!, 1.0001) }.should raise_error(Errno::EDOM)
- end
-
- it "raises an Errno::EDOM for numbers less than -1.0" do
- -> { @object.send(:acos!, -1.0001) }.should raise_error(Errno::EDOM)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/acosh.rb b/spec/ruby/library/cmath/math/shared/acosh.rb
deleted file mode 100644
index 285b0b823f6fee..00000000000000
--- a/spec/ruby/library/cmath/math/shared/acosh.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_acosh, shared: true do
- it "returns the principle value of the inverse hyperbolic cosine of the argument" do
- @object.send(:acosh, 14.2).should be_close(3.345146999647, TOLERANCE)
- @object.send(:acosh, 1.0).should be_close(0.0, TOLERANCE)
- end
-
- it "returns the principle value of the inverse hyperbolic cosine for numbers less than 1.0 as a Complex number" do
- @object.send(:acosh, 1.0 - TOLERANCE).should be_close(Complex(0.0, 0.00774598605746135), TOLERANCE)
- @object.send(:acosh, 0).should be_close(Complex(0.0, 1.5707963267949), TOLERANCE)
- @object.send(:acosh, -1.0).should be_close(Complex(0.0, 3.14159265358979), TOLERANCE)
- end
-
- it "returns the principle value of the inverse hyperbolic cosine for Complex numbers" do
- @object.send(:acosh, Complex(3, 4))
- @object.send(:acosh, Complex(3, 4)).imaginary.should be_close(0.93681246115572, TOLERANCE)
- @object.send(:acosh, Complex(3, 4)).real.should be_close(2.305509031243477, TOLERANCE)
- end
-end
-
-describe :complex_math_acosh_bang, shared: true do
- it "returns the principle value of the inverse hyperbolic cosine of the argument" do
- @object.send(:acosh!, 14.2).should be_close(3.345146999647, TOLERANCE)
- @object.send(:acosh!, 1.0).should be_close(0.0, TOLERANCE)
- end
-
- it "raises Errno::EDOM for numbers less than 1.0" do
- -> { @object.send(:acosh!, 1.0 - TOLERANCE) }.should raise_error(Errno::EDOM)
- -> { @object.send(:acosh!, 0) }.should raise_error(Errno::EDOM)
- -> { @object.send(:acosh!, -1.0) }.should raise_error(Errno::EDOM)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:acosh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/asin.rb b/spec/ruby/library/cmath/math/shared/asin.rb
deleted file mode 100644
index 91fed7aa06903f..00000000000000
--- a/spec/ruby/library/cmath/math/shared/asin.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_asin, shared: true do
- it "returns the arcsine of the argument" do
- @object.send(:asin, 1).should be_close(Math::PI/2, TOLERANCE)
- @object.send(:asin, 0).should be_close(0.0, TOLERANCE)
- @object.send(:asin, -1).should be_close(-Math::PI/2, TOLERANCE)
- @object.send(:asin, 0.25).should be_close(0.252680255142079, TOLERANCE)
- @object.send(:asin, 0.50).should be_close(0.523598775598299, TOLERANCE)
- @object.send(:asin, 0.75).should be_close(0.8480620789814816,TOLERANCE)
- end
-
- it "returns the arcsine for Complex numbers" do
- @object.send(:asin, Complex(3, 4)).should be_close(Complex(0.633983865639174, 2.30550903124347), TOLERANCE)
- end
-
- it "returns a Complex number when the argument is greater than 1.0" do
- @object.send(:asin, 1.0001).should be_close(Complex(1.5707963267949, -0.0141420177752494), TOLERANCE)
- end
-
- it "returns a Complex number when the argument is less than -1.0" do
- @object.send(:asin, -1.0001).should be_close(Complex(-1.5707963267949, 0.0141420177752494), TOLERANCE)
- end
-end
-
-describe :complex_math_asin_bang, shared: true do
- it "returns the arcsine of the argument" do
- @object.send(:asin!, 1).should be_close(Math::PI/2, TOLERANCE)
- @object.send(:asin!, 0).should be_close(0.0, TOLERANCE)
- @object.send(:asin!, -1).should be_close(-Math::PI/2, TOLERANCE)
- @object.send(:asin!, 0.25).should be_close(0.252680255142079, TOLERANCE)
- @object.send(:asin!, 0.50).should be_close(0.523598775598299, TOLERANCE)
- @object.send(:asin!, 0.75).should be_close(0.8480620789814816,TOLERANCE)
- end
-
- it "raises an Errno::EDOM if the argument is greater than 1.0" do
- -> { @object.send(:asin!, 1.0001) }.should raise_error( Errno::EDOM)
- end
-
- it "raises an Errno::EDOM if the argument is less than -1.0" do
- -> { @object.send(:asin!, -1.0001) }.should raise_error( Errno::EDOM)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:asin!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/asinh.rb b/spec/ruby/library/cmath/math/shared/asinh.rb
deleted file mode 100644
index b4ddd3a22ea6a9..00000000000000
--- a/spec/ruby/library/cmath/math/shared/asinh.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_asinh, shared: true do
- it "returns the inverse hyperbolic sin of the argument" do
- @object.send(:asinh, 1.5).should be_close(1.19476321728711, TOLERANCE)
- @object.send(:asinh, -2.97).should be_close(-1.8089166921397, TOLERANCE)
- @object.send(:asinh, 0.0).should == 0.0
- @object.send(:asinh, -0.0).should == -0.0
- @object.send(:asinh, 1.05367e-08).should be_close(1.05367e-08, TOLERANCE)
- @object.send(:asinh, -1.05367e-08).should be_close(-1.05367e-08, TOLERANCE)
- end
-
- it "returns the inverse hyperbolic sin for Complex numbers" do
- @object.send(:asinh, Complex(3, 4)).should be_close(Complex(2.29991404087927, 0.917616853351479), TOLERANCE)
- @object.send(:asinh, Complex(3.5, -4)).should be_close(Complex(2.36263337274419, -0.843166327537659), TOLERANCE)
- end
-end
-
-describe :complex_math_asinh_bang, shared: true do
- it "returns the inverse hyperbolic sin of the argument" do
- @object.send(:asinh!, 1.5).should be_close(1.19476321728711, TOLERANCE)
- @object.send(:asinh!, -2.97).should be_close(-1.8089166921397, TOLERANCE)
- @object.send(:asinh!, 0.0).should == 0.0
- @object.send(:asinh!, -0.0).should == -0.0
- @object.send(:asinh!, 1.05367e-08).should be_close(1.05367e-08, TOLERANCE)
- @object.send(:asinh!, -1.05367e-08).should be_close(-1.05367e-08, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:asinh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/atan.rb b/spec/ruby/library/cmath/math/shared/atan.rb
deleted file mode 100644
index 63a496e841cd6d..00000000000000
--- a/spec/ruby/library/cmath/math/shared/atan.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_atan, shared: true do
- it "returns the arctangent of the argument" do
- @object.send(:atan, 1).should be_close(Math::PI/4, TOLERANCE)
- @object.send(:atan, 0).should be_close(0.0, TOLERANCE)
- @object.send(:atan, -1).should be_close(-Math::PI/4, TOLERANCE)
- @object.send(:atan, 0.25).should be_close(0.244978663126864, TOLERANCE)
- @object.send(:atan, 0.50).should be_close(0.463647609000806, TOLERANCE)
- @object.send(:atan, 0.75).should be_close(0.643501108793284, TOLERANCE)
- end
-
- it "returns the arctangent for Complex numbers" do
- @object.send(:atan, Complex(3, 4)).should be_close(Complex(1.44830699523146, 0.158997191679999), TOLERANCE)
- @object.send(:atan, Complex(3.5, -4)).should be_close(Complex(1.44507428165589, -0.140323762363786), TOLERANCE)
- end
-end
-
-describe :complex_math_atan_bang, shared: true do
- it "returns the arctangent of the argument" do
- @object.send(:atan!, 1).should be_close(Math::PI/4, TOLERANCE)
- @object.send(:atan!, 0).should be_close(0.0, TOLERANCE)
- @object.send(:atan!, -1).should be_close(-Math::PI/4, TOLERANCE)
- @object.send(:atan!, 0.25).should be_close(0.244978663126864, TOLERANCE)
- @object.send(:atan!, 0.50).should be_close(0.463647609000806, TOLERANCE)
- @object.send(:atan!, 0.75).should be_close(0.643501108793284, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:atan!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/atan2.rb b/spec/ruby/library/cmath/math/shared/atan2.rb
deleted file mode 100644
index 6d89423924f252..00000000000000
--- a/spec/ruby/library/cmath/math/shared/atan2.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_atan2, shared: true do
- it "returns the arc tangent of the passed arguments" do
- @object.send(:atan2, 4.2, 0.3).should be_close(1.49948886200961, TOLERANCE)
- @object.send(:atan2, 0.0, 1.0).should be_close(0.0, TOLERANCE)
- @object.send(:atan2, -9.1, 3.2).should be_close(-1.23265379809025, TOLERANCE)
- @object.send(:atan2, 7.22, -3.3).should be_close(1.99950888779256, TOLERANCE)
- end
-
- it "returns the arc tangent for two Complex numbers" do
- CMath.atan2(Complex(3, 4), Complex(3.5, -4)).should be_close(Complex(-0.641757436698881, 1.10829873031207), TOLERANCE)
- end
-
- it "returns the arc tangent for Complex and real numbers" do
- CMath.atan2(Complex(3, 4), -7).should be_close(Complex(2.61576754731561, -0.494290673139855), TOLERANCE)
- CMath.atan2(5, Complex(3.5, -4)).should be_close(Complex(0.739102348493673, 0.487821626522923), TOLERANCE)
- end
-end
-
-describe :complex_math_atan2_bang, shared: true do
- it "returns the arc tangent of the passed arguments" do
- @object.send(:atan2!, 4.2, 0.3).should be_close(1.49948886200961, TOLERANCE)
- @object.send(:atan2!, 0.0, 1.0).should be_close(0.0, TOLERANCE)
- @object.send(:atan2!, -9.1, 3.2).should be_close(-1.23265379809025, TOLERANCE)
- @object.send(:atan2!, 7.22, -3.3).should be_close(1.99950888779256, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:atan2!, Complex(4, 5), Complex(4, 5)) }.should raise_error(TypeError)
- -> { @object.send(:atan2!, 4, Complex(4, 5)) }.should raise_error(TypeError)
- -> { @object.send(:atan2!, Complex(4, 5), 5) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/atanh.rb b/spec/ruby/library/cmath/math/shared/atanh.rb
deleted file mode 100644
index ae80e61bec9ade..00000000000000
--- a/spec/ruby/library/cmath/math/shared/atanh.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_atanh_complex, shared: true do
- it "returns the inverse hyperbolic tangent as a Complex number for arguments greater than 1.0" do
- value = Complex(18.36840028483855, 1.5707963267948966)
- @object.send(@method, 1.0 + Float::EPSILON).should be_close(value, TOLERANCE)
-
- value = Complex(0.100335347731076, 1.5707963267949)
- @object.send(@method, 10).should be_close(value, TOLERANCE)
- end
-
- it "returns the inverse hyperbolic tangent as a Complex number for arguments greater than 1.0" do
- value = Complex(-18.36840028483855, 1.5707963267948966)
- @object.send(@method, -1.0 - Float::EPSILON).should be_close(value, TOLERANCE)
-
- value = Complex(0.100335347731076, 1.5707963267949)
- @object.send(@method, 10).should be_close(value, TOLERANCE)
- end
-
- it "returns the inverse hyperbolic tangent for Complex numbers" do
- value = Complex(0.117500907311434, 1.40992104959658)
- @object.send(@method, Complex(3, 4)).should be_close(value, TOLERANCE)
- end
-end
-
-describe :complex_math_atanh_no_complex, shared: true do
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:atanh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/cos.rb b/spec/ruby/library/cmath/math/shared/cos.rb
deleted file mode 100644
index 31cb5ab1e52dce..00000000000000
--- a/spec/ruby/library/cmath/math/shared/cos.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_cos, shared: true do
- it "returns the cosine of the argument expressed in radians" do
- @object.send(:cos, CMath::PI).should be_close(-1.0, TOLERANCE)
- @object.send(:cos, 0).should be_close(1.0, TOLERANCE)
- @object.send(:cos, CMath::PI/2).should be_close(0.0, TOLERANCE)
- @object.send(:cos, 3*Math::PI/2).should be_close(0.0, TOLERANCE)
- @object.send(:cos, 2*Math::PI).should be_close(1.0, TOLERANCE)
- end
-
- it "returns the cosine for Complex numbers" do
- @object.send(:cos, Complex(0, CMath::PI)).should be_close(Complex(11.5919532755215, 0.0), TOLERANCE)
- @object.send(:cos, Complex(3, 4)).should be_close(Complex(-27.0349456030742, -3.85115333481178), TOLERANCE)
- end
-end
-
-describe :complex_math_cos_bang, shared: true do
- it "returns the cosine of the argument expressed in radians" do
- @object.send(:cos!, CMath::PI).should be_close(-1.0, TOLERANCE)
- @object.send(:cos!, 0).should be_close(1.0, TOLERANCE)
- @object.send(:cos!, CMath::PI/2).should be_close(0.0, TOLERANCE)
- @object.send(:cos!, 3*Math::PI/2).should be_close(0.0, TOLERANCE)
- @object.send(:cos!, 2*Math::PI).should be_close(1.0, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:cos!, Complex(3, 4)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/cosh.rb b/spec/ruby/library/cmath/math/shared/cosh.rb
deleted file mode 100644
index 7cf561c9851ad7..00000000000000
--- a/spec/ruby/library/cmath/math/shared/cosh.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_cosh, shared: true do
- it "returns the hyperbolic cosine of the passed argument" do
- @object.send(:cosh, 0.0).should == 1.0
- @object.send(:cosh, -0.0).should == 1.0
- @object.send(:cosh, 1.5).should be_close(2.35240961524325, TOLERANCE)
- @object.send(:cosh, -2.99).should be_close(9.96798496414416, TOLERANCE)
- end
-
- it "returns the hyperbolic cosine for Complex numbers" do
- @object.send(:cosh, Complex(0, CMath::PI)).should be_close(Complex(-1.0, 0.0), TOLERANCE)
- @object.send(:cosh, Complex(3, 4)).should be_close(Complex(-6.58066304055116, -7.58155274274654), TOLERANCE)
- end
-end
-
-describe :complex_math_cosh_bang, shared: true do
- it "returns the hyperbolic cosine of the passed argument" do
- @object.send(:cosh!, 0.0).should == 1.0
- @object.send(:cosh!, -0.0).should == 1.0
- @object.send(:cosh!, 1.5).should be_close(2.35240961524325, TOLERANCE)
- @object.send(:cosh!, -2.99).should be_close(9.96798496414416, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:cosh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/exp.rb b/spec/ruby/library/cmath/math/shared/exp.rb
deleted file mode 100644
index 6715ac63d32ba5..00000000000000
--- a/spec/ruby/library/cmath/math/shared/exp.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_exp, shared: true do
- it "returns the base-e exponential of the passed argument" do
- @object.send(:exp, 0.0).should == 1.0
- @object.send(:exp, -0.0).should == 1.0
- @object.send(:exp, -1.8).should be_close(0.165298888221587, TOLERANCE)
- @object.send(:exp, 1.25).should be_close(3.49034295746184, TOLERANCE)
- end
-
- it "returns the base-e exponential for Complex numbers" do
- @object.send(:exp, Complex(0, 0)).should == Complex(1.0, 0.0)
- @object.send(:exp, Complex(1, 3)).should be_close(Complex(-2.69107861381979, 0.383603953541131), TOLERANCE)
- end
-end
-
-describe :complex_math_exp_bang, shared: true do
- it "returns the base-e exponential of the passed argument" do
- @object.send(:exp!, 0.0).should == 1.0
- @object.send(:exp!, -0.0).should == 1.0
- @object.send(:exp!, -1.8).should be_close(0.165298888221587, TOLERANCE)
- @object.send(:exp!, 1.25).should be_close(3.49034295746184, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:exp!, Complex(1, 3)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/log.rb b/spec/ruby/library/cmath/math/shared/log.rb
deleted file mode 100644
index 4b23e8c5f2f6dc..00000000000000
--- a/spec/ruby/library/cmath/math/shared/log.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_log, shared: true do
- it "returns the natural logarithm of the passed argument" do
- @object.send(:log, 0.0001).should be_close(-9.21034037197618, TOLERANCE)
- @object.send(:log, 0.000000000001e-15).should be_close(-62.1697975108392, TOLERANCE)
- @object.send(:log, 1).should be_close(0.0, TOLERANCE)
- @object.send(:log, 10).should be_close( 2.30258509299405, TOLERANCE)
- @object.send(:log, 10e15).should be_close(36.8413614879047, TOLERANCE)
- end
-
- it "returns the natural logarithm for Complex numbers" do
- @object.send(:log, Complex(3, 4)).should be_close(Complex(1.6094379124341, 0.927295218001612), TOLERANCE)
- @object.send(:log, Complex(-3, 4)).should be_close(Complex(1.6094379124341, 2.21429743558818), TOLERANCE)
- end
-
- it "returns the natural logarithm for negative numbers as a Complex number" do
- @object.send(:log, -10).should be_close(Complex(2.30258509299405, 3.14159265358979), TOLERANCE)
- @object.send(:log, -20).should be_close(Complex(2.99573227355399, 3.14159265358979), TOLERANCE)
- end
-end
-
-describe :complex_math_log_bang, shared: true do
- it "returns the natural logarithm of the argument" do
- @object.send(:log!, 0.0001).should be_close(-9.21034037197618, TOLERANCE)
- @object.send(:log!, 0.000000000001e-15).should be_close(-62.1697975108392, TOLERANCE)
- @object.send(:log!, 1).should be_close(0.0, TOLERANCE)
- @object.send(:log!, 10).should be_close( 2.30258509299405, TOLERANCE)
- @object.send(:log!, 10e15).should be_close(36.8413614879047, TOLERANCE)
- end
-
- it "raises an Errno::EDOM if the argument is less than 0" do
- -> { @object.send(:log!, -10) }.should raise_error(Errno::EDOM)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:log!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/log10.rb b/spec/ruby/library/cmath/math/shared/log10.rb
deleted file mode 100644
index f49934d958224d..00000000000000
--- a/spec/ruby/library/cmath/math/shared/log10.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_log10, shared: true do
- it "returns the base-10 logarithm of the passed argument" do
- @object.send(:log10, 0.0001).should be_close(-4.0, TOLERANCE)
- @object.send(:log10, 0.000000000001e-15).should be_close(-27.0, TOLERANCE)
- @object.send(:log10, 1).should be_close(0.0, TOLERANCE)
- @object.send(:log10, 10).should be_close(1.0, TOLERANCE)
- @object.send(:log10, 10e15).should be_close(16.0, TOLERANCE)
- end
-
- it "returns the base-10 logarithm for Complex numbers" do
- @object.send(:log10, Complex(3, 4)).should be_close(Complex(0.698970004336019, 0.402719196273373), TOLERANCE)
- @object.send(:log10, Complex(-3, 4)).should be_close(Complex(0.698970004336019, 0.961657157568468), TOLERANCE)
- end
-
- # BUG: does not work correctly, because Math#log10
- # does not check for negative values
- #it "returns the base-10 logarithm for negative numbers as a Complex number" do
- # @object.send(:log10, -10).should be_close(Complex(2.30258509299405, 3.14159265358979), TOLERANCE)
- # @object.send(:log10, -20).should be_close(Complex(2.99573227355399, 3.14159265358979), TOLERANCE)
- #end
-end
-
-describe :complex_math_log10_bang, shared: true do
- it "returns the base-10 logarithm of the argument" do
- @object.send(:log10!, 0.0001).should be_close(-4.0, TOLERANCE)
- @object.send(:log10!, 0.000000000001e-15).should be_close(-27.0, TOLERANCE)
- @object.send(:log10!, 1).should be_close(0.0, TOLERANCE)
- @object.send(:log10!, 10).should be_close(1.0, TOLERANCE)
- @object.send(:log10!, 10e15).should be_close(16.0, TOLERANCE)
- end
-
- it "raises an Errno::EDOM when the passed argument is negative" do
- -> { @object.send(:log10!, -10) }.should raise_error(Errno::EDOM)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:log10!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/sin.rb b/spec/ruby/library/cmath/math/shared/sin.rb
deleted file mode 100644
index 1cb1b29cda94ea..00000000000000
--- a/spec/ruby/library/cmath/math/shared/sin.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_sin, shared: true do
- it "returns the sine of the passed argument expressed in radians" do
- @object.send(:sin, CMath::PI).should be_close(0.0, TOLERANCE)
- @object.send(:sin, 0).should be_close(0.0, TOLERANCE)
- @object.send(:sin, CMath::PI/2).should be_close(1.0, TOLERANCE)
- @object.send(:sin, 3*Math::PI/2).should be_close(-1.0, TOLERANCE)
- @object.send(:sin, 2*Math::PI).should be_close(0.0, TOLERANCE)
- end
-
- it "returns the sine for Complex numbers" do
- @object.send(:sin, Complex(0, CMath::PI)).should be_close(Complex(0.0, 11.5487393572577), TOLERANCE)
- @object.send(:sin, Complex(3, 4)).should be_close(Complex(3.85373803791938, -27.0168132580039), TOLERANCE)
- end
-end
-
-describe :complex_math_sin_bang, shared: true do
- it "returns the sine of the passed argument expressed in radians" do
- @object.send(:sin!, CMath::PI).should be_close(0.0, TOLERANCE)
- @object.send(:sin!, 0).should be_close(0.0, TOLERANCE)
- @object.send(:sin!, CMath::PI/2).should be_close(1.0, TOLERANCE)
- @object.send(:sin!, 3*Math::PI/2).should be_close(-1.0, TOLERANCE)
- @object.send(:sin!, 2*Math::PI).should be_close(0.0, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:sin!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/sinh.rb b/spec/ruby/library/cmath/math/shared/sinh.rb
deleted file mode 100644
index de80a376da0995..00000000000000
--- a/spec/ruby/library/cmath/math/shared/sinh.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_sinh, shared: true do
- it "returns the hyperbolic sin of the argument" do
- @object.send(:sinh, 0.0).should == 0.0
- @object.send(:sinh, -0.0).should == 0.0
- @object.send(:sinh, 1.5).should be_close(2.12927945509482, TOLERANCE)
- @object.send(:sinh, -2.8).should be_close(-8.19191835423591, TOLERANCE)
- end
-
- it "returns the hyperbolic sin for Complex numbers" do
- @object.send(:sinh, Complex(0, CMath::PI)).should be_close(Complex(-0.0, 1.22464679914735e-16), TOLERANCE)
- @object.send(:sinh, Complex(3, 4)).should be_close(Complex(-6.548120040911, -7.61923172032141), TOLERANCE)
- end
-end
-
-describe :complex_math_sinh_bang, shared: true do
- it "returns the hyperbolic sin of the argument" do
- @object.send(:sinh!, 0.0).should == 0.0
- @object.send(:sinh!, -0.0).should == 0.0
- @object.send(:sinh!, 1.5).should be_close(2.12927945509482, TOLERANCE)
- @object.send(:sinh!, -2.8).should be_close(-8.19191835423591, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:sinh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/sqrt.rb b/spec/ruby/library/cmath/math/shared/sqrt.rb
deleted file mode 100644
index 23b1ba48ffef75..00000000000000
--- a/spec/ruby/library/cmath/math/shared/sqrt.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_sqrt, shared: true do
- it "returns the square root for positive numbers" do
- @object.send(:sqrt, 4).should == 2
- @object.send(:sqrt, 19.36).should == 4.4
- end
-
- it "returns the square root for negative numbers" do
- @object.send(:sqrt, -4).should == Complex(0, 2.0)
- @object.send(:sqrt, -19.36).should == Complex(0, 4.4)
- end
-
- it "returns the square root for Complex numbers" do
- @object.send(:sqrt, Complex(4, 5)).should be_close(Complex(2.2806933416653, 1.09615788950152), TOLERANCE)
- @object.send(:sqrt, Complex(4, -5)).should be_close(Complex(2.2806933416653, -1.09615788950152), TOLERANCE)
- end
-end
-
-describe :complex_math_sqrt_bang, shared: true do
- it "returns the square root for positive numbers" do
- @object.send(:sqrt!, 4).should == 2
- @object.send(:sqrt!, 19.36).should == 4.4
- end
-
- it "raises Errno::EDOM when the passed argument is negative" do
- -> { @object.send(:sqrt!, -4) }.should raise_error(Errno::EDOM)
- -> { @object.send(:sqrt!, -19.36) }.should raise_error(Errno::EDOM)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:sqrt!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/tan.rb b/spec/ruby/library/cmath/math/shared/tan.rb
deleted file mode 100644
index 9022c84fc9b384..00000000000000
--- a/spec/ruby/library/cmath/math/shared/tan.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_tan, shared: true do
- it "returns the tangent of the argument" do
- @object.send(:tan, 0.0).should == 0.0
- @object.send(:tan, -0.0).should == -0.0
- @object.send(:tan, 4.22).should be_close(1.86406937682395, TOLERANCE)
- @object.send(:tan, -9.65).should be_close(-0.229109052606441, TOLERANCE)
- end
-
- it "returns the tangent for Complex numbers" do
- @object.send(:tan, Complex(0, CMath::PI)).should be_close(Complex(0.0, 0.99627207622075), TOLERANCE)
- @object.send(:tan, Complex(3, 4)).should be_close(Complex(-0.000187346204629452, 0.999355987381473), TOLERANCE)
- end
-end
-
-describe :complex_math_tan_bang, shared: true do
- it "returns the tangent of the argument" do
- @object.send(:tan!, 0.0).should == 0.0
- @object.send(:tan!, -0.0).should == -0.0
- @object.send(:tan!, 4.22).should be_close(1.86406937682395, TOLERANCE)
- @object.send(:tan!, -9.65).should be_close(-0.229109052606441, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:tan!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/shared/tanh.rb b/spec/ruby/library/cmath/math/shared/tanh.rb
deleted file mode 100644
index f2c9a5abb1f356..00000000000000
--- a/spec/ruby/library/cmath/math/shared/tanh.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require_relative '../fixtures/classes'
-
-describe :complex_math_tanh, shared: true do
- it "returns the hyperbolic tangent of the argument" do
- @object.send(:tanh, 0.0).should == 0.0
- @object.send(:tanh, -0.0).should == -0.0
- @object.send(:tanh, infinity_value).should == 1.0
- @object.send(:tanh, -infinity_value).should == -1.0
- @object.send(:tanh, 2.5).should be_close(0.98661429815143, TOLERANCE)
- @object.send(:tanh, -4.892).should be_close(-0.999887314427707, TOLERANCE)
- end
-
- it "returns the hyperbolic tangent for Complex numbers" do
- @object.send(:tanh, Complex(0, CMath::PI)).should be_close(Complex(0.0, -1.22464679914735e-16), TOLERANCE)
- @object.send(:tanh, Complex(3, 4)).should be_close(Complex(1.00070953606723, 0.00490825806749599), TOLERANCE)
- end
-end
-
-describe :complex_math_tanh_bang, shared: true do
- it "returns the hyperbolic tangent of the argument" do
- @object.send(:tanh!, 0.0).should == 0.0
- @object.send(:tanh!, -0.0).should == -0.0
- @object.send(:tanh!, infinity_value).should == 1.0
- @object.send(:tanh!, -infinity_value).should == -1.0
- @object.send(:tanh!, 2.5).should be_close(0.98661429815143, TOLERANCE)
- @object.send(:tanh!, -4.892).should be_close(-0.999887314427707, TOLERANCE)
- end
-
- it "raises a TypeError when passed a Complex number" do
- -> { @object.send(:tanh!, Complex(4, 5)) }.should raise_error(TypeError)
- end
-end
diff --git a/spec/ruby/library/cmath/math/sin_spec.rb b/spec/ruby/library/cmath/math/sin_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/sin_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/sinh_spec.rb b/spec/ruby/library/cmath/math/sinh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/sinh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/sqrt_spec.rb b/spec/ruby/library/cmath/math/sqrt_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/sqrt_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/tan_spec.rb b/spec/ruby/library/cmath/math/tan_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/tan_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/cmath/math/tanh_spec.rb b/spec/ruby/library/cmath/math/tanh_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/cmath/math/tanh_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/erb/new_spec.rb b/spec/ruby/library/erb/new_spec.rb
index f18e25939ef594..4d7f7bf36a3346 100644
--- a/spec/ruby/library/erb/new_spec.rb
+++ b/spec/ruby/library/erb/new_spec.rb
@@ -138,4 +138,20 @@
ERB.new(@eruby_str).result
->{ ERB.new("<%= list %>").result }.should raise_error(NameError)
end
+
+ describe "warning about arguments" do
+ ruby_version_is "3.1" do
+ it "warns when passed safe_level and later arguments" do
+ -> {
+ ERB.new(@eruby_str, nil, '%')
+ }.should complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./)
+ end
+
+ it "does not warn when passed arguments as keyword argument" do
+ -> {
+ ERB.new(@eruby_str, trim_mode: '%')
+ }.should_not complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./)
+ end
+ end
+ end
end
diff --git a/spec/ruby/library/scanf/io/block_scanf_spec.rb b/spec/ruby/library/scanf/io/block_scanf_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/scanf/io/block_scanf_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/scanf/io/fixtures/date.txt b/spec/ruby/library/scanf/io/fixtures/date.txt
deleted file mode 100644
index a1bd635c0c3af2..00000000000000
--- a/spec/ruby/library/scanf/io/fixtures/date.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Beethoven 1770
-Bach 1685
-Handel 1685
-
diff --git a/spec/ruby/library/scanf/io/fixtures/helloworld.txt b/spec/ruby/library/scanf/io/fixtures/helloworld.txt
deleted file mode 100644
index 3b18e512dba79e..00000000000000
--- a/spec/ruby/library/scanf/io/fixtures/helloworld.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello world
diff --git a/spec/ruby/library/scanf/io/scanf_spec.rb b/spec/ruby/library/scanf/io/scanf_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/scanf/io/scanf_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/scanf/io/shared/block_scanf.rb b/spec/ruby/library/scanf/io/shared/block_scanf.rb
deleted file mode 100644
index d938f4373460b3..00000000000000
--- a/spec/ruby/library/scanf/io/shared/block_scanf.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'scanf'
-
-describe :scanf_io_block_scanf, shared: true do
- before :each do
- @data = File.open(fixture(__FILE__, 'date.txt'), 'rb')
- end
-
- after :each do
- @data.close unless @data.closed?
- end
-
- it "passes each match to the block as an array" do
- res = @data.send(@method, "%s%d") { |name, year| "#{name} was born in #{year}." }
- res.should == ["Beethoven was born in 1770.", "Bach was born in 1685.", "Handel was born in 1685."]
- end
-
- it "keeps scanning the input and cycling back to the beginning of the input string" do
- a = []
- @data.send(@method, "%s"){|w| a << w}
- a.should == [["Beethoven"], ["1770"], ["Bach"], ["1685"], ["Handel"], ["1685"]]
- end
-
- it "returns an empty array when a wrong specifier is passed" do
- a = []
- @data.send(@method, "%z"){|w| a << w}
- a.empty?.should be_true
- end
-end
diff --git a/spec/ruby/library/scanf/string/block_scanf_spec.rb b/spec/ruby/library/scanf/string/block_scanf_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/scanf/string/block_scanf_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/scanf/string/scanf_spec.rb b/spec/ruby/library/scanf/string/scanf_spec.rb
deleted file mode 100644
index e15f14f95f5d6f..00000000000000
--- a/spec/ruby/library/scanf/string/scanf_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/scanf/string/shared/block_scanf.rb b/spec/ruby/library/scanf/string/shared/block_scanf.rb
deleted file mode 100644
index 25ab3f442a88e7..00000000000000
--- a/spec/ruby/library/scanf/string/shared/block_scanf.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'scanf'
-
-describe :scanf_string_block_scanf, shared: true do
- it "passes each match to the block as an array" do
- a = []
- "hello world".send(@method, "%s%s"){|w| a << w}
- a.should == [["hello", "world"]]
- end
-
- it "keeps scanning the input and cycling back to the beginning of the input string" do
- a = []
- "hello world".send(@method, "%s"){|w| a << w}
- a.should == [["hello"], ["world"]]
-
- string = "123 abc 456 def 789 ghi"
- s = string.send(@method, "%d%s"){|num,str| [num * 2, str.upcase]}
- s.should == [[246, "ABC"], [912, "DEF"], [1578, "GHI"]]
- end
-
- it "returns an empty array when a wrong specifier is passed" do
- a = []
- "hello world".send(@method, "%z"){|w| a << w}
- a.empty?.should be_true
- end
-end
diff --git a/spec/ruby/library/stringio/putc_spec.rb b/spec/ruby/library/stringio/putc_spec.rb
index 223b3523e52735..1ce53b7ef20ed4 100644
--- a/spec/ruby/library/stringio/putc_spec.rb
+++ b/spec/ruby/library/stringio/putc_spec.rb
@@ -35,6 +35,21 @@
@io.putc("t")
@io.pos.should == 3
end
+
+ it "handles concurrent writes correctly" do
+ @io = StringIO.new
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.putc i.to_s
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n
+ end
end
describe "StringIO#putc when passed [Object]" do
diff --git a/spec/ruby/library/stringio/puts_spec.rb b/spec/ruby/library/stringio/puts_spec.rb
index a9f289a5a56398..9c890262dd996f 100644
--- a/spec/ruby/library/stringio/puts_spec.rb
+++ b/spec/ruby/library/stringio/puts_spec.rb
@@ -101,6 +101,20 @@
@io.puts ''
@io.string.should == "\n"
end
+
+ it "handles concurrent writes correctly" do
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.puts i
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n.times.map { |i| "#{i}\n" }.join.size
+ end
end
describe "StringIO#puts when passed no arguments" do
diff --git a/spec/ruby/library/stringio/shared/write.rb b/spec/ruby/library/stringio/shared/write.rb
index 0eb71466e36032..c5a0f8f513d68a 100644
--- a/spec/ruby/library/stringio/shared/write.rb
+++ b/spec/ruby/library/stringio/shared/write.rb
@@ -45,6 +45,21 @@
@io.pos.should eql(4)
end
+ it "handles concurrent writes correctly" do
+ @io = StringIO.new
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.write i.to_s
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n.times.map(&:to_s).join.size
+ end
+
ruby_version_is ""..."3.0" do
it "does not taint self when the passed argument is tainted" do
@io.send(@method, "test".taint)
diff --git a/spec/ruby/optional/capi/class_spec.rb b/spec/ruby/optional/capi/class_spec.rb
index abeba0f741b622..66af3812430c96 100644
--- a/spec/ruby/optional/capi/class_spec.rb
+++ b/spec/ruby/optional/capi/class_spec.rb
@@ -323,6 +323,15 @@
@s.rb_define_class("ClassSpecDefineClass4", nil)
}.should raise_error(ArgumentError)
end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class("_INVALID_CLASS", CApiClassSpecs::Super)
+ cls.name.should == "_INVALID_CLASS"
+
+ -> {
+ Object.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
end
describe "rb_define_class_under" do
@@ -367,6 +376,15 @@
it "raises a TypeError if class is defined and its superclass mismatches the given one" do
-> { @s.rb_define_class_under(CApiClassSpecs, "Sub", Object) }.should raise_error(TypeError)
end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class_under(CApiClassSpecs, "_INVALID_CLASS", CApiClassSpecs::Super)
+ cls.name.should == "CApiClassSpecs::_INVALID_CLASS"
+
+ -> {
+ CApiClassSpecs.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
end
describe "rb_define_class_id_under" do
@@ -394,6 +412,15 @@
it "raises a TypeError if class is defined and its superclass mismatches the given one" do
-> { @s.rb_define_class_id_under(CApiClassSpecs, :Sub, Object) }.should raise_error(TypeError)
end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class_id_under(CApiClassSpecs, :_INVALID_CLASS2, CApiClassSpecs::Super)
+ cls.name.should == "CApiClassSpecs::_INVALID_CLASS2"
+
+ -> {
+ CApiClassSpecs.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
end
describe "rb_define_class_variable" do
diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c
index 865fc484be2f19..a0136530f2812e 100644
--- a/spec/ruby/optional/capi/ext/encoding_spec.c
+++ b/spec/ruby/optional/capi/ext/encoding_spec.c
@@ -71,11 +71,9 @@ static VALUE encoding_spec_rb_default_external_encoding(VALUE self) {
return rb_str_new2(enc->name);
}
-#ifdef RUBY_VERSION_IS_2_6
static VALUE encoding_spec_rb_enc_alias(VALUE self, VALUE alias, VALUE orig) {
return INT2NUM(rb_enc_alias(RSTRING_PTR(alias), RSTRING_PTR(orig)));
}
-#endif
static VALUE encoding_spec_rb_enc_associate(VALUE self, VALUE obj, VALUE enc) {
return rb_enc_associate(obj, NIL_P(enc) ? NULL : rb_enc_find(RSTRING_PTR(enc)));
@@ -327,16 +325,9 @@ void Init_encoding_spec(void) {
rb_define_method(cls, "rb_locale_encindex", encoding_spec_rb_locale_encindex, 0);
rb_define_method(cls, "rb_filesystem_encoding", encoding_spec_rb_filesystem_encoding, 0);
rb_define_method(cls, "rb_filesystem_encindex", encoding_spec_rb_filesystem_encindex, 0);
- rb_define_method(cls, "rb_default_internal_encoding",
- encoding_spec_rb_default_internal_encoding, 0);
-
- rb_define_method(cls, "rb_default_external_encoding",
- encoding_spec_rb_default_external_encoding, 0);
-
-#ifdef RUBY_VERSION_IS_2_6
+ rb_define_method(cls, "rb_default_internal_encoding", encoding_spec_rb_default_internal_encoding, 0);
+ rb_define_method(cls, "rb_default_external_encoding", encoding_spec_rb_default_external_encoding, 0);
rb_define_method(cls, "rb_enc_alias", encoding_spec_rb_enc_alias, 2);
-#endif
-
rb_define_method(cls, "MBCLEN_CHARFOUND_P", encoding_spec_MBCLEN_CHARFOUND_P, 1);
rb_define_method(cls, "rb_enc_associate", encoding_spec_rb_enc_associate, 2);
rb_define_method(cls, "rb_enc_associate_index", encoding_spec_rb_enc_associate_index, 2);
diff --git a/spec/ruby/optional/capi/ext/gc_spec.c b/spec/ruby/optional/capi/ext/gc_spec.c
index 7dc9c347c76f43..082e4af59ce25b 100644
--- a/spec/ruby/optional/capi/ext/gc_spec.c
+++ b/spec/ruby/optional/capi/ext/gc_spec.c
@@ -7,6 +7,9 @@ extern "C" {
VALUE registered_tagged_value;
VALUE registered_reference_value;
+VALUE registered_before_rb_gc_register_address;
+VALUE registered_before_rb_global_variable;
+VALUE rb_gc_register_address_outside_init;
static VALUE registered_tagged_address(VALUE self) {
return registered_tagged_value;
@@ -16,6 +19,25 @@ static VALUE registered_reference_address(VALUE self) {
return registered_reference_value;
}
+static VALUE get_registered_before_rb_gc_register_address(VALUE self) {
+ return registered_before_rb_gc_register_address;
+}
+
+static VALUE get_registered_before_rb_global_variable(VALUE self) {
+ return registered_before_rb_global_variable;
+}
+
+static VALUE gc_spec_rb_gc_register_address(VALUE self) {
+ rb_gc_register_address_outside_init = rb_str_new_cstr("rb_gc_register_address() outside Init_");
+ rb_gc_register_address(&rb_gc_register_address_outside_init);
+ return rb_gc_register_address_outside_init;
+}
+
+static VALUE gc_spec_rb_gc_unregister_address(VALUE self) {
+ rb_gc_unregister_address(&rb_gc_register_address_outside_init);
+ return Qnil;
+}
+
static VALUE gc_spec_rb_gc_enable(VALUE self) {
return rb_gc_enable();
}
@@ -50,9 +72,18 @@ void Init_gc_spec(void) {
rb_gc_register_address(®istered_tagged_value);
rb_gc_register_address(®istered_reference_value);
+ rb_gc_register_address(®istered_before_rb_gc_register_address);
+ rb_global_variable(®istered_before_rb_global_variable);
+
+ registered_before_rb_gc_register_address = rb_str_new_cstr("registered before rb_gc_register_address()");
+ registered_before_rb_global_variable = rb_str_new_cstr("registered before rb_global_variable()");
rb_define_method(cls, "registered_tagged_address", registered_tagged_address, 0);
rb_define_method(cls, "registered_reference_address", registered_reference_address, 0);
+ rb_define_method(cls, "registered_before_rb_gc_register_address", get_registered_before_rb_gc_register_address, 0);
+ rb_define_method(cls, "registered_before_rb_global_variable", get_registered_before_rb_global_variable, 0);
+ rb_define_method(cls, "rb_gc_register_address", gc_spec_rb_gc_register_address, 0);
+ rb_define_method(cls, "rb_gc_unregister_address", gc_spec_rb_gc_unregister_address, 0);
rb_define_method(cls, "rb_gc_enable", gc_spec_rb_gc_enable, 0);
rb_define_method(cls, "rb_gc_disable", gc_spec_rb_gc_disable, 0);
rb_define_method(cls, "rb_gc", gc_spec_rb_gc, 0);
diff --git a/spec/ruby/optional/capi/ext/globals_spec.c b/spec/ruby/optional/capi/ext/globals_spec.c
index 28a9633f98b67a..20dea1a05adaf3 100644
--- a/spec/ruby/optional/capi/ext/globals_spec.c
+++ b/spec/ruby/optional/capi/ext/globals_spec.c
@@ -20,6 +20,16 @@ static VALUE sb_define_hooked_variable(VALUE self, VALUE var_name) {
return Qnil;
}
+static VALUE sb_define_hooked_variable_default_accessors(VALUE self, VALUE var_name) {
+ rb_define_hooked_variable(StringValuePtr(var_name), &g_hooked_var, (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
+static VALUE sb_define_hooked_variable_null_var(VALUE self, VALUE var_name) {
+ rb_define_hooked_variable(StringValuePtr(var_name), NULL, (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
VALUE g_ro_var;
static VALUE sb_define_readonly_variable(VALUE self, VALUE var_name, VALUE val) {
@@ -40,6 +50,26 @@ static VALUE sb_define_variable(VALUE self, VALUE var_name, VALUE val) {
return Qnil;
}
+long virtual_var_storage;
+
+VALUE incrementing_getter(ID id, VALUE *data) {
+ return LONG2FIX(virtual_var_storage++);
+}
+
+void incrementing_setter(VALUE val, ID id, VALUE *data) {
+ virtual_var_storage = FIX2LONG(val);
+}
+
+static VALUE sb_define_virtual_variable_default_accessors(VALUE self, VALUE name) {
+ rb_define_virtual_variable(StringValuePtr(name), (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
+static VALUE sb_define_virtual_variable_incrementing_accessors(VALUE self, VALUE name) {
+ rb_define_virtual_variable(StringValuePtr(name), incrementing_getter, incrementing_setter);
+ return Qnil;
+}
+
static VALUE sb_f_global_variables(VALUE self) {
return rb_f_global_variables();
}
@@ -101,10 +131,14 @@ void Init_globals_spec(void) {
VALUE cls = rb_define_class("CApiGlobalSpecs", rb_cObject);
g_hooked_var = Qnil;
rb_define_method(cls, "rb_define_hooked_variable_2x", sb_define_hooked_variable, 1);
+ rb_define_method(cls, "rb_define_hooked_variable_default_accessors", sb_define_hooked_variable_default_accessors, 1);
+ rb_define_method(cls, "rb_define_hooked_variable_null_var", sb_define_hooked_variable_null_var, 1);
g_ro_var = Qnil;
rb_define_method(cls, "rb_define_readonly_variable", sb_define_readonly_variable, 2);
g_var = Qnil;
rb_define_method(cls, "rb_define_variable", sb_define_variable, 2);
+ rb_define_method(cls, "rb_define_virtual_variable_default_accessors", sb_define_virtual_variable_default_accessors, 1);
+ rb_define_method(cls, "rb_define_virtual_variable_incrementing_accessors", sb_define_virtual_variable_incrementing_accessors, 1);
rb_define_method(cls, "sb_get_global_value", sb_get_global_value, 0);
rb_define_method(cls, "rb_f_global_variables", sb_f_global_variables, 0);
rb_define_method(cls, "sb_gv_get", sb_gv_get, 1);
diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h
index 426b1ddc04b3b7..245669d2007b1c 100644
--- a/spec/ruby/optional/capi/ext/rubyspec.h
+++ b/spec/ruby/optional/capi/ext/rubyspec.h
@@ -34,34 +34,4 @@
#define RUBY_VERSION_IS_3_0
#endif
-#if RUBY_VERSION_MAJOR > 2 || (RUBY_VERSION_MAJOR == 2 && RUBY_VERSION_MINOR >= 7)
-#define RUBY_VERSION_IS_2_7
-#endif
-
-#if RUBY_VERSION_MAJOR > 2 || (RUBY_VERSION_MAJOR == 2 && RUBY_VERSION_MINOR >= 6)
-#define RUBY_VERSION_IS_2_6
-#endif
-
-#if defined(__cplusplus) && !defined(RUBY_VERSION_IS_2_7)
-/* Ruby < 2.7 needs this to let these function with callbacks and compile in C++ code */
-#define rb_define_method(mod, name, func, argc) rb_define_method(mod, name, RUBY_METHOD_FUNC(func), argc)
-#define rb_define_protected_method(mod, name, func, argc) rb_define_protected_method(mod, name, RUBY_METHOD_FUNC(func), argc)
-#define rb_define_private_method(mod, name, func, argc) rb_define_private_method(mod, name, RUBY_METHOD_FUNC(func), argc)
-#define rb_define_singleton_method(mod, name, func, argc) rb_define_singleton_method(mod, name, RUBY_METHOD_FUNC(func), argc)
-#define rb_define_module_function(mod, name, func, argc) rb_define_module_function(mod, name, RUBY_METHOD_FUNC(func), argc)
-#define rb_define_global_function(name, func, argc) rb_define_global_function(name, RUBY_METHOD_FUNC(func), argc)
-#define rb_hash_foreach(hash, func, farg) rb_hash_foreach(hash, (int (*)(...))func, farg)
-#define st_foreach(tab, func, arg) st_foreach(tab, (int (*)(...))func, arg)
-#define rb_block_call(object, name, args_count, args, block_call_func, data) rb_block_call(object, name, args_count, args, RUBY_METHOD_FUNC(block_call_func), data)
-#define rb_ensure(b_proc, data1, e_proc, data2) rb_ensure(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2)
-#define rb_rescue(b_proc, data1, e_proc, data2) rb_rescue(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2)
-#define rb_rescue2(b_proc, data1, e_proc, data2, ...) rb_rescue2(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2, __VA_ARGS__)
-#define rb_catch(tag, func, data) rb_catch(tag, RUBY_METHOD_FUNC(func), data)
-#define rb_catch_obj(tag, func, data) rb_catch_obj(tag, RUBY_METHOD_FUNC(func), data)
-#define rb_proc_new(fn, arg) rb_proc_new(RUBY_METHOD_FUNC(fn), arg)
-#define rb_fiber_new(fn, arg) rb_fiber_new(RUBY_METHOD_FUNC(fn), arg)
-#define rb_thread_create(fn, arg) rb_thread_create(RUBY_METHOD_FUNC(fn), arg)
-#define rb_define_hooked_variable(name, var, getter, setter) rb_define_hooked_variable(name, var, RUBY_METHOD_FUNC(getter), (void (*)(...))setter)
-#endif
-
#endif
diff --git a/spec/ruby/optional/capi/gc_spec.rb b/spec/ruby/optional/capi/gc_spec.rb
index 23e2b7c9ab9116..d76ea7394f304e 100644
--- a/spec/ruby/optional/capi/gc_spec.rb
+++ b/spec/ruby/optional/capi/gc_spec.rb
@@ -7,15 +7,33 @@
@f = CApiGCSpecs.new
end
- it "correctly gets the value from a registered address" do
- @f.registered_tagged_address.should == 10
- @f.registered_tagged_address.should equal(@f.registered_tagged_address)
- @f.registered_reference_address.should == "Globally registered data"
- @f.registered_reference_address.should equal(@f.registered_reference_address)
+ describe "rb_gc_register_address" do
+ it "correctly gets the value from a registered address" do
+ @f.registered_tagged_address.should == 10
+ @f.registered_tagged_address.should equal(@f.registered_tagged_address)
+ @f.registered_reference_address.should == "Globally registered data"
+ @f.registered_reference_address.should equal(@f.registered_reference_address)
+ end
+
+ it "keeps the value alive even if the value is assigned after rb_gc_register_address() is called" do
+ GC.start
+ @f.registered_before_rb_gc_register_address.should == "registered before rb_gc_register_address()"
+ end
+
+ it "can be called outside Init_" do
+ @f.rb_gc_register_address.should == "rb_gc_register_address() outside Init_"
+ @f.rb_gc_unregister_address
+ end
end
- describe "rb_gc_enable" do
+ describe "rb_global_variable" do
+ it "keeps the value alive even if the value is assigned after rb_global_variable() is called" do
+ GC.start
+ @f.registered_before_rb_global_variable.should == "registered before rb_global_variable()"
+ end
+ end
+ describe "rb_gc_enable" do
after do
GC.enable
end
diff --git a/spec/ruby/optional/capi/globals_spec.rb b/spec/ruby/optional/capi/globals_spec.rb
index cc6f6ef3a83841..48677620bcf8a2 100644
--- a/spec/ruby/optional/capi/globals_spec.rb
+++ b/spec/ruby/optional/capi/globals_spec.rb
@@ -9,7 +9,7 @@
end
it "correctly gets global values" do
- @f.sb_gv_get("$BLAH").should == nil
+ suppress_warning { @f.sb_gv_get("$BLAH") }.should == nil
@f.sb_gv_get("$\\").should == nil
@f.sb_gv_get("\\").should == nil # rb_gv_get should change \ to $\
end
@@ -21,7 +21,7 @@
end
it "correctly sets global values" do
- @f.sb_gv_get("$BLAH").should == nil
+ suppress_warning { @f.sb_gv_get("$BLAH") }.should == nil
@f.sb_gv_set("$BLAH", 10)
begin
@f.sb_gv_get("$BLAH").should == 10
@@ -42,6 +42,10 @@
end
it "rb_define_readonly_variable should define a new readonly global variable" do
+ # Check the gvar doesn't exist and ensure rb_gv_get doesn't implicitly declare the gvar,
+ # otherwise the rb_define_readonly_variable call will conflict.
+ suppress_warning { @f.sb_gv_get("ro_gvar") } .should == nil
+
@f.rb_define_readonly_variable("ro_gvar", 15)
$ro_gvar.should == 15
-> { $ro_gvar = 10 }.should raise_error(NameError)
@@ -53,6 +57,52 @@
$hooked_gvar.should == 4
end
+ it "rb_define_hooked_variable should use default accessors if NULL ones are supplied" do
+ @f.rb_define_hooked_variable_default_accessors("$hooked_gvar_default_accessors")
+ $hooked_gvar_default_accessors = 10
+ $hooked_gvar_default_accessors.should == 10
+ end
+
+ it "rb_define_hooked_variable with default accessors should return nil for NULL variables" do
+ @f.rb_define_hooked_variable_null_var("$hooked_gvar_null_value")
+ $hooked_gvar_null_value.should == nil
+ end
+
+ describe "rb_define_virtual_variable" do
+ describe "with default accessors" do
+ before :all do
+ @f.rb_define_virtual_variable_default_accessors("$virtual_variable_default_accessors")
+ end
+
+ it "is read-only" do
+ -> { $virtual_variable_default_accessors = 10 }.should raise_error(NameError, /read-only/)
+ end
+
+ it "returns false with the default getter" do
+ $virtual_variable_default_accessors.should == false
+ $virtual_variable_default_accessors.should == false
+ end
+ end
+
+ describe "with supplied accessors" do
+ before :all do
+ @f.rb_define_virtual_variable_incrementing_accessors("$virtual_variable_incrementing_accessors")
+ end
+
+ it "returns a dynamically changing value" do
+ $virtual_variable_incrementing_accessors = 20
+ $virtual_variable_incrementing_accessors.should == 20
+ $virtual_variable_incrementing_accessors.should == 21
+ $virtual_variable_incrementing_accessors.should == 22
+
+ $virtual_variable_incrementing_accessors = 100
+ $virtual_variable_incrementing_accessors.should == 100
+ $virtual_variable_incrementing_accessors.should == 101
+ $virtual_variable_incrementing_accessors.should == 102
+ end
+ end
+ end
+
describe "rb_fs" do
before :each do
@field_separator = $;
diff --git a/spec/ruby/shared/kernel/complex.rb b/spec/ruby/shared/kernel/complex.rb
new file mode 100644
index 00000000000000..98ee0b2b3fbea7
--- /dev/null
+++ b/spec/ruby/shared/kernel/complex.rb
@@ -0,0 +1,133 @@
+# Specs shared by Kernel#Complex() and String#to_c()
+describe :kernel_complex, shared: true do
+
+ it "returns a Complex object" do
+ @object.send(@method, '9').should be_an_instance_of(Complex)
+ end
+
+ it "understands integers" do
+ @object.send(@method, '20').should == Complex(20)
+ end
+
+ it "understands negative integers" do
+ @object.send(@method, '-3').should == Complex(-3)
+ end
+
+ it "understands fractions (numerator/denominator) for the real part" do
+ @object.send(@method, '2/3').should == Complex(Rational(2, 3))
+ end
+
+ it "understands fractions (numerator/denominator) for the imaginary part" do
+ @object.send(@method, '4+2/3i').should == Complex(4, Rational(2, 3))
+ end
+
+ it "understands negative fractions (-numerator/denominator) for the real part" do
+ @object.send(@method, '-2/3').should == Complex(Rational(-2, 3))
+ end
+
+ it "understands negative fractions (-numerator/denominator) for the imaginary part" do
+ @object.send(@method, '7-2/3i').should == Complex(7, Rational(-2, 3))
+ end
+
+ it "understands floats (a.b) for the real part" do
+ @object.send(@method, '2.3').should == Complex(2.3)
+ end
+
+ it "understands floats (a.b) for the imaginary part" do
+ @object.send(@method, '4+2.3i').should == Complex(4, 2.3)
+ end
+
+ it "understands negative floats (-a.b) for the real part" do
+ @object.send(@method, '-2.33').should == Complex(-2.33)
+ end
+
+ it "understands negative floats (-a.b) for the imaginary part" do
+ @object.send(@method, '7-28.771i').should == Complex(7, -28.771)
+ end
+
+ it "understands an integer followed by 'i' to mean that integer is the imaginary part" do
+ @object.send(@method, '35i').should == Complex(0,35)
+ end
+
+ it "understands a negative integer followed by 'i' to mean that negative integer is the imaginary part" do
+ @object.send(@method, '-29i').should == Complex(0,-29)
+ end
+
+ it "understands an 'i' by itself as denoting a complex number with an imaginary part of 1" do
+ @object.send(@method, 'i').should == Complex(0,1)
+ end
+
+ it "understands a '-i' by itself as denoting a complex number with an imaginary part of -1" do
+ @object.send(@method, '-i').should == Complex(0,-1)
+ end
+
+ it "understands 'a+bi' to mean a complex number with 'a' as the real part, 'b' as the imaginary" do
+ @object.send(@method, '79+4i').should == Complex(79,4)
+ end
+
+ it "understands 'a-bi' to mean a complex number with 'a' as the real part, '-b' as the imaginary" do
+ @object.send(@method, '79-4i').should == Complex(79,-4)
+ end
+
+ it "understands 'a+i' to mean a complex number with 'a' as the real part, 1i as the imaginary" do
+ @object.send(@method, '79+i').should == Complex(79, 1)
+ end
+
+ it "understands 'a-i' to mean a complex number with 'a' as the real part, -1i as the imaginary" do
+ @object.send(@method, '79-i').should == Complex(79, -1)
+ end
+
+ it "understands i, I, j, and J imaginary units" do
+ @object.send(@method, '79+4i').should == Complex(79, 4)
+ @object.send(@method, '79+4I').should == Complex(79, 4)
+ @object.send(@method, '79+4j').should == Complex(79, 4)
+ @object.send(@method, '79+4J').should == Complex(79, 4)
+ end
+
+ it "understands scientific notation for the real part" do
+ @object.send(@method, '2e3+4i').should == Complex(2e3,4)
+ end
+
+ it "understands negative scientific notation for the real part" do
+ @object.send(@method, '-2e3+4i').should == Complex(-2e3,4)
+ end
+
+ it "understands scientific notation for the imaginary part" do
+ @object.send(@method, '4+2e3i').should == Complex(4, 2e3)
+ end
+
+ it "understands negative scientific notation for the imaginary part" do
+ @object.send(@method, '4-2e3i').should == Complex(4, -2e3)
+ end
+
+ it "understands scientific notation for the real and imaginary part in the same String" do
+ @object.send(@method, '2e3+2e4i').should == Complex(2e3,2e4)
+ end
+
+ it "understands negative scientific notation for the real and imaginary part in the same String" do
+ @object.send(@method, '-2e3-2e4i').should == Complex(-2e3,-2e4)
+ end
+
+ it "understands scientific notation with e and E" do
+ @object.send(@method, '2e3+2e4i').should == Complex(2e3, 2e4)
+ @object.send(@method, '2E3+2E4i').should == Complex(2e3, 2e4)
+ end
+
+ it "understands 'm@a' to mean a complex number in polar form with 'm' as the modulus, 'a' as the argument" do
+ @object.send(@method, '79@4').should == Complex.polar(79, 4)
+ @object.send(@method, '-79@4').should == Complex.polar(-79, 4)
+ @object.send(@method, '79@-4').should == Complex.polar(79, -4)
+ end
+
+ it "ignores leading whitespaces" do
+ @object.send(@method, ' 79+4i').should == Complex(79, 4)
+ end
+
+ it "ignores trailing whitespaces" do
+ @object.send(@method, '79+4i ').should == Complex(79, 4)
+ end
+
+ it "understands _" do
+ @object.send(@method, '7_9+4_0i').should == Complex(79, 40)
+ end
+end
diff --git a/test/-ext-/string/test_cstr.rb b/test/-ext-/string/test_cstr.rb
index d909781700a78f..efc64119dc22ff 100644
--- a/test/-ext-/string/test_cstr.rb
+++ b/test/-ext-/string/test_cstr.rb
@@ -43,7 +43,11 @@ def test_frozen
end
def test_rb_str_new_frozen_embed
- str = Bug::String.cstr_noembed("rbconfig.rb")
+ # "rbconfi" is the smallest "maximum embeddable string". VWA adds
+ # a capacity field, which removes one pointer capacity for embedded objects,
+ # so if VWA is enabled, but there is only one size pool, then the
+ # maximum embeddable capacity on 32 bit machines is 8 bytes.
+ str = Bug::String.cstr_noembed("rbconfi")
str = Bug::String.rb_str_new_frozen(str)
assert_equal true, Bug::String.cstr_embedded?(str)
end
diff --git a/test/-ext-/test_random.rb b/test/-ext-/test_random.rb
index 838e5d2f14211e..e5cebcc871a5e8 100644
--- a/test/-ext-/test_random.rb
+++ b/test/-ext-/test_random.rb
@@ -1,11 +1,13 @@
require 'test/unit'
module TestRandomExt
+ def setup
+ super
+ assert_nothing_raised(LoadError) {require '-test-/random'}
+ end
+
class TestLoop < Test::Unit::TestCase
- def setup
- super
- assert_nothing_raised(LoadError) {require '-test-/random'}
- end
+ include TestRandomExt
def test_bytes
rnd = Bug::Random::Loop.new(1)
@@ -24,4 +26,20 @@ def test_real
assert_equal(1.00, Bug::Random::Loop.new(4<<14).rand)
end
end
+
+ class TestVersionZero < Test::Unit::TestCase
+ include TestRandomExt
+
+ def test_bad_version
+ assert_raise(TypeError) {Bug::Random::VersionZero.new}
+ end
+ end
+
+ class TestVersionMax < Test::Unit::TestCase
+ include TestRandomExt
+
+ def test_bad_version
+ assert_raise(TypeError) {Bug::Random::VersionMax.new}
+ end
+ end
end
diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb
index 1a21235d0a983d..a2a7718a30a099 100644
--- a/test/coverage/test_coverage.rb
+++ b/test/coverage/test_coverage.rb
@@ -964,4 +964,18 @@ def test_double_suspend
p :NG
end;
end
+
+ def test_tag_break_with_branch_coverage
+ result = {
+ :branches => {
+ [:"&.", 0, 1, 0, 1, 6] => {
+ [:then, 1, 1, 0, 1, 6] => 1,
+ [:else, 2, 1, 0, 1, 6] => 0,
+ },
+ },
+ }
+ assert_coverage(<<~"end;", { branches: true }, result)
+ 1&.tap do break end
+ end;
+ end
end
diff --git a/test/fiber/test_mutex.rb b/test/fiber/test_mutex.rb
index b0655f06a5a84f..449c49f38bc81e 100644
--- a/test/fiber/test_mutex.rb
+++ b/test/fiber/test_mutex.rb
@@ -194,7 +194,7 @@ def test_queue_pop_waits
end
def test_mutex_deadlock
- error_pattern = /No live threads left. Deadlock\?/
+ error_pattern = /lock already owned by another fiber/
assert_in_out_err %W[-I#{__dir__} -], <<-RUBY, ['in synchronize'], error_pattern, success: false
require 'scheduler'
@@ -217,4 +217,24 @@ def test_mutex_deadlock
thread.join
RUBY
end
+
+ def test_mutex_fiber_deadlock_no_scheduler
+ thr = Thread.new do
+ loop do
+ sleep 1
+ end
+ end
+
+ mutex = Mutex.new
+ mutex.synchronize do
+ error = assert_raise ThreadError do
+ Fiber.new do
+ mutex.lock
+ end.resume
+ end
+ assert_includes error.message, "deadlock; lock already owned by another fiber belonging to the same thread"
+ end
+ ensure
+ thr&.kill&.join
+ end
end
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index bce7271a3bda46..05ba8d184ae307 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -1822,26 +1822,6 @@ def test_rm_rf
assert_file_not_exist 'tmpdatadir'
end
- def test_rm_rf_no_permissions
- check_singleton :rm_rf
-
- return if /mswin|mingw/ =~ RUBY_PLATFORM
-
- mkdir 'tmpdatadir'
- touch 'tmpdatadir/tmpdata'
- chmod "-x", 'tmpdatadir'
-
- begin
- assert_raise Errno::EACCES do
- rm_rf 'tmpdatadir'
- end
-
- assert_file_exist 'tmpdatadir'
- ensure
- chmod "+x", 'tmpdatadir'
- end
- end
-
def test_rmdir
check_singleton :rmdir
diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb
index 2728aa656a8f5f..eafa8be38203bd 100644
--- a/test/irb/test_cmd.rb
+++ b/test/irb/test_cmd.rb
@@ -219,145 +219,139 @@ def test_irb_info_lang
end
def test_measure
- IRB.init_config(nil)
- IRB.conf[:PROMPT] = {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
- }
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> ',
+ PROMPT_N: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: false
}
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :DEFAULT
- IRB.conf[:MEASURE] = false
- input = TestInputMethod.new([
+
+ c = Class.new(Object)
+ out, err = execute_lines(
"3\n",
"measure\n",
"3\n",
"measure :off\n",
"3\n",
- ])
- c = Class.new(Object)
- irb = IRB::Irb.new(IRB::WorkSpace.new(c.new), input)
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: conf,
+ main: c
+ )
+
assert_empty err
assert_match(/\A=> 3\nTIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
assert_empty(c.class_variables)
end
def test_measure_enabled_by_rc
- IRB.init_config(nil)
- IRB.conf[:PROMPT] = {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
- }
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> ',
+ PROMPT_N: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: true
}
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :DEFAULT
- IRB.conf[:MEASURE] = true
- input = TestInputMethod.new([
+
+ out, err = execute_lines(
"3\n",
"measure :off\n",
"3\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: conf,
+ )
+
assert_empty err
assert_match(/\Aprocessing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
end
def test_measure_enabled_by_rc_with_custom
- IRB.init_config(nil)
- IRB.conf[:PROMPT] = {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
- }
- }
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :DEFAULT
- IRB.conf[:MEASURE] = true
- IRB.conf[:MEASURE_PROC][:CUSTOM] = proc { |line, line_no, &block|
+ measuring_proc = proc { |line, line_no, &block|
time = Time.now
result = block.()
puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE]
result
}
- input = TestInputMethod.new([
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> ',
+ PROMPT_N: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: true,
+ MEASURE_PROC: { CUSTOM: measuring_proc }
+ }
+
+ out, err = execute_lines(
"3\n",
"measure :off\n",
"3\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: conf,
+ )
assert_empty err
assert_match(/\Acustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
end
def test_measure_with_custom
- IRB.init_config(nil)
- IRB.conf[:PROMPT] = {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
- }
- }
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :DEFAULT
- IRB.conf[:MEASURE] = false
- IRB.conf[:MEASURE_PROC][:CUSTOM] = proc { |line, line_no, &block|
+ measuring_proc = proc { |line, line_no, &block|
time = Time.now
result = block.()
puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE]
result
}
- input = TestInputMethod.new([
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> ',
+ PROMPT_N: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: false,
+ MEASURE_PROC: { CUSTOM: measuring_proc }
+ }
+ out, err = execute_lines(
"3\n",
"measure\n",
"3\n",
"measure :off\n",
"3\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: conf
+ )
+
assert_empty err
assert_match(/\A=> 3\nCUSTOM is added\.\n=> nil\ncustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
end
def test_measure_with_proc
- IRB.init_config(nil)
- IRB.conf[:PROMPT] = {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
- }
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> ',
+ PROMPT_N: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: false,
}
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :DEFAULT
- IRB.conf[:MEASURE] = false
- input = TestInputMethod.new([
+ c = Class.new(Object)
+ out, err = execute_lines(
"3\n",
"measure { |context, code, line_no, &block|\n",
" result = block.()\n",
@@ -373,56 +367,38 @@ def test_measure_with_proc
"3\n",
"measure :off\n",
"3\n",
- ])
- c = Class.new(Object)
- irb = IRB::Irb.new(IRB::WorkSpace.new(c.new), input)
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: conf,
+ main: c
+ )
+
assert_empty err
assert_match(/\A=> 3\nBLOCK is added\.\n=> nil\naaa\n=> 3\nBLOCK is added.\naaa\n=> nil\nbbb\n=> 3\n=> nil\n=> 3\n/, out)
assert_empty(c.class_variables)
end
def test_irb_source
- IRB.init_config(nil)
File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
- input = TestInputMethod.new([
- "a = 'bug17564'\n",
- "a\n",
- "irb_source '#{@tmpdir}/a.rb'\n",
- "a\n",
- ])
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- out, err = capture_output do
- irb.eval_input
- end
+ out, err = execute_lines(
+ "a = 'bug17564'\n",
+ "a\n",
+ "irb_source '#{@tmpdir}/a.rb'\n",
+ "a\n",
+ )
assert_empty err
assert_pattern_list([
- /=> "bug17564"\n/,
- /=> "bug17564"\n/,
- / => "hi"\n/,
- / => nil\n/,
- /=> "hi"\n/,
- ], out)
+ /=> "bug17564"\n/,
+ /=> "bug17564"\n/,
+ / => "hi"\n/,
+ / => nil\n/,
+ /=> "hi"\n/,
+ ], out)
end
def test_help
- IRB.init_config(nil)
- input = TestInputMethod.new([
- "help 'String#gsub'\n",
- "\n",
- ])
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
- out, _ = capture_output do
- irb.eval_input
- end
+ out, _ = execute_lines(
+ "help 'String#gsub'\n",
+ "\n",
+ )
# the former is what we'd get without document content installed, like on CI
# the latter is what we may get locally
@@ -434,18 +410,11 @@ def test_help
end
def test_help_without_rdoc
- IRB.init_config(nil)
- input = TestInputMethod.new([
+ out, _ = without_rdoc do
+ execute_lines(
"help 'String#gsub'\n",
"\n",
- ])
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
- out, _ = capture_output do
- without_rdoc do
- irb.eval_input
- end
+ )
end
# if it fails to require rdoc, it only returns the command object
@@ -456,21 +425,13 @@ def test_help_without_rdoc
end
def test_irb_load
- IRB.init_config(nil)
File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
- input = TestInputMethod.new([
- "a = 'bug17564'\n",
- "a\n",
- "irb_load '#{@tmpdir}/a.rb'\n",
- "a\n",
- ])
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- out, err = capture_output do
- irb.eval_input
- end
+ out, err = execute_lines(
+ "a = 'bug17564'\n",
+ "a\n",
+ "irb_load '#{@tmpdir}/a.rb'\n",
+ "a\n",
+ )
assert_empty err
assert_pattern_list([
/=> "bug17564"\n/,
@@ -482,7 +443,7 @@ def test_irb_load
end
def test_ls
- input = TestInputMethod.new([
+ out, err = execute_lines(
"class P\n",
" def m() end\n",
" def m2() end\n",
@@ -508,16 +469,8 @@ def test_ls
"obj.extend M2\n",
"def obj.m5() end\n",
"ls obj\n",
- ])
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(self)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ )
+
assert_empty err
assert_match(/^instance variables:\s+@a\n/m, out)
assert_match(/P#methods:\s+m\n/m, out)
@@ -527,19 +480,48 @@ def test_ls
assert_match(/C.methods:\s+m5\n/m, out)
end
+ def test_ls_grep
+ pend if RUBY_ENGINE == 'truffleruby'
+ out, err = execute_lines("ls 42\n")
+ assert_empty err
+ assert_match(/times/, out)
+ assert_match(/polar/, out)
+
+ [
+ "ls 42, grep: /times/\n",
+ "ls 42 -g times\n",
+ "ls 42 -G times\n",
+ ].each do |line|
+ out, err = execute_lines(line)
+ assert_empty err
+ assert_match(/times/, out)
+ assert_not_match(/polar/, out)
+ end
+ end
+
+ def test_ls_grep_empty
+ pend if RUBY_ENGINE == 'truffleruby'
+ out, err = execute_lines("ls\n")
+ assert_empty err
+ assert_match(/whereami/, out)
+ assert_match(/show_source/, out)
+
+ [
+ "ls grep: /whereami/\n",
+ "ls -g whereami\n",
+ "ls -G whereami\n",
+ ].each do |line|
+ out, err = execute_lines(line)
+ assert_empty err
+ assert_match(/whereami/, out)
+ assert_not_match(/show_source/, out)
+ end
+ end
+
def test_ls_with_no_singleton_class
- input = TestInputMethod.new([
+ out, err = execute_lines(
"ls 42",
- ])
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(self)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ )
assert_empty err
assert_match(/Comparable#methods:\s+/, out)
assert_match(/Numeric#methods:\s+/, out)
@@ -547,36 +529,34 @@ def test_ls_with_no_singleton_class
end
def test_show_source
- input = TestInputMethod.new([
+ out, err = execute_lines(
+ "show_source IRB.conf\n",
+ )
+ assert_empty err
+ assert_match(%r[/irb\.rb], out)
+ end
+
+ def test_show_source_method
+ out, err = execute_lines(
+ "p show_source('IRB.conf')\n",
+ )
+ assert_empty err
+ assert_match(%r[/irb\.rb], out)
+ end
+
+ def test_show_source_string
+ out, err = execute_lines(
"show_source 'IRB.conf'\n",
- ])
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(self)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ )
assert_empty err
assert_match(%r[/irb\.rb], out)
end
def test_show_source_alias
- input = TestInputMethod.new([
+ out, err = execute_lines(
"$ 'IRB.conf'\n",
- ])
- IRB.init_config(nil)
- IRB.conf[:COMMAND_ALIASES] = { :'$' => :show_source }
- workspace = IRB::WorkSpace.new(Object.new)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ conf: { COMMAND_ALIASES: { :'$' => :show_source } }
+ )
assert_empty err
assert_match(%r[/irb\.rb], out)
end
@@ -589,79 +569,60 @@ def show_source_test_method
end
end
EOS
- input = TestInputMethod.new([
+
+ out, err = execute_lines(
"show_source 'TestIRB::ExtendCommand#show_source_test_method'\n",
- ])
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(self)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ )
+
assert_empty err
assert_include(out, code)
end
def test_whereami
- input = TestInputMethod.new([
+ out, err = execute_lines(
"whereami\n",
- ])
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(self)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.context.return_format = "=> %s\n"
- out, err = capture_output do
- irb.eval_input
- end
+ )
assert_empty err
assert_match(/^From: .+ @ line \d+ :\n/, out)
end
def test_whereami_alias
- input = TestInputMethod.new([
+ out, err = execute_lines(
"@\n",
- ])
- IRB.init_config(nil)
- IRB.conf[:COMMAND_ALIASES] = { :'@' => :whereami }
- workspace = IRB::WorkSpace.new(Object.new)
- IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
- IRB.conf[:MAIN_CONTEXT] = irb.context
- out, err = capture_output do
- irb.eval_input
- end
+ )
assert_empty err
assert_match(/^From: .+ @ line \d+ :\n/, out)
end
def test_vars_with_aliases
- input = TestInputMethod.new([
+ @foo = "foo"
+ $bar = "bar"
+ out, err = execute_lines(
"@foo\n",
"$bar\n",
- ])
+ )
+ assert_empty err
+ assert_match(/"foo"/, out)
+ assert_match(/"bar"/, out)
+ ensure
+ remove_instance_variable(:@foo)
+ $bar = nil
+ end
+
+ private
+
+ def execute_lines(*lines, conf: {}, main: self)
IRB.init_config(nil)
- IRB.conf[:COMMAND_ALIASES] = {
- :'@' => :whereami,
- :'$' => :show_source,
- }
- main = Object.new
- main.instance_variable_set(:@foo, "foo")
- $bar = "bar"
- workspace = IRB::WorkSpace.new(main)
IRB.conf[:VERBOSE] = false
- irb = IRB::Irb.new(workspace, input)
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
IRB.conf[:MAIN_CONTEXT] = irb.context
- out, err = capture_output do
+ capture_output do
irb.eval_input
end
- assert_empty err
- assert_match(/"foo"/, out)
- assert_match(/"bar"/, out)
end
end
end
diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb
index 5994fadeffd958..2366ec3b610141 100644
--- a/test/objspace/test_objspace.rb
+++ b/test/objspace/test_objspace.rb
@@ -277,7 +277,7 @@ def test_dump_to_default
info = nil
ObjectSpace.trace_object_allocations do
line = __LINE__ + 1
- str = "hello world"
+ str = "hello w"
info = ObjectSpace.dump(str)
end
assert_dump_object(info, line)
@@ -289,7 +289,7 @@ def test_dump_to_io
th = Thread.start {r.read}
ObjectSpace.trace_object_allocations do
line = __LINE__ + 1
- str = "hello world"
+ str = "hello w"
ObjectSpace.dump(str, output: w)
end
w.close
@@ -301,7 +301,7 @@ def test_dump_to_io
def assert_dump_object(info, line)
loc = caller_locations(1, 1)[0]
assert_match(/"type":"STRING"/, info)
- assert_match(/"embedded":true, "bytesize":11, "value":"hello world", "encoding":"UTF-8"/, info)
+ assert_match(/"embedded":true, "bytesize":7, "value":"hello w", "encoding":"UTF-8"/, info)
assert_match(/"file":"#{Regexp.escape __FILE__}", "line":#{line}/, info)
assert_match(/"method":"#{loc.base_label}"/, info)
JSON.parse(info) if defined?(JSON)
@@ -549,17 +549,17 @@ def assert_test_string_entry_correct_in_dump_all(output)
#
# This test makes assertions on the assignment to `str`, so we look for
# the second appearance of /TEST STRING/ in the output
- test_string_in_dump_all = output.grep(/TEST STRING/)
- assert_equal(test_string_in_dump_all.size, 2)
+ test_string_in_dump_all = output.grep(/TEST2/)
+ assert_equal(2, test_string_in_dump_all.size, "number of strings")
entry_hash = JSON.parse(test_string_in_dump_all[1])
- assert_equal(entry_hash["bytesize"], 11)
- assert_equal(entry_hash["value"], "TEST STRING")
- assert_equal(entry_hash["encoding"], "UTF-8")
- assert_equal(entry_hash["file"], "-")
- assert_equal(entry_hash["line"], 4)
- assert_equal(entry_hash["method"], "dump_my_heap_please")
+ assert_equal(5, entry_hash["bytesize"], "bytesize is wrong")
+ assert_equal("TEST2", entry_hash["value"], "value is wrong")
+ assert_equal("UTF-8", entry_hash["encoding"], "encoding is wrong")
+ assert_equal("-", entry_hash["file"], "file is wrong")
+ assert_equal(4, entry_hash["line"], "line is wrong")
+ assert_equal("dump_my_heap_please", entry_hash["method"], "method is wrong")
assert_not_nil(entry_hash["generation"])
end
@@ -571,7 +571,7 @@ def test_dump_all
def dump_my_heap_please
ObjectSpace.trace_object_allocations_start
GC.start
- str = "TEST STRING".force_encoding("UTF-8")
+ str = "TEST2".force_encoding("UTF-8")
ObjectSpace.dump_all(output: :stdout)
end
@@ -586,7 +586,7 @@ def dump_my_heap_please
def dump_my_heap_please
ObjectSpace.trace_object_allocations_start
GC.start
- (str = "TEST STRING").force_encoding("UTF-8")
+ (str = "TEST2").force_encoding("UTF-8")
ObjectSpace.dump_all().path
end
diff --git a/test/racc/case.rb b/test/racc/case.rb
index d917f3a4e4687d..ebc30b8288ae49 100644
--- a/test/racc/case.rb
+++ b/test/racc/case.rb
@@ -17,6 +17,8 @@ class TestCase < Test::Unit::TestCase
TEST_DIR = test_dir
racc = File.join(PROJECT_DIR, 'bin', 'racc')
racc = File.join(PROJECT_DIR, '..', 'libexec', 'racc') unless File.exist?(racc)
+ racc = 'racc' unless File.exist?(racc)
+
RACC = racc
ASSET_DIR = File.join(TEST_DIR, 'assets') # test grammars
REGRESS_DIR = File.join(TEST_DIR, 'regress') # known-good generated outputs
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 20e6ee79178632..f58f8a27789eba 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1294,6 +1294,12 @@ def test_pack
=end
end
+ def test_pack_with_buffer
+ n = [ 65, 66, 67 ]
+ str = "a" * 100
+ assert_equal("aaaABC", n.pack("@3ccc", buffer: str.dup), "[Bug #19116]")
+ end
+
def test_pop
a = @cls[ 'cat', 'dog' ]
assert_equal('dog', a.pop)
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 92a2be11748d0c..bae29a316222ea 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -210,7 +210,7 @@ def obj.tracee
end
def test_moving_arrays_down_size_pools
- omit if !GC.using_rvargc?
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
ARY_COUNT = 500
@@ -229,7 +229,8 @@ def test_moving_arrays_down_size_pools
end
def test_moving_arrays_up_size_pools
- omit if !GC.using_rvargc?
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
ARY_COUNT = 500
@@ -250,6 +251,8 @@ def test_moving_arrays_up_size_pools
end
def test_moving_objects_between_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
class Foo
@@ -274,7 +277,8 @@ def add_ivars
end
def test_moving_strings_up_size_pools
- omit if !GC.using_rvargc?
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
STR_COUNT = 500
@@ -292,7 +296,8 @@ def test_moving_strings_up_size_pools
end
def test_moving_strings_down_size_pools
- omit if !GC.using_rvargc?
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
STR_COUNT = 500
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 2204c3db09a97e..70070e93c93022 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -330,6 +330,10 @@ def test_invalidation
end
def test_read
+ # This is currently a bug in IO:Buffer [#19084] which affects extended
+ # strings. On 32 bit machines, the example below becomes extended, so
+ # we omit this test until the bug is fixed.
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
io = Tempfile.new
io.write("Hello World")
io.seek(0)
@@ -339,7 +343,7 @@ def test_read
assert_equal "Hello", buffer.get_string(0, 5)
ensure
- io.close!
+ io.close! if io
end
def test_write
diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb
index f066433f6a168d..a4beffd689e7b3 100644
--- a/test/ruby/test_rand.rb
+++ b/test/ruby/test_rand.rb
@@ -336,6 +336,14 @@ def test_seed
}
end
+ def test_seed_leading_zero_guard
+ guard = 1<<32
+ range = 0...(1<<32)
+ all_assertions_foreach(nil, 0, 1, 2) do |i|
+ assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range))
+ end
+ end
+
def test_marshal
bug3656 = '[ruby-core:31622]'
assert_raise(TypeError, bug3656) {
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index 3d3cdbb46e2054..3479a9f212049b 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -1580,16 +1580,15 @@ def assert_match_each(re, conds, msg = nil)
def test_s_timeout
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect }
begin;
- timeout = EnvUtil.apply_timeout_scale(0.2)
-
Regexp.timeout = timeout
- assert_equal(timeout, Regexp.timeout)
+ assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON)
t = Time.now
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
# A typical ReDoS case
- /^(a*)*$/ =~ "a" * 1000000 + "x"
+ /^(a*)*\1$/ =~ "a" * 1000000 + "x"
end
t = Time.now - t
@@ -1622,17 +1621,18 @@ def test_s_timeout_corner_cases
def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
- global_timeout = #{ global_timeout.inspect }
- per_instance_timeout = #{ per_instance_timeout.inspect }
- expected_timeout = #{ expected_timeout.inspect }
+ global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
+ per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect }
+ expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect }
begin;
- global_timeout = EnvUtil.apply_timeout_scale(global_timeout)
- per_instance_timeout = EnvUtil.apply_timeout_scale(per_instance_timeout)
-
Regexp.timeout = global_timeout
- re = Regexp.new("^a*b?a*$", timeout: per_instance_timeout)
- assert_equal(per_instance_timeout, re.timeout)
+ re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout)
+ if per_instance_timeout
+ assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON)
+ else
+ assert_nil(re.timeout)
+ end
t = Time.now
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
@@ -1673,4 +1673,24 @@ def test_timeout_corner_cases
assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
end;
end
+
+ def test_cache_optimization_exponential
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(2).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_cache_optimization_square
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(2).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
end
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb
index 23696acc703b3b..326ff3a4537391 100644
--- a/test/ruby/test_shapes.rb
+++ b/test/ruby/test_shapes.rb
@@ -86,15 +86,10 @@ def test_iv_index
assert_equal(2, bar_shape.next_iv_index)
end
- def test_new_obj_has_root_shape
- assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(Object.new))
- end
+ class TestObject; end
- def test_frozen_new_obj_has_frozen_root_shape
- assert_shape_equal(
- RubyVM::Shape.frozen_root_shape,
- RubyVM::Shape.of(Object.new.freeze)
- )
+ def test_new_obj_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(TestObject.new))
end
def test_str_has_root_shape
@@ -109,12 +104,12 @@ def test_hash_has_root_shape
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of({}))
end
- def test_true_has_frozen_root_shape
- assert_shape_equal(RubyVM::Shape.frozen_root_shape, RubyVM::Shape.of(true))
+ def test_true_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id)
end
- def test_nil_has_frozen_root_shape
- assert_shape_equal(RubyVM::Shape.frozen_root_shape, RubyVM::Shape.of(nil))
+ def test_nil_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id)
end
def test_basic_shape_transition
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index f6156a16fdb949..8ca23e7d025733 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -1515,4 +1515,12 @@ def test_signal_at_join
end
};
end
+
+ def test_pending_interrupt?
+ t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } }
+ t.raise(StandardError)
+ assert_equal(true, t.pending_interrupt?)
+ assert_equal(true, t.pending_interrupt?(Exception))
+ assert_equal(false, t.pending_interrupt?(ArgumentError))
+ end
end
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index 6ae12dea5de622..6fdb95bafe7748 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -612,6 +612,11 @@ def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset)
assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)}
end
+ def subtest_hour24(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2000, 1, 1, 24, 0, 0, tzarg)
+ assert_equal([0, 0, 0, 2, 1, 2000], [t.sec, t.min, t.hour, t.mday, t.mon, t.year])
+ end
+
def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset)
t = time_class.now(in: tzarg)
assert_equal(tz, t.zone)
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 1a564889af6963..fab8768a7e4692 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -918,6 +918,54 @@ def test_code_gc_with_many_iseqs
RUBY
end
+ def test_trace_script_compiled # not ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @eval_counter = 0
+ def eval_script
+ eval('@eval_counter += 1')
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:script_compiled) do |t|
+ @trace_counter += 1
+ end
+
+ eval_script # JIT without TracePoint
+ trace.enable
+ eval_script # call with TracePoint
+ trace.disable
+
+ return :"eval_#{@eval_counter}" if @eval_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_b_call # ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @call_counter = 0
+ def block_call
+ 1.times { @call_counter += 1 }
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:b_call) do |t|
+ @trace_counter += 1
+ end
+
+ block_call # JIT without TracePoint
+ trace.enable
+ block_call # call with TracePoint
+ trace.disable
+
+ return :"call_#{@call_counter}" if @call_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
private
def code_gc_helpers
diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb
index ae89d669fe8939..8d94f82d8e0454 100644
--- a/test/rubygems/helper.rb
+++ b/test/rubygems/helper.rb
@@ -307,8 +307,10 @@ def setup
ENV["XDG_CACHE_HOME"] = nil
ENV["XDG_CONFIG_HOME"] = nil
ENV["XDG_DATA_HOME"] = nil
+ ENV["XDG_STATE_HOME"] = nil
ENV["SOURCE_DATE_EPOCH"] = nil
ENV["BUNDLER_VERSION"] = nil
+ ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true"
@current_dir = Dir.pwd
@fetcher = nil
@@ -326,6 +328,7 @@ def setup
@gemhome = File.join @tempdir, "gemhome"
@userhome = File.join @tempdir, "userhome"
+ @statehome = File.join @tempdir, "statehome"
ENV["GEM_SPEC_CACHE"] = File.join @tempdir, "spec_cache"
@orig_ruby = if ENV["RUBY"]
@@ -360,6 +363,7 @@ def setup
Gem.instance_variable_set :@user_home, nil
Gem.instance_variable_set :@config_home, nil
Gem.instance_variable_set :@data_home, nil
+ Gem.instance_variable_set :@state_home, @statehome
Gem.instance_variable_set :@gemdeps, nil
Gem.instance_variable_set :@env_requirements_by_name, nil
Gem.send :remove_instance_variable, :@ruby_version if
diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb
index 7a58bcd7cb916d..14bddec485eb77 100644
--- a/test/rubygems/test_gem_commands_install_command.rb
+++ b/test/rubygems/test_gem_commands_install_command.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require_relative "helper"
+require_relative "test_gem_update_suggestion"
require "rubygems/commands/install_command"
require "rubygems/request_set"
require "rubygems/rdoc"
@@ -1550,4 +1551,22 @@ def test_explain_platform_ruby_ignore_dependencies
assert_equal " a-3", out.shift
assert_empty out
end
+
+ def test_suggest_update_if_enabled
+ TestUpdateSuggestion.with_eglible_environment(cmd: @cmd) do
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2
+ end
+
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_includes @ui.output, "A new release of RubyGems is available: 1.2.3 → 2.0.0!"
+ end
+ end
end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock
index aa975b1cd02d05..08c97618fd1e0b 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock
@@ -160,18 +160,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.35"
+version = "0.9.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d2bde30824a18f2e68cd1c8004cec16656764c6efc385bc1c7fb4c904b276a5"
+checksum = "5ba942b6777ea18ded013b267023a9c98994557e6539e43740de9e75084cb124"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.35"
+version = "0.9.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ff5d3ba92624df9c66bf0d1f0251d96284f08ac9773b7723d370e3f225c1d38"
+checksum = "d35109e1a11ef8d1a988db242ab2ba2e80170f9f5a28f88ab30184a2cea8e09b"
dependencies = [
"bindgen",
"linkify",
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml
index 6673f784645b9a..8175b5ae2e86ff 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-rb-sys = { version = "0.9.35", features = ["gem"] }
+rb-sys = "0.9.37"
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 e7e91de57674cc..eb71f255700498 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
@@ -153,18 +153,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.35"
+version = "0.9.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d2bde30824a18f2e68cd1c8004cec16656764c6efc385bc1c7fb4c904b276a5"
+checksum = "5ba942b6777ea18ded013b267023a9c98994557e6539e43740de9e75084cb124"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.35"
+version = "0.9.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ff5d3ba92624df9c66bf0d1f0251d96284f08ac9773b7723d370e3f225c1d38"
+checksum = "d35109e1a11ef8d1a988db242ab2ba2e80170f9f5a28f88ab30184a2cea8e09b"
dependencies = [
"bindgen",
"linkify",
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 814afe10f72f2d..766346757d204b 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 = { version = "0.9.35", features = ["gem"] }
+rb-sys = "0.9.37"
diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb
index ecb902ddbe8f08..2fbf5c817736e1 100644
--- a/test/rubygems/test_gem_platform.rb
+++ b/test/rubygems/test_gem_platform.rb
@@ -85,9 +85,8 @@ def test_self_new
def test_initialize
test_cases = {
- "amd64-freebsd6" => ["amd64", "freebsd", "6"],
- "hppa2.0w-hpux11.31" => ["hppa2.0w", "hpux", "11"],
- "java" => [nil, "java", nil],
+ "amd64-freebsd6" => ["amd64", "freebsd", "6"],
+ "java" => [nil, "java", nil],
"jruby" => [nil, "java", nil],
"universal-dotnet" => ["universal", "dotnet", nil],
"universal-dotnet2.0" => ["universal", "dotnet", "2.0"],
diff --git a/test/rubygems/test_gem_request_set_gem_dependency_api.rb b/test/rubygems/test_gem_request_set_gem_dependency_api.rb
index d1411ddc5643c8..5fd2bbb9c2f399 100644
--- a/test/rubygems/test_gem_request_set_gem_dependency_api.rb
+++ b/test/rubygems/test_gem_request_set_gem_dependency_api.rb
@@ -183,7 +183,7 @@ def test_gem_github
assert_equal [dep("a")], @set.dependencies
- assert_equal %w[git://github.com/example/repository.git master],
+ assert_equal %w[https://github.com/example/repository.git master],
@git_set.repositories["a"]
expected = { "a" => Gem::Requirement.create("!") }
@@ -196,7 +196,7 @@ def test_gem_github_expand_path
assert_equal [dep("a")], @set.dependencies
- assert_equal %w[git://github.com/example/example.git master],
+ assert_equal %w[https://github.com/example/example.git master],
@git_set.repositories["a"]
expected = { "a" => Gem::Requirement.create("!") }
diff --git a/test/rubygems/test_gem_update_suggestion.rb b/test/rubygems/test_gem_update_suggestion.rb
new file mode 100644
index 00000000000000..520a69ac69015f
--- /dev/null
+++ b/test/rubygems/test_gem_update_suggestion.rb
@@ -0,0 +1,208 @@
+# frozen_string_literal: true
+require_relative "helper"
+require "rubygems/command"
+require "rubygems/update_suggestion"
+
+class TestUpdateSuggestion < Gem::TestCase
+ def setup
+ super
+
+ @cmd = Gem::Command.new "dummy", "dummy"
+ @cmd.extend Gem::UpdateSuggestion
+ @start_time = 1_000_000
+ @minute = 60 * 60
+ @week = 7 * 24 * @minute
+ end
+
+ def with_eglible_environment(**params)
+ self.class.with_eglible_environment(**params) do
+ yield
+ end
+ end
+
+ def self.with_eglible_environment(
+ tty: true,
+ rubygems_version: Gem::Version.new("1.2.3"),
+ latest_rubygems_version: Gem::Version.new("2.0.0"),
+ ci: false,
+ reset_last_update_check: true,
+ cmd:
+ )
+ original_config, Gem.configuration[:prevent_update_suggestion] = Gem.configuration[:prevent_update_suggestion], nil
+ original_env, ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"], nil
+ original_disable, Gem.disable_system_update_message = Gem.disable_system_update_message, nil
+ Gem.configuration.last_update_check = 0 if reset_last_update_check
+
+ Gem.ui.stub :tty?, tty do
+ Gem.stub :rubygems_version, rubygems_version do
+ Gem.stub :latest_rubygems_version, latest_rubygems_version do
+ cmd.stub :ci?, ci do
+ yield
+ end
+ end
+ end
+ end
+ ensure
+ Gem.configuration[:prevent_update_suggestion] = original_config
+ ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = original_env
+ Gem.disable_system_update_message = original_disable
+ end
+
+ def test_update_suggestion
+ Gem.stub :rubygems_version, Gem::Version.new("1.2.3") do
+ Gem.stub :latest_rubygems_version, Gem::Version.new("2.0.0") do
+ assert_equal @cmd.update_suggestion, <<~SUGGESTION
+
+ A new release of RubyGems is available: 1.2.3 → 2.0.0!
+ Run `gem update --system 2.0.0` to update your installation.
+
+ SUGGESTION
+ end
+ end
+ end
+
+ def test_eglible_for_update
+ with_eglible_environment(cmd: @cmd) do
+ Time.stub :now, 123456789 do
+ assert @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, 123456789
+
+ # test last check is written to config file
+ assert File.read(Gem.configuration.state_file_name).match("123456789")
+ end
+ end
+ end
+
+ def test_eglible_for_update_is_not_annoying_when_new_version_is_released
+ current_version = Gem::Version.new("1.2.0")
+ latest_version = current_version
+
+ # checking for first time, it is not eglible since new version
+ # is not released yet and stored
+ with_eglible_environment(cmd: @cmd, rubygems_version: current_version, latest_rubygems_version: latest_version) do
+ Time.stub :now, @start_time do
+ refute @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time
+ end
+ end
+
+ # checking next week, it is not eglible since new version
+ # is not released yet and timestamp is stored
+ with_eglible_environment(
+ cmd: @cmd,
+ rubygems_version: current_version,
+ latest_rubygems_version: latest_version,
+ reset_last_update_check: false
+ ) do
+ Time.stub :now, @start_time + @week do
+ refute @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time + @week
+ end
+ end
+
+ # pretend new version is released
+ latest_version = Gem::Version.new("1.3.0")
+
+ # checking later same next week, it is not eglible even new version
+ # is released and timestamp is not stored
+ with_eglible_environment(
+ cmd: @cmd,
+ rubygems_version: current_version,
+ latest_rubygems_version: latest_version,
+ reset_last_update_check: false
+ ) do
+ Time.stub :now, @start_time + @week + @minute do
+ refute @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time + @week
+ end
+ end
+ end
+
+ def test_eglible_for_update_is_not_annoying_when_not_upgraded
+ with_eglible_environment(cmd: @cmd) do
+ # checking for first time, it is eglible and stored
+ Time.stub :now, @start_time do
+ assert @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time
+ end
+
+ # checking minute later is not eglible and not stored
+ Time.stub :now, @start_time + @minute do
+ refute @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time
+ end
+
+ # checking week later is eglible again and stored
+ Time.stub :now, @start_time + @week do
+ assert @cmd.eglible_for_update?
+ assert_equal Gem.configuration.last_update_check, @start_time + @week
+ end
+ end
+ end
+
+ def test_eglible_for_update_prevent_config
+ with_eglible_environment(cmd: @cmd) do
+ begin
+ original_config, Gem.configuration[:prevent_update_suggestion] = Gem.configuration[:prevent_update_suggestion], true
+ refute @cmd.eglible_for_update?
+ ensure
+ Gem.configuration[:prevent_update_suggestion] = original_config
+ end
+ end
+ end
+
+ def test_eglible_for_update_prevent_env
+ with_eglible_environment(cmd: @cmd) do
+ begin
+ original_env, ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"], "yes"
+ refute @cmd.eglible_for_update?
+ ensure
+ ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = original_env
+ end
+ end
+ end
+
+ def test_eglible_for_update_non_tty
+ with_eglible_environment(tty: false, cmd: @cmd) do
+ refute @cmd.eglible_for_update?
+ end
+ end
+
+ def test_eglible_for_update_for_prerelease
+ with_eglible_environment(rubygems_version: Gem::Version.new("1.0.0-rc1"), cmd: @cmd) do
+ refute @cmd.eglible_for_update?
+ end
+ end
+
+ def test_eglible_for_update_disabled_update
+ with_eglible_environment(cmd: @cmd) do
+ begin
+ original_disable, Gem.disable_system_update_message = Gem.disable_system_update_message, "disabled"
+ refute @cmd.eglible_for_update?
+ ensure
+ Gem.disable_system_update_message = original_disable
+ end
+ end
+ end
+
+ def test_eglible_for_update_on_ci
+ with_eglible_environment(ci: true, cmd: @cmd) do
+ refute @cmd.eglible_for_update?
+ end
+ end
+
+ def test_eglible_for_update_unwrittable_config
+ with_eglible_environment(cmd: @cmd) do
+ Gem.configuration.stub :state_file_writable?, false do
+ refute @cmd.eglible_for_update?
+ end
+ end
+ end
+
+ def test_eglible_for_update_notification_delay
+ with_eglible_environment(cmd: @cmd) do
+ Gem.configuration.last_update_check = Time.now.to_i
+ refute @cmd.eglible_for_update?
+ end
+ end
+end
diff --git a/thread.c b/thread.c
index 91060c3a4af8a6..89d4a910c14fa0 100644
--- a/thread.c
+++ b/thread.c
@@ -1933,7 +1933,7 @@ rb_threadptr_pending_interrupt_include_p(rb_thread_t *th, VALUE err)
int i;
for (i=0; ipending_interrupt_queue); i++) {
VALUE e = RARRAY_AREF(th->pending_interrupt_queue, i);
- if (rb_class_inherited_p(e, err)) {
+ if (rb_obj_is_kind_of(e, err)) {
return TRUE;
}
}
diff --git a/thread_sync.c b/thread_sync.c
index 2bcf59137eea97..2f43896cfb2ac0 100644
--- a/thread_sync.c
+++ b/thread_sync.c
@@ -327,6 +327,10 @@ do_mutex_lock(VALUE self, int interruptible_p)
}
}
else {
+ if (!th->vm->thread_ignore_deadlock && rb_fiber_threadptr(mutex->fiber) == th) {
+ rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread");
+ }
+
enum rb_thread_status prev_status = th->status;
rb_hrtime_t *timeout = 0;
rb_hrtime_t rel = rb_msec2hrtime(100);
diff --git a/time.c b/time.c
index b16a0865378ce7..b3604dd48874af 100644
--- a/time.c
+++ b/time.c
@@ -2331,6 +2331,19 @@ find_timezone(VALUE time, VALUE zone)
return rb_check_funcall_default(klass, id_find_timezone, 1, &zone, Qnil);
}
+/* Turn the special case 24:00:00 of already validated vtm into
+ * 00:00:00 the next day */
+static void
+vtm_day_wraparound(struct vtm *vtm)
+{
+ if (vtm->hour < 24) return;
+
+ /* Assuming UTC and no care of DST, just reset hour and advance
+ * date, not to discard the validated vtm. */
+ vtm->hour = 0;
+ vtm_add_day(vtm, 1);
+}
+
static VALUE
time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VALUE mday, VALUE hour, VALUE min, VALUE sec, VALUE zone)
{
@@ -2386,6 +2399,7 @@ time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VA
if (!NIL_P(zone)) {
tobj->timew = timegmw(&vtm);
+ vtm_day_wraparound(&vtm);
tobj->vtm = vtm;
tobj->tm_got = 1;
TZMODE_SET_LOCALTIME(tobj);
@@ -2400,13 +2414,7 @@ time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VA
if (utc == UTC_ZONE) {
tobj->timew = timegmw(&vtm);
- if (vtm.hour == 24) { /* special case: 24:00:00 only */
- /* Since no need to take care of DST in UTC, just reset
- * hour and advance date, not to discard the validated
- * vtm. */
- vtm.hour = 0;
- vtm_add_day(&vtm, 1);
- }
+ vtm_day_wraparound(&vtm);
tobj->vtm = vtm;
tobj->tm_got = 1;
TZMODE_SET_UTC(tobj);
@@ -4105,7 +4113,7 @@ time_inspect(VALUE time)
GetTimeval(time, tobj);
str = strftimev("%Y-%m-%d %H:%M:%S", time, rb_usascii_encoding());
subsec = w2v(wmod(tobj->timew, WINT2FIXWV(TIME_SCALE)));
- if (FIXNUM_P(subsec) && FIX2LONG(subsec) == 0) {
+ if (subsec == INT2FIX(0)) {
}
else if (FIXNUM_P(subsec) && FIX2LONG(subsec) < TIME_SCALE) {
long len;
diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb
index 59409f016c85be..0449fa215a0078 100644
--- a/tool/lib/test/unit.rb
+++ b/tool/lib/test/unit.rb
@@ -286,10 +286,16 @@ def non_options(files, options)
@jobserver = nil
makeflags = ENV.delete("MAKEFLAGS")
if !options[:parallel] and
- /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ makeflags
+ /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags
begin
- r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
- w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
+ if fifo = $3
+ fifo.gsub!(/\\(?=.)/, '')
+ r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY)
+ w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY)
+ else
+ r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
+ w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
+ end
rescue
r.close if r
nil
diff --git a/tool/pure_parser.rb b/tool/pure_parser.rb
deleted file mode 100755
index 21c87cc5d61b80..00000000000000
--- a/tool/pure_parser.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/ruby -pi.bak
-BEGIN {
- # pathological setting
- ENV['LANG'] = ENV['LC_MESSAGES'] = ENV['LC_ALL'] = 'C'
-
- require_relative 'lib/colorize'
-
- colorize = Colorize.new
- file = ARGV.shift
- begin
- version = IO.popen(ARGV+%w[--version], "rb", &:read)
- rescue Errno::ENOENT
- abort "Failed to run `#{colorize.fail ARGV.join(' ')}'; You may have to install it."
- end
- unless /\Abison .* (\d+)\.\d+/ =~ version
- puts colorize.fail("not bison")
- exit
- end
- exit if $1.to_i >= 3
- ARGV.clear
- ARGV.push(file)
-}
-$_.sub!(/^%define\s+api\.pure/, '%pure-parser')
-$_.sub!(/^%define\s+.*/, '')
diff --git a/variable.c b/variable.c
index fd0c30b9b53315..93abdac454752d 100644
--- a/variable.c
+++ b/variable.c
@@ -1092,7 +1092,7 @@ rb_generic_shape_id(VALUE obj)
shape_id = ivtbl->shape_id;
}
else if (OBJ_FROZEN(obj)) {
- shape_id = FROZEN_ROOT_SHAPE_ID;
+ shape_id = SPECIAL_CONST_SHAPE_ID;
}
}
RB_VM_LOCK_LEAVE();
@@ -1183,7 +1183,8 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
shape_id = ivtbl->shape_id;
#endif
ivar_list = ivtbl->ivptr;
- } else {
+ }
+ else {
return undef;
}
break;
@@ -1345,7 +1346,7 @@ rb_obj_transient_heap_evacuate(VALUE obj, int promote)
if (ROBJ_TRANSIENT_P(obj)) {
assert(!RB_FL_TEST_RAW(obj, ROBJECT_EMBED));
- uint32_t len = ROBJECT_NUMIV(obj);
+ uint32_t len = ROBJECT_IV_CAPACITY(obj);
const VALUE *old_ptr = ROBJECT_IVPTR(obj);
VALUE *new_ptr;
@@ -1363,26 +1364,20 @@ rb_obj_transient_heap_evacuate(VALUE obj, int promote)
#endif
void
-rb_ensure_iv_list_size(VALUE obj, uint32_t len, uint32_t newsize)
+rb_ensure_iv_list_size(VALUE obj, uint32_t current_capacity, uint32_t new_capacity)
{
VALUE *ptr = ROBJECT_IVPTR(obj);
VALUE *newptr;
if (RBASIC(obj)->flags & ROBJECT_EMBED) {
- newptr = obj_ivar_heap_alloc(obj, newsize);
- MEMCPY(newptr, ptr, VALUE, len);
+ newptr = obj_ivar_heap_alloc(obj, new_capacity);
+ MEMCPY(newptr, ptr, VALUE, current_capacity);
RB_FL_UNSET_RAW(obj, ROBJECT_EMBED);
ROBJECT(obj)->as.heap.ivptr = newptr;
}
else {
- newptr = obj_ivar_heap_realloc(obj, len, newsize);
+ newptr = obj_ivar_heap_realloc(obj, current_capacity, new_capacity);
}
-
-#if USE_RVARGC
- ROBJECT(obj)->numiv = newsize;
-#else
- ROBJECT(obj)->as.heap.numiv = newsize;
-#endif
}
struct gen_ivtbl *
@@ -1406,12 +1401,18 @@ rb_ensure_generic_iv_list_size(VALUE obj, uint32_t newsize)
}
// @note May raise when there are too many instance variables.
-void
-rb_init_iv_list(VALUE obj)
+rb_shape_t *
+rb_grow_iv_list(VALUE obj)
{
- uint32_t newsize = (uint32_t)(rb_shape_get_shape(obj)->next_iv_index * 2.0);
- uint32_t len = ROBJECT_NUMIV(obj);
- rb_ensure_iv_list_size(obj, len, newsize < len ? len : newsize);
+ rb_shape_t * initial_shape = rb_shape_get_shape(obj);
+ uint32_t len = initial_shape->capacity;
+ RUBY_ASSERT(len > 0);
+ uint32_t newsize = (uint32_t)(len * 2);
+ rb_ensure_iv_list_size(obj, len, newsize);
+
+ rb_shape_t * res = rb_shape_transition_shape_capa(initial_shape, newsize);
+ rb_shape_set_shape(obj, res);
+ return res;
}
static VALUE
@@ -1422,23 +1423,26 @@ obj_ivar_set(VALUE obj, ID id, VALUE val)
// Get the current shape
rb_shape_t * shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj));
+ bool found = true;
if (!rb_shape_get_iv_index(shape, id, &index)) {
- shape = rb_shape_get_next(shape, obj, id);
- index = shape->next_iv_index - 1;
+ index = shape->next_iv_index;
+ found = false;
}
- uint32_t len = ROBJECT_NUMIV(obj);
-
// Reallocating can kick off GC. We can't set the new shape
// on this object until the buffer has been allocated, otherwise
// GC could read off the end of the buffer.
- if (len <= index) {
- uint32_t newsize = (uint32_t)((len + 1) * 1.25);
- rb_ensure_iv_list_size(obj, len, newsize);
+ if (shape->capacity <= index) {
+ shape = rb_grow_iv_list(obj);
+ }
+
+ if (!found) {
+ shape = rb_shape_get_next(shape, obj, id);
+ RUBY_ASSERT(index == (shape->next_iv_index - 1));
+ rb_shape_set_shape(obj, shape);
}
RB_OBJ_WRITE(obj, &ROBJECT_IVPTR(obj)[index], val);
- rb_shape_set_shape(obj, shape);
return val;
}
@@ -1474,7 +1478,7 @@ rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id)
RCLASS_EXT(obj)->shape_id = shape_id;
break;
default:
- if (shape_id != FROZEN_ROOT_SHAPE_ID) {
+ if (shape_id != SPECIAL_CONST_SHAPE_ID) {
struct gen_ivtbl *ivtbl = 0;
RB_VM_LOCK_ENTER();
{
@@ -1579,7 +1583,7 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu
case SHAPE_ROOT:
return;
case SHAPE_IVAR:
- iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), callback, itr_data);
+ iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data);
VALUE * iv_list;
switch (BUILTIN_TYPE(itr_data->obj)) {
case T_OBJECT:
@@ -1598,9 +1602,11 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu
callback(shape->edge_name, val, itr_data->arg);
}
return;
- case SHAPE_IVAR_UNDEF:
+ case SHAPE_INITIAL_CAPACITY:
+ case SHAPE_CAPACITY_CHANGE:
case SHAPE_FROZEN:
- iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), callback, itr_data);
+ case SHAPE_IVAR_UNDEF:
+ iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data);
return;
}
}
@@ -2051,7 +2057,8 @@ autoload_data(VALUE mod, ID id)
if (RB_TYPE_P(mod, T_ICLASS)) {
if (FL_TEST_RAW(mod, RICLASS_IS_ORIGIN)) {
return 0;
- } else {
+ }
+ else {
mod = RBASIC(mod)->klass;
}
}
@@ -2262,7 +2269,8 @@ autoload_table_lookup_or_create(VALUE module)
VALUE autoload_table_value = rb_ivar_lookup(module, autoload, 0);
if (autoload_table_value) {
return check_autoload_table(autoload_table_value);
- } else {
+ }
+ else {
autoload_table_value = TypedData_Wrap_Struct(0, &autoload_table_type, 0);
rb_class_ivar_set(module, autoload, autoload_table_value);
return (DATA_PTR(autoload_table_value) = st_init_numtable());
@@ -3492,7 +3500,8 @@ cvar_lookup_at(VALUE klass, ID id, st_data_t *v)
if (RB_TYPE_P(klass, T_ICLASS)) {
if (FL_TEST_RAW(klass, RICLASS_IS_ORIGIN)) {
return 0;
- } else {
+ }
+ else {
// check the original module
klass = RBASIC(klass)->klass;
}
@@ -3879,7 +3888,8 @@ rb_class_ivar_set(VALUE obj, ID key, VALUE value)
RCLASS_IVPTR(obj)[idx] = value;
RB_OBJ_WRITTEN(obj, Qundef, value);
- } else {
+ }
+ else {
// Creating and setting a new instance variable
// Move to a shape which fits the new ivar
@@ -3917,7 +3927,7 @@ rb_iv_tbl_copy(VALUE dst, VALUE src)
RUBY_ASSERT(rb_type(dst) == rb_type(src));
RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE));
- RUBY_ASSERT(RCLASS_SHAPE_ID(dst) == ROOT_SHAPE_ID);
+ RUBY_ASSERT(RCLASS_SHAPE_ID(dst) == ROOT_SHAPE_ID || rb_shape_get_shape_by_id(RCLASS_SHAPE_ID(dst))->type == SHAPE_INITIAL_CAPACITY);
RUBY_ASSERT(!RCLASS_IVPTR(dst));
rb_ivar_foreach(src, tbl_copy_i, dst);
diff --git a/vm.c b/vm.c
index 06c34f949bdf5a..c6e37ec8129839 100644
--- a/vm.c
+++ b/vm.c
@@ -4053,17 +4053,6 @@ Init_vm_objects(void)
if (!vm->shape_list) {
rb_memerror();
}
-
- // Root shape
- vm->root_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
- RUBY_ASSERT(rb_shape_id(vm->root_shape) == ROOT_SHAPE_ID);
-
- // Frozen root shape
- vm->frozen_root_shape = rb_shape_alloc_with_parent_id(rb_make_internal_id(), rb_shape_id(vm->root_shape));
- vm->frozen_root_shape->type = (uint8_t)SHAPE_FROZEN;
- RUBY_ASSERT(rb_shape_id(vm->frozen_root_shape) == FROZEN_ROOT_SHAPE_ID);
-
- vm->next_shape_id = 2;
}
/* Stub for builtin function when not building YJIT units*/
diff --git a/vm_core.h b/vm_core.h
index 559d5040540ccf..4f3ebcffd9077f 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -257,7 +257,7 @@ struct iseq_inline_constant_cache_entry {
};
STATIC_ASSERT(sizeof_iseq_inline_constant_cache_entry,
(offsetof(struct iseq_inline_constant_cache_entry, ic_cref) +
- sizeof(const rb_cref_t *)) <= sizeof(struct RObject));
+ sizeof(const rb_cref_t *)) <= RVALUE_SIZE);
struct iseq_inline_constant_cache {
struct iseq_inline_constant_cache_entry *entry;
@@ -695,7 +695,6 @@ typedef struct rb_vm_struct {
/* object shapes */
rb_shape_t *shape_list;
rb_shape_t *root_shape;
- rb_shape_t *frozen_root_shape;
shape_id_t next_shape_id;
/* load */
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index cff4b9138a6ead..a39b6f87ccc941 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -50,11 +50,6 @@ MJIT_STATIC VALUE
ruby_vm_special_exception_copy(VALUE exc)
{
VALUE e = rb_obj_alloc(rb_class_real(RBASIC_CLASS(exc)));
- rb_shape_t * shape = rb_shape_get_shape(exc);
- if (rb_shape_frozen_shape_p(shape)) {
- shape = rb_shape_get_shape_by_id(shape->parent_id);
- }
- rb_shape_set_shape(e, shape);
rb_obj_copy_ivar(e, exc);
return e;
}
@@ -1306,41 +1301,37 @@ vm_setivar_slowpath(VALUE obj, ID id, VALUE val, const rb_iseq_t *iseq, IVC ic,
attr_index_t index;
- uint32_t num_iv = ROBJECT_NUMIV(obj);
rb_shape_t* shape = rb_shape_get_shape(obj);
+ uint32_t num_iv = shape->capacity;
shape_id_t next_shape_id = ROBJECT_SHAPE_ID(obj);
- rb_shape_t* next_shape = rb_shape_get_next(shape, obj, id);
+ if (!rb_shape_get_iv_index(shape, id, &index)) {
+ if (UNLIKELY(shape->next_iv_index >= num_iv)) {
+ RUBY_ASSERT(shape->next_iv_index == num_iv);
- if (shape != next_shape) {
- RUBY_ASSERT(next_shape->parent_id == rb_shape_id(shape));
- next_shape_id = rb_shape_id(next_shape);
- }
+ shape = rb_grow_iv_list(obj);
+ RUBY_ASSERT(shape->type == SHAPE_CAPACITY_CHANGE);
+ }
+
+ index = shape->next_iv_index;
- if (rb_shape_get_iv_index(next_shape, id, &index)) { // based off the hash stored in the transition tree
if (index >= MAX_IVARS) {
rb_raise(rb_eArgError, "too many instance variables");
}
- populate_cache(index, next_shape_id, id, iseq, ic, cc, is_attr);
- }
- else {
- rb_bug("Didn't find instance variable %s\n", rb_id2name(id));
- }
-
- // Ensure the IV buffer is wide enough to store the IV
- if (UNLIKELY(index >= num_iv)) {
- RUBY_ASSERT(index == num_iv);
- rb_init_iv_list(obj);
- }
+ rb_shape_t * next_shape = rb_shape_get_next(shape, obj, id);
+ RUBY_ASSERT(next_shape->type == SHAPE_IVAR);
+ RUBY_ASSERT(index == (next_shape->next_iv_index - 1));
+ next_shape_id = rb_shape_id(next_shape);
- if (shape != next_shape) {
rb_shape_set_shape(obj, next_shape);
}
+
+ populate_cache(index, next_shape_id, id, iseq, ic, cc, is_attr);
+
VALUE *ptr = ROBJECT_IVPTR(obj);
RB_OBJ_WRITE(obj, &ptr[index], val);
RB_DEBUG_COUNTER_INC(ivar_set_ic_miss_iv_hit);
-
return val;
}
case T_CLASS:
@@ -1450,17 +1441,16 @@ vm_setivar(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_index_t i
else if (dest_shape_id != INVALID_SHAPE_ID) {
rb_shape_t *dest_shape = rb_shape_get_shape_by_id(dest_shape_id);
shape_id_t source_shape_id = dest_shape->parent_id;
- if (shape_id == source_shape_id && dest_shape->edge_name == id && dest_shape->type == SHAPE_IVAR) {
+
+ RUBY_ASSERT(dest_shape->type == SHAPE_IVAR || dest_shape->type == SHAPE_IVAR_UNDEF);
+
+ if (shape_id == source_shape_id && dest_shape->edge_name == id) {
RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID);
- if (UNLIKELY(index >= ROBJECT_NUMIV(obj))) {
- rb_init_iv_list(obj);
- }
ROBJECT_SET_SHAPE_ID(obj, dest_shape_id);
- RUBY_ASSERT(rb_shape_get_next(rb_shape_get_shape_by_id(source_shape_id), obj, id) == dest_shape);
- RUBY_ASSERT(index < ROBJECT_NUMIV(obj));
-
+ RUBY_ASSERT(rb_shape_get_next_iv_shape(rb_shape_get_shape_by_id(source_shape_id), id) == dest_shape);
+ RUBY_ASSERT(index < dest_shape->capacity);
}
else {
break;
diff --git a/vm_trace.c b/vm_trace.c
index 93a8c1a4ed180f..caed71e96f72fb 100644
--- a/vm_trace.c
+++ b/vm_trace.c
@@ -87,8 +87,9 @@ update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events
{
rb_event_flag_t new_iseq_events = new_events & ISEQ_TRACE_EVENTS;
rb_event_flag_t enabled_iseq_events = ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS;
+ bool trace_iseq_p = new_iseq_events & ~enabled_iseq_events;
- if (new_iseq_events & ~enabled_iseq_events) {
+ if (trace_iseq_p) {
// :class events are triggered only in ISEQ_TYPE_CLASS, but mjit_target_iseq_p ignores such iseqs.
// Thus we don't need to cancel JIT-ed code for :class events.
if (new_iseq_events != RUBY_EVENT_CLASS) {
@@ -111,10 +112,11 @@ update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events
ruby_vm_event_enabled_global_flags |= new_events;
rb_objspace_set_event_hook(new_events);
- if (new_events & RUBY_EVENT_TRACEPOINT_ALL) {
- // Invalidate all code if listening for any TracePoint event.
+ if (trace_iseq_p) {
+ // Invalidate all code when ISEQs are modified to use trace_* insns above.
// Internal events fire inside C routines so don't need special handling.
- // Do this last so other ractors see updated vm events when they wake up.
+ // Do this after event flags updates so other ractors see updated vm events
+ // when they wake up.
rb_yjit_tracing_invalidate_all();
}
}
diff --git a/wasm/README.md b/wasm/README.md
index 050030e5143908..0f9ca1a3d587b5 100644
--- a/wasm/README.md
+++ b/wasm/README.md
@@ -54,6 +54,16 @@ $ wasmtime ruby-wasm32-wasi/usr/local/bin/ruby --mapdir /::./ruby-wasm32-wasi/ -
wasm32-wasi
```
+Note: you cannot run the built ruby without a WebAssembly runtime, because of the difference of the binary file type.
+
+```
+$ ruby-wasm32-wasi/usr/local/bin/ruby -e 'puts "a"'
+bash: ruby-wasm32-wasi/usr/local/bin/ruby: cannot execute binary file: Exec format error
+
+$ file ruby-wasm32-wasi/usr/local/bin/ruby
+ruby-wasm32-wasi/usr/local/bin/ruby: WebAssembly (wasm) binary module version 0x1 (MVP)
+```
+
## Current Limitation
- No `Thread` support for now.
diff --git a/yjit.c b/yjit.c
index b943277d61167b..aa49b3cfdca4f6 100644
--- a/yjit.c
+++ b/yjit.c
@@ -716,6 +716,15 @@ rb_get_iseq_body_param_opt_table(const rb_iseq_t *iseq)
return iseq->body->param.opt_table;
}
+VALUE
+rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler)
+{
+ rb_proc_t *proc;
+ GetProcPtr(recv, proc);
+ return rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, block_handler);
+}
+
+
// If true, the iseq is leaf and it can be replaced by a single C call.
bool
rb_leaf_invokebuiltin_iseq_p(const rb_iseq_t *iseq)
diff --git a/yjit.rb b/yjit.rb
index 71cbaf7edb96fb..caa3a035d37228 100644
--- a/yjit.rb
+++ b/yjit.rb
@@ -112,13 +112,25 @@ def self.exit_locations
#
# Usage:
#
+ # If `--yjit-exit-locations` is passed, a file named
+ # "yjit_exit_locations.dump" will automatically be generated.
+ #
+ # If you want to collect traces manually, call `dump_exit_locations`
+ # directly.
+ #
+ # Note that calling this in a script will generate stats after the
+ # dump is created, so the stats data may include exits from the
+ # dump itself.
+ #
# In a script call:
#
- # RubyVM::YJIT.dump_exit_locations("my_file.dump")
+ # at_exit do
+ # RubyVM::YJIT.dump_exit_locations("my_file.dump")
+ # end
#
# Then run the file with the following options:
#
- # ruby --yjit --yjit-stats --yjit-trace-exits test.rb
+ # ruby --yjit --yjit-trace-exits test.rb
#
# Once the code is done running, use Stackprof to read the dump file.
# See Stackprof documentation for options.
@@ -133,7 +145,33 @@ def self.dump_exit_locations(filename)
# Return a hash for statistics generated for the --yjit-stats command line option.
# Return nil when option is not passed or unavailable.
def self.runtime_stats
- Primitive.rb_yjit_get_stats
+ stats = Primitive.rb_yjit_get_stats
+ return stats if stats.nil? || !Primitive.rb_yjit_stats_enabled_p
+
+ side_exits = total_exit_count(stats)
+ total_exits = side_exits + stats[:leave_interp_return]
+
+ # Number of instructions that finish executing in YJIT.
+ # See :count-placement: about the subtraction.
+ retired_in_yjit = stats[:exec_instruction] - side_exits
+
+ # Average length of instruction sequences executed by YJIT
+ avg_len_in_yjit = retired_in_yjit.to_f / total_exits
+
+ # This only available on yjit stats builds
+ if stats.key?(:vm_insns_count)
+ # Proportion of instructions that retire in YJIT
+ total_insns_count = retired_in_yjit + stats[:vm_insns_count]
+ yjit_ratio_pct = 100.0 * retired_in_yjit.to_f / total_insns_count
+ stats[:ratio_in_yjit] = yjit_ratio_pct
+ end
+
+ # Make those stats available in RubyVM::YJIT.runtime_stats as well
+ stats[:side_exit_count] = side_exits
+ stats[:total_exit_count] = total_exits
+ stats[:avg_len_in_yjit] = avg_len_in_yjit
+
+ stats
end
# Produce disassembly for an iseq
@@ -173,12 +211,24 @@ def self.simulate_oom!
# Avoid calling a method here to not interfere with compilation tests
if Primitive.rb_yjit_stats_enabled_p
- at_exit { _print_stats }
+ at_exit do
+ _print_stats
+ _dump_locations
+ end
end
class << self
private
+ def _dump_locations
+ return unless trace_exit_locations_enabled?
+
+ filename = "yjit_exit_locations.dump"
+ dump_exit_locations(filename)
+
+ $stderr.puts("YJIT exit locations dumped to `#{filename}`.")
+ end
+
# Format and print out counters
def _print_stats
stats = runtime_stats
@@ -198,20 +248,6 @@ def _print_stats
print_counters(stats, prefix: 'opt_getinlinecache_', prompt: 'opt_getinlinecache exit reasons: ')
print_counters(stats, prefix: 'invalidate_', prompt: 'invalidation reasons: ')
- side_exits = total_exit_count(stats)
- total_exits = side_exits + stats[:leave_interp_return]
-
- # Number of instructions that finish executing in YJIT.
- # See :count-placement: about the subtraction.
- retired_in_yjit = stats[:exec_instruction] - side_exits
-
- # Average length of instruction sequences executed by YJIT
- avg_len_in_yjit = retired_in_yjit.to_f / total_exits
-
- # Proportion of instructions that retire in YJIT
- total_insns_count = retired_in_yjit + stats[:vm_insns_count]
- yjit_ratio_pct = 100.0 * retired_in_yjit.to_f / total_insns_count
-
# Number of failed compiler invocations
compilation_failure = stats[:compilation_failure]
@@ -230,14 +266,17 @@ def _print_stats
$stderr.puts "freed_page_count: " + ("%10d" % stats[:freed_page_count])
$stderr.puts "code_gc_count: " + ("%10d" % stats[:code_gc_count])
$stderr.puts "num_gc_obj_refs: " + ("%10d" % stats[:num_gc_obj_refs])
-
- $stderr.puts "side_exit_count: " + ("%10d" % side_exits)
- $stderr.puts "total_exit_count: " + ("%10d" % total_exits)
- $stderr.puts "total_insns_count: " + ("%10d" % total_insns_count)
- $stderr.puts "vm_insns_count: " + ("%10d" % stats[:vm_insns_count])
+ $stderr.puts "side_exit_count: " + ("%10d" % stats[:side_exit_count])
+ $stderr.puts "total_exit_count: " + ("%10d" % stats[:side_exit_count])
+ $stderr.puts "total_insns_count: " + ("%10d" % stats[:total_exit_count])
+ if stats.key?(:vm_insns_count)
+ $stderr.puts "vm_insns_count: " + ("%10d" % stats[:vm_insns_count])
+ end
$stderr.puts "yjit_insns_count: " + ("%10d" % stats[:exec_instruction])
- $stderr.puts "ratio_in_yjit: " + ("%9.1f" % yjit_ratio_pct) + "%"
- $stderr.puts "avg_len_in_yjit: " + ("%10.1f" % avg_len_in_yjit)
+ if stats.key?(:ratio_in_yjit)
+ $stderr.puts "ratio_in_yjit: " + ("%9.1f" % stats[:ratio_in_yjit]) + "%"
+ end
+ $stderr.puts "avg_len_in_yjit: " + ("%10.1f" % stats[:avg_len_in_yjit])
print_sorted_exit_counts(stats, prefix: "exit_")
end
diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs
index 167ab2a74f20d1..acbbaa613b4024 100644
--- a/yjit/bindgen/src/main.rs
+++ b/yjit/bindgen/src/main.rs
@@ -288,7 +288,7 @@ fn main() {
.allowlist_function("rb_yjit_get_proc_ptr")
.allowlist_function("rb_yjit_exit_locations_dict")
.allowlist_function("rb_yjit_icache_invalidate")
-
+ .allowlist_function("rb_optimized_call")
// from vm_sync.h
.allowlist_function("rb_vm_barrier")
diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs
index 3d7de7cd79bed8..4f3d20f5e5f772 100644
--- a/yjit/src/asm/mod.rs
+++ b/yjit/src/asm/mod.rs
@@ -463,6 +463,12 @@ impl CodeBlock {
self.dropped_bytes
}
+ /// To patch code that straddle pages correctly, we need to start with
+ /// the dropped bytes flag unset so we can detect when to switch to a new page.
+ pub fn set_dropped_bytes(&mut self, dropped_bytes: bool) {
+ self.dropped_bytes = dropped_bytes;
+ }
+
/// Allocate a new label with a given name
pub fn new_label(&mut self, name: String) -> usize {
assert!(!name.contains(' '), "use underscores in label names, not spaces");
@@ -563,23 +569,14 @@ impl CodeBlock {
pages_in_use[*page] = true;
}
});
- // Outlined code generated by CodegenGlobals::init() should also be kept.
- for page in CodegenGlobals::get_ocb_pages() {
- pages_in_use[*page] = true;
- }
-
- // Let VirtuamMem free the pages
- let mut freed_pages: Vec = pages_in_use.iter().enumerate()
- .filter(|&(_, &in_use)| !in_use).map(|(page, _)| page).collect();
- self.free_pages(&freed_pages);
// Avoid accumulating freed pages for future code GC
for_each_off_stack_iseq_payload(|iseq_payload: &mut IseqPayload| {
iseq_payload.pages.clear();
});
-
- // Append virtual pages in case RubyVM::YJIT.code_gc is manually triggered.
- let mut virtual_pages: Vec = (self.num_mapped_pages()..self.num_virtual_pages()).collect();
- freed_pages.append(&mut virtual_pages);
+ // Outlined code generated by CodegenGlobals::init() should also be kept.
+ for page in CodegenGlobals::get_ocb_pages() {
+ pages_in_use[*page] = true;
+ }
// Invalidate everything to have more compact code after code GC.
// This currently patches every ISEQ, which works, but in the future,
@@ -591,6 +588,17 @@ impl CodeBlock {
// can be safely reset to pass the frozen bytes check on invalidation.
CodegenGlobals::set_inline_frozen_bytes(0);
+ // Let VirtuamMem free the pages
+ let mut freed_pages: Vec = pages_in_use.iter().enumerate()
+ .filter(|&(_, &in_use)| !in_use).map(|(page, _)| page).collect();
+ // ObjectSpace API may trigger Ruby's GC, which marks gc_offsets in JIT code.
+ // So this should be called after for_each_*_iseq_payload and rb_yjit_tracing_invalidate_all.
+ self.free_pages(&freed_pages);
+
+ // Append virtual pages in case RubyVM::YJIT.code_gc is manually triggered.
+ let mut virtual_pages: Vec = (self.num_mapped_pages()..self.num_virtual_pages()).collect();
+ freed_pages.append(&mut virtual_pages);
+
if let Some(&first_page) = freed_pages.first() {
let mut cb = CodegenGlobals::get_inline_cb();
cb.write_pos = cb.get_page_pos(first_page);
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index 46f5ed64d3dd3e..421e14c553549f 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -7,7 +7,6 @@ use crate::core::*;
use crate::cruby::*;
use crate::invariants::*;
use crate::options::*;
-#[cfg(feature = "stats")]
use crate::stats::*;
use crate::utils::*;
use CodegenStatus::*;
@@ -181,12 +180,6 @@ fn jit_peek_at_block_handler(jit: &JITState, level: u32) -> VALUE {
}
}
-/// Increment a profiling counter with counter_name
-#[cfg(not(feature = "stats"))]
-macro_rules! gen_counter_incr {
- ($asm:tt, $counter_name:ident) => {};
-}
-#[cfg(feature = "stats")]
macro_rules! gen_counter_incr {
($asm:tt, $counter_name:ident) => {
if (get_option!(gen_stats)) {
@@ -204,15 +197,6 @@ macro_rules! gen_counter_incr {
};
}
-/// Increment a counter then take an existing side exit
-#[cfg(not(feature = "stats"))]
-macro_rules! counted_exit {
- ($ocb:tt, $existing_side_exit:tt, $counter_name:ident) => {{
- let _ = $ocb;
- $existing_side_exit
- }};
-}
-#[cfg(feature = "stats")]
macro_rules! counted_exit {
($ocb:tt, $existing_side_exit:tt, $counter_name:ident) => {
// The counter is only incremented when stats are enabled
@@ -422,7 +406,6 @@ fn gen_exit(exit_pc: *mut VALUE, ctx: &Context, asm: &mut Assembler) {
);
// Accumulate stats about interpreter exits
- #[cfg(feature = "stats")]
if get_option!(gen_stats) {
asm.ccall(
rb_yjit_count_side_exit_op as *const u8,
@@ -2100,14 +2083,6 @@ fn gen_get_ivar(
} else {
// Compile time value is *not* embedded.
- if USE_RVARGC == 0 {
- // Check that the extended table is big enough
- // Check that the slot is inside the extended table (num_slots > index)
- let num_slots = Opnd::mem(32, recv, ROBJECT_OFFSET_NUMIV);
- asm.cmp(num_slots, Opnd::UImm(ivar_index as u64));
- asm.jbe(counted_exit!(ocb, side_exit, getivar_idx_out_of_range).into());
- }
-
// Get a pointer to the extended table
let tbl_opnd = asm.load(Opnd::mem(64, recv, ROBJECT_OFFSET_AS_HEAP_IVPTR));
@@ -5557,8 +5532,50 @@ fn gen_send_general(
}
OPTIMIZED_METHOD_TYPE_CALL => {
- gen_counter_incr!(asm, send_optimized_method_call);
- return CantCompile;
+
+ if block.is_some() {
+ gen_counter_incr!(asm, send_call_block);
+ return CantCompile;
+ }
+
+ if flags & VM_CALL_KWARG != 0 {
+ gen_counter_incr!(asm, send_call_kwarg);
+ return CantCompile;
+ }
+
+ // Optimize for single ractor mode and avoid runtime check for
+ // "defined with an un-shareable Proc in a different Ractor"
+ if !assume_single_ractor_mode(jit, ocb) {
+ gen_counter_incr!(asm, send_call_multi_ractor);
+ return CantCompile;
+ }
+
+ // About to reset the SP, need to load this here
+ let recv_load = asm.load(recv);
+
+ let sp = asm.lea(ctx.sp_opnd(0));
+
+ // Save the PC and SP because the callee can make Ruby calls
+ jit_prepare_routine_call(jit, ctx, asm);
+
+ let kw_splat = flags & VM_CALL_KW_SPLAT;
+ let stack_argument_pointer = asm.lea(Opnd::mem(64, sp, -(argc) * SIZEOF_VALUE_I32));
+
+ let ret = asm.ccall(rb_optimized_call as *const u8, vec![
+ recv_load,
+ EC,
+ argc.into(),
+ stack_argument_pointer,
+ kw_splat.into(),
+ VM_BLOCK_HANDLER_NONE.into(),
+ ]);
+
+ ctx.stack_pop(argc as usize + 1);
+
+ let stack_ret = ctx.stack_push(Type::Unknown);
+ asm.mov(stack_ret, ret);
+ return KeepCompiling;
+
}
OPTIMIZED_METHOD_TYPE_BLOCK_CALL => {
gen_counter_incr!(asm, send_optimized_method_block_call);
diff --git a/yjit/src/core.rs b/yjit/src/core.rs
index c0e48e87b2bc20..eca58f813502ad 100644
--- a/yjit/src/core.rs
+++ b/yjit/src/core.rs
@@ -1599,18 +1599,13 @@ fn regenerate_branch(cb: &mut CodeBlock, branch: &mut Branch) {
}
*/
- let old_write_pos = cb.get_write_pos();
-
let mut block = branch.block.borrow_mut();
let branch_terminates_block = branch.end_addr == block.end_addr;
-
- // Rewrite the branch
assert!(branch.dst_addrs[0].is_some());
- cb.set_write_ptr(branch.start_addr.unwrap());
+ // Generate the branch
let mut asm = Assembler::new();
asm.comment("regenerate_branch");
-
(branch.gen_fn)(
&mut asm,
branch.dst_addrs[0].unwrap(),
@@ -1618,6 +1613,11 @@ fn regenerate_branch(cb: &mut CodeBlock, branch: &mut Branch) {
branch.shape,
);
+ // Rewrite the branch
+ let old_write_pos = cb.get_write_pos();
+ let old_dropped_bytes = cb.has_dropped_bytes();
+ cb.set_write_ptr(branch.start_addr.unwrap());
+ cb.set_dropped_bytes(false);
asm.compile(cb);
branch.end_addr = Some(cb.get_write_ptr());
@@ -1638,6 +1638,7 @@ fn regenerate_branch(cb: &mut CodeBlock, branch: &mut Branch) {
if old_write_pos > cb.get_write_pos() {
// We rewound cb->write_pos to generate the branch, now restore it.
cb.set_pos(old_write_pos);
+ cb.set_dropped_bytes(old_dropped_bytes);
} else {
// The branch sits at the end of cb and consumed some memory.
// Keep cb.write_pos.
@@ -2193,10 +2194,12 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
// Patch in a jump to block.entry_exit.
let cur_pos = cb.get_write_ptr();
+ let cur_dropped_bytes = cb.has_dropped_bytes();
cb.set_write_ptr(block_start);
let mut asm = Assembler::new();
asm.jmp(block_entry_exit.into());
+ cb.set_dropped_bytes(false);
asm.compile(&mut cb);
assert!(
@@ -2206,6 +2209,7 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
cb.get_write_ptr().into_i64() - block_start.into_i64(),
);
cb.set_write_ptr(cur_pos);
+ cb.set_dropped_bytes(cur_dropped_bytes);
}
}
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index 3373c6d76e41bc..d6218385b001cd 100644
--- a/yjit/src/cruby_bindings.inc.rs
+++ b/yjit/src/cruby_bindings.inc.rs
@@ -248,10 +248,9 @@ extern "C" {
}
pub const ROBJECT_EMBED: ruby_robject_flags = 8192;
pub type ruby_robject_flags = u32;
-pub const ROBJECT_OFFSET_NUMIV: i32 = 16;
-pub const ROBJECT_OFFSET_AS_HEAP_IVPTR: i32 = 24;
-pub const ROBJECT_OFFSET_AS_HEAP_IV_INDEX_TBL: i32 = 32;
-pub const ROBJECT_OFFSET_AS_ARY: i32 = 24;
+pub const ROBJECT_OFFSET_AS_HEAP_IVPTR: i32 = 16;
+pub const ROBJECT_OFFSET_AS_HEAP_IV_INDEX_TBL: i32 = 24;
+pub const ROBJECT_OFFSET_AS_ARY: i32 = 16;
extern "C" {
pub static mut rb_mKernel: VALUE;
}
@@ -420,7 +419,9 @@ pub struct rb_shape {
pub edges: *mut rb_id_table,
pub edge_name: ID,
pub next_iv_index: attr_index_t,
+ pub capacity: u32,
pub type_: u8,
+ pub size_pool_index: u8,
pub parent_id: shape_id_t,
}
pub type rb_shape_t = rb_shape;
@@ -1480,6 +1481,16 @@ extern "C" {
extern "C" {
pub fn rb_get_iseq_body_param_opt_table(iseq: *const rb_iseq_t) -> *const VALUE;
}
+extern "C" {
+ pub fn rb_optimized_call(
+ recv: *mut VALUE,
+ ec: *mut rb_execution_context_t,
+ argc: ::std::os::raw::c_int,
+ argv: *mut VALUE,
+ kw_splat: ::std::os::raw::c_int,
+ block_handler: VALUE,
+ ) -> VALUE;
+}
extern "C" {
pub fn rb_leaf_invokebuiltin_iseq_p(iseq: *const rb_iseq_t) -> bool;
}
diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs
index 0d8577924cf6ba..cd3214feae546d 100644
--- a/yjit/src/invariants.rs
+++ b/yjit/src/invariants.rs
@@ -558,6 +558,7 @@ pub extern "C" fn rb_yjit_tracing_invalidate_all() {
// Apply patches
let old_pos = cb.get_write_pos();
+ let old_dropped_bytes = cb.has_dropped_bytes();
let mut patches = CodegenGlobals::take_global_inval_patches();
patches.sort_by_cached_key(|patch| patch.inline_patch_pos.raw_ptr());
let mut last_patch_end = std::ptr::null();
@@ -568,10 +569,12 @@ pub extern "C" fn rb_yjit_tracing_invalidate_all() {
asm.jmp(patch.outlined_target_pos.into());
cb.set_write_ptr(patch.inline_patch_pos);
+ cb.set_dropped_bytes(false);
asm.compile(cb);
last_patch_end = cb.get_write_ptr().raw_ptr();
}
cb.set_pos(old_pos);
+ cb.set_dropped_bytes(old_dropped_bytes);
// Freeze invalidated part of the codepage. We only want to wait for
// running instances of the code to exit from now on, so we shouldn't
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs
index a60fcaf8364f84..128672a95943ac 100644
--- a/yjit/src/stats.rs
+++ b/yjit/src/stats.rs
@@ -173,6 +173,9 @@ make_counters! {
send_optimized_method,
send_optimized_method_call,
send_optimized_method_block_call,
+ send_call_block,
+ send_call_kwarg,
+ send_call_multi_ractor,
send_missing_method,
send_refined_method,
send_cfunc_ruby_array_varg,
@@ -286,12 +289,12 @@ make_counters! {
/// Check if stats generation is enabled
#[no_mangle]
pub extern "C" fn rb_yjit_stats_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
- #[cfg(feature = "stats")]
+
if get_option!(gen_stats) {
return Qtrue;
+ } else {
+ return Qfalse;
}
-
- return Qfalse;
}
/// Primitive called in yjit.rb.
@@ -401,7 +404,7 @@ fn rb_yjit_gen_stats_dict() -> VALUE {
}
// If the stats feature is enabled
- #[cfg(feature = "stats")]
+
unsafe {
// Indicate that the complete set of stats is available
rb_hash_aset(hash, rust_str_to_sym("all_stats"), Qtrue);
@@ -412,6 +415,13 @@ fn rb_yjit_gen_stats_dict() -> VALUE {
let counter_ptr = get_counter_ptr(counter_name);
let counter_val = *counter_ptr;
+ #[cfg(not(feature = "stats"))]
+ if counter_name == &"vm_insns_count" {
+ // If the stats feature is disabled, we don't have vm_insns_count
+ // so we are going to exlcude the key
+ continue;
+ }
+
// Put counter into hash
let key = rust_str_to_sym(counter_name);
let value = VALUE::fixnum_from_usize(counter_val as usize);