diff --git a/doc/string.rb b/doc/string.rb
index b3d5886b8dd718..4304b96aee7f9a 100644
--- a/doc/string.rb
+++ b/doc/string.rb
@@ -402,6 +402,7 @@
# - #to_c: Returns the complex value of leading characters, interpreted as a complex number.
# - #to_i: Returns the integer value of leading characters, interpreted as an integer.
# - #to_f: Returns the floating-point value of leading characters, interpreted as a floating-point number.
+# - #to_r: Returns the rational value of leading characters, interpreted as a rational.
#
# Strings and Symbols
#
diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index 0ebcbd789d4038..dd20d65080ff00 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -56,7 +56,7 @@ class Gem::Package::TarHeader
##
# Pack format for a tar header
- PACK_FORMAT = "a100" + # name
+ PACK_FORMAT = ("a100" + # name
"a8" + # mode
"a8" + # uid
"a8" + # gid
@@ -71,12 +71,12 @@ class Gem::Package::TarHeader
"a32" + # gname
"a8" + # devmajor
"a8" + # devminor
- "a155" # prefix
+ "a155").freeze # prefix
##
# Unpack format for a tar header
- UNPACK_FORMAT = "A100" + # name
+ UNPACK_FORMAT = ("A100" + # name
"A8" + # mode
"A8" + # uid
"A8" + # gid
@@ -91,7 +91,7 @@ class Gem::Package::TarHeader
"A32" + # gname
"A8" + # devmajor
"A8" + # devminor
- "A155" # prefix
+ "A155").freeze # prefix
attr_reader(*FIELDS)
diff --git a/rational.c b/rational.c
index 89e74c328dca0d..28f116580fb945 100644
--- a/rational.c
+++ b/rational.c
@@ -2467,31 +2467,32 @@ string_to_r_strict(VALUE self, int raise)
/*
* call-seq:
- * str.to_r -> rational
- *
- * Returns the result of interpreting leading characters in +str+
- * as a rational. Leading whitespace and extraneous characters
- * past the end of a valid number are ignored.
- * Digit sequences can be separated by an underscore.
- * If there is not a valid number at the start of +str+,
- * zero is returned. This method never raises an exception.
- *
- * ' 2 '.to_r #=> (2/1)
- * '300/2'.to_r #=> (150/1)
- * '-9.2'.to_r #=> (-46/5)
- * '-9.2e2'.to_r #=> (-920/1)
- * '1_234_567'.to_r #=> (1234567/1)
- * '21 June 09'.to_r #=> (21/1)
- * '21/06/09'.to_r #=> (7/2)
- * 'BWV 1079'.to_r #=> (0/1)
- *
- * NOTE: "0.3".to_r isn't the same as 0.3.to_r. The former is
- * equivalent to "3/10".to_r, but the latter isn't so.
+ * str.to_r -> rational
*
- * "0.3".to_r == 3/10r #=> true
- * 0.3.to_r == 3/10r #=> false
+ * Returns the result of interpreting leading characters in +self+ as a rational value:
+ *
+ * '123'.to_r # => (123/1) # Integer literal.
+ * '300/2'.to_r # => (150/1) # Rational literal.
+ * '-9.2'.to_r # => (-46/5) # Float literal.
+ * '-9.2e2'.to_r # => (-920/1) # Float literal.
+ *
+ * Ignores leading and trailing whitespace, and trailing non-numeric characters:
+ *
+ * ' 2 '.to_r # => (2/1)
+ * '21-Jun-09'.to_r # => (21/1)
+ *
+ * Returns \Rational zero if there are no leading numeric characters.
+ *
+ * 'BWV 1079'.to_r # => (0/1)
+ *
+ * NOTE: '0.3'.to_r is equivalent to 3/10r,
+ * but is different from 0.3.to_r:
+ *
+ * '0.3'.to_r # => (3/10)
+ * 3/10r # => (3/10)
+ * 0.3.to_r # => (5404319552844595/18014398509481984)
*
- * See also Kernel#Rational.
+ * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString].
*/
static VALUE
string_to_r(VALUE self)
diff --git a/test/rubygems/test_gem_package_tar_header_ractor.rb b/test/rubygems/test_gem_package_tar_header_ractor.rb
new file mode 100644
index 00000000000000..6f417f43f90cd2
--- /dev/null
+++ b/test/rubygems/test_gem_package_tar_header_ractor.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+require_relative "package/tar_test_case"
+
+class TestGemPackageTarHeaderRactor < Gem::Package::TarTestCase
+ ASSERT_HEADERS_EQUAL = <<~RUBY
+ def assert_headers_equal(expected, actual)
+ expected = expected.to_s unless String === expected
+ actual = actual.to_s unless String === actual
+
+ fields = %w[
+ name 100
+ mode 8
+ uid 8
+ gid 8
+ size 12
+ mtime 12
+ checksum 8
+ typeflag 1
+ linkname 100
+ magic 6
+ version 2
+ uname 32
+ gname 32
+ devmajor 8
+ devminor 8
+ prefix 155
+ ]
+
+ offset = 0
+
+ until fields.empty? do
+ name = fields.shift
+ length = fields.shift.to_i
+
+ if name == "checksum"
+ chksum_off = offset
+ offset += length
+ next
+ end
+
+ assert_equal expected[offset, length], actual[offset, length]
+
+ offset += length
+ end
+
+ assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
+ end
+ RUBY
+
+ SETUP = <<~RUBY
+ header = {
+ name: "x",
+ mode: 0o644,
+ uid: 1000,
+ gid: 10_000,
+ size: 100,
+ mtime: 12_345,
+ typeflag: "0",
+ linkname: "link",
+ uname: "user",
+ gname: "group",
+ devmajor: 1,
+ devminor: 2,
+ prefix: "y",
+ }
+
+ tar_header = Gem::Package::TarHeader.new header
+ RUBY
+
+ def test_decode_in_ractor
+ assert_ractor(ASSERT_HEADERS_EQUAL + SETUP + <<~RUBY, require: ["rubygems/package", "stringio"])
+ new_header = Ractor.new(tar_header.to_s) do |str|
+ Gem::Package::TarHeader.from StringIO.new str
+ end.value
+
+ assert_headers_equal tar_header, new_header
+ RUBY
+ end
+
+ def test_encode_in_ractor
+ assert_ractor(ASSERT_HEADERS_EQUAL + SETUP + <<~RUBY, require: ["rubygems/package", "stringio"])
+ header_bytes = tar_header.to_s
+
+ new_header_bytes = Ractor.new(header_bytes) do |str|
+ new_header = Gem::Package::TarHeader.from StringIO.new str
+ new_header.to_s
+ end.value
+
+ assert_headers_equal header_bytes, new_header_bytes
+ RUBY
+ end
+end
diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb
index fbf2c1a8bb3174..5d4c733a73d3be 100644
--- a/tool/lib/core_assertions.rb
+++ b/tool/lib/core_assertions.rb
@@ -339,6 +339,8 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o
args = args.dup
args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag
+ # power_assert 3 requires ruby 3.1 or later
+ args << "-W:no-experimental" if RUBY_VERSION < "3.1."
stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
if sanitizers&.lsan_enabled?
@@ -394,7 +396,11 @@ def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil,
shim_value = "class Ractor; alias value take; end" unless Ractor.method_defined?(:value)
shim_join = "class Ractor; alias join take; end" unless Ractor.method_defined?(:join)
- require = "require #{require.inspect}" if require
+ if require
+ require = [require] unless require.is_a?(Array)
+ require = require.map {|r| "require #{r.inspect}"}.join("\n")
+ end
+
if require_relative
dir = File.dirname(caller_locations[0,1][0].absolute_path)
full_path = File.expand_path(require_relative, dir)
diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb
index fef9a0c992b28c..ea1f83e6f0b3bc 100644
--- a/tool/lib/envutil.rb
+++ b/tool/lib/envutil.rb
@@ -104,9 +104,11 @@ def dump(pid, timeout: 60, reprieve: timeout&.div(4))
else
return unless dpid
[[timeout, :TERM], [reprieve, :KILL]].find do |t, sig|
- return EnvUtil.timeout(t) {Process.wait(dpid)}
- rescue Timeout::Error
- Process.kill(sig, dpid)
+ begin
+ return EnvUtil.timeout(t) {Process.wait(dpid)}
+ rescue Timeout::Error
+ Process.kill(sig, dpid)
+ end
end
true
end
diff --git a/vm_method.c b/vm_method.c
index bf04140cb7fd19..179deb749daeec 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -563,6 +563,7 @@ invalidate_ccs_in_iclass_cc_tbl(VALUE value, void *data)
{
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)value;
vm_cme_invalidate((rb_callable_method_entry_t *)ccs->cme);
+ xfree(ccs);
return ID_TABLE_DELETE;
}