diff --git a/.github/actions/compilers/action.yml b/.github/actions/compilers/action.yml
index daad5b694766f9..ab5b56a889672a 100644
--- a/.github/actions/compilers/action.yml
+++ b/.github/actions/compilers/action.yml
@@ -5,7 +5,7 @@ description: >-
inputs:
tag:
required: false
- default: clang-18
+ default: clang-20
description: >-
container image tag to use in this run.
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index 834eb627ef0e90..ed80dd08628e9d 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -2350,3 +2350,23 @@ def call_test(obj)
end
:ok
RUBY
+
+assert_equal 'ok', <<~'RUBY'
+ begin
+ 100.times do |i|
+ Ractor.new(i) do |j|
+ 1000.times do |i|
+ "#{j}-#{i}"
+ end
+ Ractor.receive
+ end
+ pid = fork { }
+ _, status = Process.waitpid2 pid
+ raise unless status.success?
+ end
+
+ :ok
+ rescue NotImplementedError
+ :ok
+ end
+RUBY
diff --git a/class.c b/class.c
index 34bc7d100538e7..2f94b801471104 100644
--- a/class.c
+++ b/class.c
@@ -183,6 +183,12 @@ rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext)
};
st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, rb_class_set_box_classext_update, (st_data_t)&args);
+
+ // FIXME: This is done here because this is the first time the objects in
+ // the classext are exposed via this class. It's likely that if GC
+ // compaction occurred between the VALUEs being copied in and this
+ // writebarrier trigger the values will be stale.
+ rb_gc_writebarrier_remember(obj);
}
RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state;
diff --git a/cont.c b/cont.c
index f885cdb1095032..d167685f13bec3 100644
--- a/cont.c
+++ b/cont.c
@@ -268,6 +268,8 @@ struct rb_fiber_struct {
unsigned int killed : 1;
+ rb_serial_t serial;
+
struct coroutine_context context;
struct fiber_pool_stack stack;
};
@@ -1010,6 +1012,13 @@ rb_fiber_threadptr(const rb_fiber_t *fiber)
return fiber->cont.saved_ec.thread_ptr;
}
+rb_serial_t
+rb_fiber_serial(const rb_fiber_t *fiber)
+{
+ VM_ASSERT(fiber->serial >= 1);
+ return fiber->serial;
+}
+
static VALUE
cont_thread_value(const rb_context_t *cont)
{
@@ -1995,6 +2004,13 @@ fiber_alloc(VALUE klass)
return TypedData_Wrap_Struct(klass, &fiber_data_type, 0);
}
+static rb_serial_t
+next_fiber_serial(void)
+{
+ static rbimpl_atomic_uint64_t fiber_serial = 1;
+ return (rb_serial_t)ATOMIC_U64_FETCH_ADD(fiber_serial, 1);
+}
+
static rb_fiber_t*
fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
{
@@ -2011,6 +2027,7 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
fiber->cont.type = FIBER_CONTEXT;
fiber->blocking = blocking;
fiber->killed = 0;
+ fiber->serial = next_fiber_serial();
cont_init(&fiber->cont, th);
fiber->cont.saved_ec.fiber_ptr = fiber;
@@ -2563,6 +2580,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th)
fiber->cont.saved_ec.thread_ptr = th;
fiber->blocking = 1;
fiber->killed = 0;
+ fiber->serial = next_fiber_serial();
fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */
th->ec = &fiber->cont.saved_ec;
cont_init_jit_cont(&fiber->cont);
diff --git a/doc/string.rb b/doc/string.rb
index b37cb5d324ec95..304ab60c298967 100644
--- a/doc/string.rb
+++ b/doc/string.rb
@@ -194,10 +194,10 @@
#
# _Counts_
#
-# - #length (aliased as #size): Returns the count of characters (not bytes).
-# - #empty?: Returns whether the length of +self+ is zero.
# - #bytesize: Returns the count of bytes.
# - #count: Returns the count of substrings matching given strings.
+# - #empty?: Returns whether the length of +self+ is zero.
+# - #length (aliased as #size): Returns the count of characters (not bytes).
#
# _Substrings_
#
diff --git a/doc/string/upcase.rdoc b/doc/string/upcase.rdoc
new file mode 100644
index 00000000000000..5a9cce217f7972
--- /dev/null
+++ b/doc/string/upcase.rdoc
@@ -0,0 +1,20 @@
+Returns a new string containing the upcased characters in +self+:
+
+ 'Hello, World!'.upcase # => "HELLO, WORLD!"
+
+The sizes of +self+ and the upcased result may differ:
+
+ s = 'Straße'
+ s.size # => 6
+ s.upcase # => "STRASSE"
+ s.upcase.size # => 7
+
+Some characters (and some character sets) do not have upcased and downcased versions:
+
+ s = 'よろしくお願いします'
+ s.upcase == s # => true
+
+The casing may be affected by the given +mapping+;
+see {Case Mapping}[rdoc-ref:case_mapping.rdoc].
+
+Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String].
diff --git a/doc/string/upto.rdoc b/doc/string/upto.rdoc
new file mode 100644
index 00000000000000..f860fe84fe6485
--- /dev/null
+++ b/doc/string/upto.rdoc
@@ -0,0 +1,38 @@
+With a block given, calls the block with each +String+ value
+returned by successive calls to String#succ;
+the first value is +self+, the next is self.succ, and so on;
+the sequence terminates when value +other_string+ is reached;
+returns +self+:
+
+ a = []
+ 'a'.upto('f') {|c| a.push(c) }
+ a # => ["a", "b", "c", "d", "e", "f"]
+
+ a = []
+ 'Ж'.upto('П') {|c| a.push(c) }
+ a # => ["Ж", "З", "И", "Й", "К", "Л", "М", "Н", "О", "П"]
+
+ a = []
+ 'よ'.upto('ろ') {|c| a.push(c) }
+ a # => ["よ", "ら", "り", "る", "れ", "ろ"]
+
+ a = []
+ 'a8'.upto('b6') {|c| a.push(c) }
+ a # => ["a8", "a9", "b0", "b1", "b2", "b3", "b4", "b5", "b6"]
+
+If argument +exclusive+ is given as a truthy object, the last value is omitted:
+
+ a = []
+ 'a'.upto('f', true) {|c| a.push(c) }
+ a # => ["a", "b", "c", "d", "e"]
+
+If +other_string+ would not be reached, does not call the block:
+
+ '25'.upto('5') {|s| fail s }
+ 'aa'.upto('a') {|s| fail s }
+
+With no block given, returns a new Enumerator:
+
+ 'a8'.upto('b6') # => #
+
+Related: see {Iterating}[rdoc-ref:String@Iterating].
diff --git a/doc/string/valid_encoding_p.rdoc b/doc/string/valid_encoding_p.rdoc
new file mode 100644
index 00000000000000..e1db55174a5428
--- /dev/null
+++ b/doc/string/valid_encoding_p.rdoc
@@ -0,0 +1,8 @@
+Returns whether +self+ is encoded correctly:
+
+ s = 'Straße'
+ s.valid_encoding? # => true
+ s.encoding # => #
+ s.force_encoding(Encoding::ASCII).valid_encoding? # => false
+
+Related: see {Querying}[rdoc-ref:String@Querying].
diff --git a/internal/cont.h b/internal/cont.h
index 3c2528a02a6e77..21a054f37c1294 100644
--- a/internal/cont.h
+++ b/internal/cont.h
@@ -31,5 +31,6 @@ VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb
VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber);
unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber);
struct rb_execution_context_struct * rb_fiberptr_get_ec(struct rb_fiber_struct *fiber);
+rb_serial_t rb_fiber_serial(const struct rb_fiber_struct *fiber);
#endif /* INTERNAL_CONT_H */
diff --git a/io_buffer.c b/io_buffer.c
index 87e392b79181d6..abe7832bee829a 100644
--- a/io_buffer.c
+++ b/io_buffer.c
@@ -667,18 +667,25 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags
* call-seq: IO::Buffer.map(file, [size, [offset, [flags]]]) -> io_buffer
*
* Create an IO::Buffer for reading from +file+ by memory-mapping the file.
- * +file_io+ should be a +File+ instance, opened for reading.
+ * +file+ should be a +File+ instance, opened for reading or reading and writing.
*
* Optional +size+ and +offset+ of mapping can be specified.
+ * Trying to map an empty file or specify +size+ of 0 will raise an error.
+ * Valid values for +offset+ are system-dependent.
*
- * By default, the buffer would be immutable (read only); to create a writable
- * mapping, you need to open a file in read-write mode, and explicitly pass
- * +flags+ argument without IO::Buffer::IMMUTABLE.
+ * By default, the buffer is writable and expects the file to be writable.
+ * It is also shared, so several processes can use the same mapping.
+ *
+ * You can pass IO::Buffer::READONLY in +flags+ argument to make a read-only buffer;
+ * this allows to work with files opened only for reading.
+ * Specifying IO::Buffer::PRIVATE in +flags+ creates a private mapping,
+ * which will not impact other processes or the underlying file.
+ * It also allows updating a buffer created from a read-only file.
*
* File.write('test.txt', 'test')
*
* buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
- * # => #
+ * # => #
*
* buffer.readonly? # => true
*
@@ -686,7 +693,7 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags
* # => "test"
*
* buffer.set_string('b', 0)
- * # `set_string': Buffer is not writable! (IO::Buffer::AccessError)
+ * # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError)
*
* # create read/write mapping: length 4 bytes, offset 0, flags 0
* buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0)
@@ -708,31 +715,48 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass)
// We might like to handle a string path?
VALUE io = argv[0];
+ rb_off_t file_size = rb_file_size(io);
+ // Compiler can confirm that we handled file_size <= 0 case:
+ if (UNLIKELY(file_size <= 0)) {
+ rb_raise(rb_eArgError, "Invalid negative or zero file size!");
+ }
+ // Here, we assume that file_size is positive:
+ else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
+ rb_raise(rb_eArgError, "File larger than address space!");
+ }
+
size_t size;
if (argc >= 2 && !RB_NIL_P(argv[1])) {
size = io_buffer_extract_size(argv[1]);
- }
- else {
- rb_off_t file_size = rb_file_size(io);
-
- // Compiler can confirm that we handled file_size < 0 case:
- if (file_size < 0) {
- rb_raise(rb_eArgError, "Invalid negative file size!");
+ if (UNLIKELY(size == 0)) {
+ rb_raise(rb_eArgError, "Size can't be zero!");
}
- // Here, we assume that file_size is positive:
- else if ((uintmax_t)file_size > SIZE_MAX) {
- rb_raise(rb_eArgError, "File larger than address space!");
- }
- else {
- // This conversion should be safe:
- size = (size_t)file_size;
+ if (UNLIKELY(size > (size_t)file_size)) {
+ rb_raise(rb_eArgError, "Size can't be larger than file size!");
}
}
+ else {
+ // This conversion should be safe:
+ size = (size_t)file_size;
+ }
// This is the file offset, not the buffer offset:
rb_off_t offset = 0;
if (argc >= 3) {
offset = NUM2OFFT(argv[2]);
+ if (UNLIKELY(offset < 0)) {
+ rb_raise(rb_eArgError, "Offset can't be negative!");
+ }
+ if (UNLIKELY(offset >= file_size)) {
+ rb_raise(rb_eArgError, "Offset too large!");
+ }
+ if (RB_NIL_P(argv[1])) {
+ // Decrease size if it's set from the actual file size:
+ size = (size_t)(file_size - offset);
+ }
+ else if (UNLIKELY((size_t)(file_size - offset) < size)) {
+ rb_raise(rb_eArgError, "Offset too large!");
+ }
}
enum rb_io_buffer_flags flags = 0;
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 79f93a7784734c..48178965697667 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -234,6 +234,7 @@ def remove(*gems)
method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead"
method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely"
method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache."
+ method_option "no-lock", type: :boolean, banner: "Don't create a lockfile."
method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)."
method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)."
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index 20e22155de8ee9..85b303eee60900 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -44,6 +44,7 @@ def run
# (rather than some optimizations we perform at app runtime).
definition = Bundler.definition(strict: true)
definition.validate_runtime!
+ definition.lockfile = false if options["no-lock"]
installer = Installer.install(Bundler.root, definition, options)
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 3c8c13b1303171..ca41d7953d8ea2 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "lockfile_parser"
+require_relative "worker"
module Bundler
class Definition
@@ -9,6 +10,8 @@ class << self
attr_accessor :no_lock
end
+ attr_writer :lockfile
+
attr_reader(
:dependencies,
:locked_checksums,
@@ -379,7 +382,7 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or
end
def write_lock(file, preserve_unknown_sections)
- return if Definition.no_lock || file.nil?
+ return if Definition.no_lock || !lockfile || file.nil?
contents = to_lock
@@ -1100,7 +1103,23 @@ def source_requirements
@source_requirements ||= find_source_requirements
end
+ def preload_git_source_worker
+ @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs })
+ end
+
+ def preload_git_sources
+ sources.git_sources.each {|source| preload_git_source_worker.enq(source) }
+ ensure
+ preload_git_source_worker.stop
+ end
+
def find_source_requirements
+ if Gem.ruby_version >= "3.3"
+ # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to
+ # fetch git repositories one by one.
+ preload_git_sources
+ end
+
# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index 998a8134f8e12b..6f06c4e918797d 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -9,8 +9,9 @@ class Dsl
def self.evaluate(gemfile, lockfile, unlock)
builder = new
+ builder.lockfile(lockfile)
builder.eval_gemfile(gemfile)
- builder.to_definition(lockfile, unlock)
+ builder.to_definition(builder.lockfile_path, unlock)
end
VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze
@@ -38,6 +39,7 @@ def initialize
@gemspecs = []
@gemfile = nil
@gemfiles = []
+ @lockfile = nil
add_git_sources
end
@@ -101,6 +103,15 @@ def gem(name, *args)
add_dependency(name, version, options)
end
+ # For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile.
+ def lockfile_path
+ @lockfile
+ end
+
+ def lockfile(file)
+ @lockfile = file
+ end
+
def source(source, *args, &blk)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
options = normalize_hash(options)
@@ -175,6 +186,7 @@ def github(repo, options = {})
def to_definition(lockfile, unlock)
check_primary_source_safety
+ lockfile = @lockfile unless @lockfile.nil?
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
end
@@ -415,8 +427,8 @@ def normalize_options(name, version, opts)
windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) }
if windows_platforms.any?
windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ")
- removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead."
- Bundler::SharedHelpers.feature_removed! removed_message
+ deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead."
+ Bundler::SharedHelpers.feature_deprecated! deprecated_message
end
# Save sources passed in a key
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index 444ab6fd373d86..bf9478a2990689 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -6,6 +6,7 @@ class EnvironmentPreserver
BUNDLER_KEYS = %w[
BUNDLE_BIN_PATH
BUNDLE_GEMFILE
+ BUNDLE_LOCKFILE
BUNDLER_VERSION
BUNDLER_SETUP
GEM_HOME
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
index 4e4b51e7a5dfb4..c861bee1496738 100644
--- a/lib/bundler/inline.rb
+++ b/lib/bundler/inline.rb
@@ -44,12 +44,14 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty?
old_gemfile = ENV["BUNDLE_GEMFILE"]
+ old_lockfile = ENV["BUNDLE_LOCKFILE"]
Bundler.unbundle_env!
begin
Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir))
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock"
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins]
builder = Bundler::Dsl.new
@@ -94,5 +96,11 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
else
ENV["BUNDLE_GEMFILE"] = ""
end
+
+ if old_lockfile
+ ENV["BUNDLE_LOCKFILE"] = old_lockfile
+ else
+ ENV["BUNDLE_LOCKFILE"] = ""
+ end
end
end
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index b44891f6e92374..05c13e2d0f3213 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -145,6 +145,9 @@ Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init
\fBjobs\fR (\fBBUNDLE_JOBS\fR)
The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
.TP
+\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR)
+The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\.
+.TP
\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR)
Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\.
.TP
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index 281ab2da0cd124..7c34f1d1afb26e 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -189,6 +189,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
* `jobs` (`BUNDLE_JOBS`):
The number of gems Bundler can install in parallel. Defaults to the number of
available processors.
+* `lockfile` (`BUNDLE_LOCKFILE`):
+ The path to the lockfile that bundler should use. By default, Bundler adds
+ `.lock` to the end of the `gemfile` entry. Can be set to `false` in the
+ Gemfile to disable lockfile creation entirely (see gemfile(5)).
* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`):
Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true.
* `no_install` (`BUNDLE_NO_INSTALL`):
diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1
index 2d7ef96b4490fd..1acbe430585e22 100644
--- a/lib/bundler/man/bundle-install.1
+++ b/lib/bundler/man/bundle-install.1
@@ -4,7 +4,7 @@
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
.SH "SYNOPSIS"
-\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG]
+\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG]
.SH "DESCRIPTION"
Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\.
.P
@@ -34,6 +34,11 @@ Force using locally installed gems, or gems already present in Rubygems' cache o
\fB\-\-no\-cache\fR
Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\.
.TP
+\fB\-\-no\-lock\fR
+Do not create a lockfile\. Useful if you want to install dependencies but not lock versions of gems\. Recommended for library development, and other situations where the code is expected to work with a range of dependency versions\.
+.IP
+This has the same effect as using \fBlockfile false\fR in the Gemfile\. See gemfile(5) for more information\.
+.TP
\fB\-\-quiet\fR
Do not print progress information to the standard output\.
.TP
diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn
index b946cbf8322917..adb47490d74b2d 100644
--- a/lib/bundler/man/bundle-install.1.ronn
+++ b/lib/bundler/man/bundle-install.1.ronn
@@ -9,6 +9,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile
[--jobs=NUMBER]
[--local]
[--no-cache]
+ [--no-lock]
[--prefer-local]
[--quiet]
[--retry=NUMBER]
@@ -71,6 +72,15 @@ update process below under [CONSERVATIVE UPDATING][].
does not remove any gems in the cache but keeps the newly bundled gems from
being cached during the install.
+* `--no-lock`:
+ Do not create a lockfile. Useful if you want to install dependencies but not
+ lock versions of gems. Recommended for library development, and other
+ situations where the code is expected to work with a range of dependency
+ versions.
+
+ This has the same effect as using `lockfile false` in the Gemfile.
+ See gemfile(5) for more information.
+
* `--quiet`:
Do not print progress information to the standard output.
diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5
index 4e1a63578076e2..f345580ed77edb 100644
--- a/lib/bundler/man/gemfile.5
+++ b/lib/bundler/man/gemfile.5
@@ -469,4 +469,33 @@ For implicit gems (dependencies of explicit gems), any source, git, or path repo
.IP "3." 4
If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\.
.IP "" 0
+.SH "LOCKFILE"
+By default, Bundler will create a lockfile by adding \fB\.lock\fR to the end of the Gemfile name\. To change this, use the \fBlockfile\fR method:
+.IP "" 4
+.nf
+lockfile "/path/to/lockfile\.lock"
+.fi
+.IP "" 0
+.P
+This is useful when you want to use different lockfiles per ruby version or platform\.
+.P
+To avoid writing a lock file, use \fBfalse\fR as the argument:
+.IP "" 4
+.nf
+lockfile false
+.fi
+.IP "" 0
+.P
+This is useful for library development and other situations where the code is expected to work with a range of dependency versions\.
+.SS "LOCKFILE PRECEDENCE"
+When determining path to the lockfile or whether to create a lockfile, the following precedence is used:
+.IP "1." 4
+The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\.
+.IP "2." 4
+The \fBlockfile\fR method in the Gemfile\.
+.IP "3." 4
+The \fBBUNDLE_LOCKFILE\fR environment variable\.
+.IP "4." 4
+The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\.
+.IP "" 0
diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn
index 802549737e38e4..3dea29cb3c6aaf 100644
--- a/lib/bundler/man/gemfile.5.ronn
+++ b/lib/bundler/man/gemfile.5.ronn
@@ -556,3 +556,30 @@ bundler uses the following priority order:
If multiple global sources are specified, they will be prioritized from
last to first, but this is deprecated since Bundler 1.13, so Bundler prints
a warning and will abort with an error in the future.
+
+## LOCKFILE
+
+By default, Bundler will create a lockfile by adding `.lock` to the end of the
+Gemfile name. To change this, use the `lockfile` method:
+
+ lockfile "/path/to/lockfile.lock"
+
+This is useful when you want to use different lockfiles per ruby version or
+platform.
+
+To avoid writing a lock file, use `false` as the argument:
+
+ lockfile false
+
+This is useful for library development and other situations where the code is
+expected to work with a range of dependency versions.
+
+### LOCKFILE PRECEDENCE
+
+When determining path to the lockfile or whether to create a lockfile, the
+following precedence is used:
+
+1. The `bundle install` `--no-lock` option (which disables lockfile creation).
+2. The `lockfile` method in the Gemfile.
+3. The `BUNDLE_LOCKFILE` environment variable.
+4. The default behavior of adding `.lock` to the end of the Gemfile name.
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index fb1b875b264500..d00a4bb916f692 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -65,6 +65,7 @@ class Settings
gem.rubocop
gem.test
gemfile
+ lockfile
path
shebang
simulate_version
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 4c914eb1a447a4..6419e4299760b7 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -23,6 +23,9 @@ def default_gemfile
end
def default_lockfile
+ given = ENV["BUNDLE_LOCKFILE"]
+ return Pathname.new(given) if given && !given.empty?
+
gemfile = default_gemfile
case gemfile.basename.to_s
@@ -297,6 +300,7 @@ def set_env(key, value)
def set_bundle_variables
Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s
Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION
Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__)
end
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
index 6f080b64598f02..b836208da8e30c 100644
--- a/lib/bundler/ui/shell.rb
+++ b/lib/bundler/ui/shell.rb
@@ -17,6 +17,7 @@ def initialize(options = {})
@level = ENV["DEBUG"] ? "debug" : "info"
@warning_history = []
@output_stream = :stdout
+ @thread_safe_logger_key = "logger_level_#{object_id}"
end
def add_color(string, *color)
@@ -97,11 +98,13 @@ def level=(level)
end
def level(name = nil)
- return @level unless name
+ current_level = Thread.current.thread_variable_get(@thread_safe_logger_key) || @level
+ return current_level unless name
+
unless index = LEVELS.index(name)
raise "#{name.inspect} is not a valid level"
end
- index <= LEVELS.index(@level)
+ index <= LEVELS.index(current_level)
end
def output_stream=(symbol)
@@ -167,12 +170,13 @@ def word_wrap(text, line_width = Thor::Terminal.terminal_width)
end * "\n"
end
- def with_level(level)
- original = @level
- @level = level
+ def with_level(desired_level)
+ old_level = level
+ Thread.current.thread_variable_set(@thread_safe_logger_key, desired_level)
+
yield
ensure
- @level = original
+ Thread.current.thread_variable_set(@thread_safe_logger_key, old_level)
end
def with_output_stream(symbol)
diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb
index ac8988dea577bb..602e00c1d866fb 100644
--- a/lib/rubygems/bundler_version_finder.rb
+++ b/lib/rubygems/bundler_version_finder.rb
@@ -65,9 +65,12 @@ def self.lockfile_contents
return unless gemfile
- lockfile = case gemfile
- when "gems.rb" then "gems.locked"
- else "#{gemfile}.lock"
+ lockfile = ENV["BUNDLE_LOCKFILE"]
+ lockfile = nil if lockfile&.empty?
+
+ lockfile ||= case gemfile
+ when "gems.rb" then "gems.locked"
+ else "#{gemfile}.lock"
end
return unless File.file?(lockfile)
diff --git a/numeric.c b/numeric.c
index 3d6648f7d6fbd7..0d5f3f26f617e3 100644
--- a/numeric.c
+++ b/numeric.c
@@ -633,7 +633,7 @@ num_div(VALUE x, VALUE y)
* call-seq:
* self % other -> real_numeric
*
- * Returns +self+ modulo +other+ as a real number.
+ * Returns +self+ modulo +other+ as a real numeric (\Integer, \Float, or \Rational).
*
* Of the Core and Standard Library classes,
* only Rational uses this implementation.
@@ -1358,7 +1358,7 @@ ruby_float_mod(double x, double y)
* call-seq:
* self % other -> float
*
- * Returns +self+ modulo +other+ as a float.
+ * Returns +self+ modulo +other+ as a \Float.
*
* For float +f+ and real number +r+, these expressions are equivalent:
*
@@ -4316,9 +4316,9 @@ fix_mod(VALUE x, VALUE y)
/*
* call-seq:
- * self % other -> real_number
+ * self % other -> real_numeric
*
- * Returns +self+ modulo +other+ as a real number.
+ * Returns +self+ modulo +other+ as a real numeric (\Integer, \Float, or \Rational).
*
* For integer +n+ and real number +r+, these expressions are equivalent:
*
diff --git a/prism/prism.c b/prism/prism.c
index a87932f1b77a00..45817cdd8c05b2 100644
--- a/prism/prism.c
+++ b/prism/prism.c
@@ -13467,7 +13467,7 @@ parse_target_implicit_parameter(pm_parser_t *parser, pm_node_t *node) {
// remaining nodes down to fill the gap. This is extremely unlikely
// to happen.
if (index != implicit_parameters->size - 1) {
- memcpy(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *));
+ memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *));
}
implicit_parameters->size--;
diff --git a/process.c b/process.c
index 2871596ceae14f..46c55abcce42c6 100644
--- a/process.c
+++ b/process.c
@@ -1575,8 +1575,8 @@ after_exec(void)
static void
before_fork_ruby(void)
{
- rb_gc_before_fork();
before_exec();
+ rb_gc_before_fork();
}
static void
diff --git a/ruby_atomic.h b/ruby_atomic.h
index ad53356f069ce2..9eaa5a9651f96a 100644
--- a/ruby_atomic.h
+++ b/ruby_atomic.h
@@ -63,4 +63,27 @@ rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t
}
#define ATOMIC_U64_SET_RELAXED(var, val) rbimpl_atomic_u64_set_relaxed(&(var), val)
+static inline uint64_t
+rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val)
+{
+#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
+ return __atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST);
+#elif defined(_WIN32)
+ return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val);
+#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx))
+ return atomic_add_64_nv(ptr, val) - val;
+#elif defined(HAVE_STDATOMIC_H)
+ return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst);
+#else
+ // Fallback using mutex for platforms without 64-bit atomics
+ static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER;
+ rb_native_mutex_lock(&lock);
+ uint64_t old = *ptr;
+ *ptr = old + val;
+ rb_native_mutex_unlock(&lock);
+ return old;
+#endif
+}
+#define ATOMIC_U64_FETCH_ADD(var, val) rbimpl_atomic_u64_fetch_add(&(var), val)
+
#endif
diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb
index 286dfa71151877..a19f251be5d946 100644
--- a/spec/bundler/bundler/dsl_spec.rb
+++ b/spec/bundler/bundler/dsl_spec.rb
@@ -221,8 +221,8 @@
to raise_error(Bundler::GemfileError, /is not a valid platform/)
end
- it "raises an error for legacy windows platforms" do
- expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin, :x64_mingw has been removed/)
+ it "warn for legacy windows platforms" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin, :x64_mingw will be removed in the future./)
subject.gem("foo", platforms: [:mswin, :jruby, :x64_mingw])
end
@@ -291,8 +291,8 @@
end
describe "#platforms" do
- it "raises an error for legacy windows platforms" do
- expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin64, :mingw has been removed/)
+ it "warn for legacy windows platforms" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin64, :mingw will be removed in the future./)
subject.platforms(:mswin64, :jruby, :mingw) do
subject.gem("foo")
end
diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb
index ffbc07807429e6..7481622a967d9d 100644
--- a/spec/bundler/bundler/retry_spec.rb
+++ b/spec/bundler/bundler/retry_spec.rb
@@ -12,7 +12,7 @@
end
it "returns the first valid result" do
- jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }]
+ jobs = [proc { raise "job 1 failed" }, proc { :bar }, proc { raise "job 2 failed" }]
attempts = 0
result = Bundler::Retry.new(nil, nil, 3).attempt do
attempts += 1
diff --git a/spec/bundler/bundler/ui/shell_spec.rb b/spec/bundler/bundler/ui/shell_spec.rb
index 422c850a6536e6..83f147191ef139 100644
--- a/spec/bundler/bundler/ui/shell_spec.rb
+++ b/spec/bundler/bundler/ui/shell_spec.rb
@@ -81,4 +81,32 @@
end
end
end
+
+ describe "threads" do
+ it "is thread safe when using with_level" do
+ stop_thr1 = false
+ stop_thr2 = false
+
+ expect(subject.level).to eq("debug")
+
+ thr1 = Thread.new do
+ subject.silence do
+ sleep(0.1) until stop_thr1
+ end
+
+ stop_thr2 = true
+ end
+
+ thr2 = Thread.new do
+ subject.silence do
+ stop_thr1 = true
+ sleep(0.1) until stop_thr2
+ end
+ end
+
+ [thr1, thr2].each(&:join)
+
+ expect(subject.level).to eq("debug")
+ end
+ end
end
diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb
index 2414e1ca02e439..bd92a84e185315 100644
--- a/spec/bundler/commands/cache_spec.rb
+++ b/spec/bundler/commands/cache_spec.rb
@@ -207,15 +207,15 @@
expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
end
- it "prints an error when using legacy windows rubies" do
+ it "prints a warn when using legacy windows rubies" do
gemfile <<-D
source "https://gem.repo1"
gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20]
D
bundle "cache --all-platforms", raise_on_error: false
- expect(err).to include("removed")
- expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).not_to exist
+ expect(err).to include("will be removed in the future")
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
end
it "does not attempt to install gems in without groups" do
diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb
index 1392b1731574a3..954cae09d8464a 100644
--- a/spec/bundler/commands/config_spec.rb
+++ b/spec/bundler/commands/config_spec.rb
@@ -592,3 +592,20 @@
end
end
end
+
+RSpec.describe "setting lockfile via config" do
+ it "persists the lockfile location to .bundle/config" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ bundle "config set --local lockfile #{bundled_app("ReallyNotGemfile.lock")}"
+ expect(File.exist?(bundled_app(".bundle/config"))).to eq(true)
+
+ bundle "config list"
+ expect(out).to include("NotGemfile")
+ expect(out).to include("ReallyNotGemfile.lock")
+ end
+end
diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb
index 03f1d839c76c9a..1ac308bdda4bfc 100644
--- a/spec/bundler/commands/exec_spec.rb
+++ b/spec/bundler/commands/exec_spec.rb
@@ -1034,7 +1034,7 @@ def bin_path(a,b,c)
puts 'Started' # For process sync
STDOUT.flush
sleep 1 # ignore quality_spec
- raise "Didn't receive INT at all"
+ raise RuntimeError, "Didn't receive expected INT"
end.join
rescue Interrupt
puts "foo"
@@ -1218,7 +1218,7 @@ def require(path)
build_repo4 do
build_gem "openssl", openssl_version do |s|
s.write("lib/openssl.rb", <<-RUBY)
- raise "custom openssl should not be loaded, it's not in the gemfile!"
+ raise ArgumentError, "custom openssl should not be loaded"
RUBY
end
end
diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb
index 3ab7d4ab880059..69d9a860996197 100644
--- a/spec/bundler/commands/install_spec.rb
+++ b/spec/bundler/commands/install_spec.rb
@@ -29,6 +29,28 @@
expect(bundled_app_lock).to exist
end
+ it "creates lockfile based on the lockfile method in Gemfile" do
+ install_gemfile <<-G
+ lockfile "OmgFile.lock"
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install"
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+
+ it "does not make a lockfile if lockfile false is used in Gemfile" do
+ install_gemfile <<-G
+ lockfile false
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ expect(bundled_app_lock).not_to exist
+ end
+
it "does not create ./.bundle by default" do
install_gemfile <<-G
source "https://gem.repo1"
@@ -67,6 +89,17 @@
expect(bundled_app("OmgFile.lock")).to exist
end
+ it "doesn't create a lockfile if --no-lock option is given" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile --no-lock"
+
+ expect(bundled_app("OmgFile.lock")).not_to exist
+ end
+
it "doesn't delete the lockfile if one already exists" do
install_gemfile <<-G
source "https://gem.repo1"
@@ -688,13 +721,12 @@
it "fails gracefully when downloading an invalid specification from the full index" do
build_repo2(build_compact_index: false) do
build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s|
- bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]]
+ invalid_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]]
s.
instance_variable_get(:@spec).
- instance_variable_set(:@dependencies, bad_deps)
-
- raise "failed to set bad deps" unless s.dependencies == bad_deps
+ instance_variable_set(:@dependencies, invalid_deps)
end
+
build_gem "ruby-ajp", "1.0.0"
end
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
index 6ba69d466a9b8c..ab1926734c1e78 100644
--- a/spec/bundler/commands/lock_spec.rb
+++ b/spec/bundler/commands/lock_spec.rb
@@ -834,7 +834,7 @@
bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
expect(lockfile).to end_with("BUNDLED WITH\n 55\n")
- update_repo4 do
+ build_repo4 do
build_gem "bundler", "99"
end
@@ -1456,7 +1456,7 @@
before do
gemfile_with_rails_weakling_and_foo_from_repo4
- update_repo4 do
+ build_repo4 do
build_gem "foo", "2.0"
end
diff --git a/spec/bundler/commands/ssl_spec.rb b/spec/bundler/commands/ssl_spec.rb
index b4aca55194e23f..4220731b697073 100644
--- a/spec/bundler/commands/ssl_spec.rb
+++ b/spec/bundler/commands/ssl_spec.rb
@@ -16,16 +16,17 @@
end
end
- @previous_level = Bundler.ui.level
- Bundler.ui.instance_variable_get(:@warning_history).clear
- @previous_client = Gem::Request::ConnectionPools.client
+ @previous_ui = Bundler.ui
+ Bundler.ui = Bundler::UI::Shell.new
Bundler.ui.level = "info"
+
+ @previous_client = Gem::Request::ConnectionPools.client
Artifice.activate_with(@dummy_endpoint)
Gem::Request::ConnectionPools.client = Gem::Net::HTTP
end
after(:each) do
- Bundler.ui.level = @previous_level
+ Bundler.ui = @previous_ui
Artifice.deactivate
Gem::Request::ConnectionPools.client = @previous_client
end
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
index 61d8ece2798a75..cdaeb75c4a3399 100644
--- a/spec/bundler/commands/update_spec.rb
+++ b/spec/bundler/commands/update_spec.rb
@@ -247,7 +247,7 @@
expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1")
- update_repo4 do
+ build_repo4 do
build_gem "slim", "4.0.0" do |s|
s.add_dependency "tilt", [">= 2.0.6", "< 2.1"]
end
@@ -572,7 +572,7 @@
expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
- update_repo4 do
+ build_repo4 do
build_gem "b", "2.0" do |s|
s.add_dependency "c", "< 2"
end
@@ -976,7 +976,7 @@
bundle "update", all: true
expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/)
- update_repo4 do
+ build_repo4 do
build_gem "foo", "2.0"
end
diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb
index daa977fc9b7f9f..3d9766d21ff66f 100644
--- a/spec/bundler/install/gemfile/gemspec_spec.rb
+++ b/spec/bundler/install/gemfile/gemspec_spec.rb
@@ -420,12 +420,13 @@
end
build_lib "foo", path: bundled_app do |s|
- if platform_specific_type == :runtime
+ case platform_specific_type
+ when :runtime
s.add_runtime_dependency dependency
- elsif platform_specific_type == :development
+ when :development
s.add_development_dependency dependency
else
- raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
+ raise ArgumentError, "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
end
end
diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb
index 32de3f2be2dcc3..4727d5ef9b02cf 100644
--- a/spec/bundler/install/gemfile/groups_spec.rb
+++ b/spec/bundler/install/gemfile/groups_spec.rb
@@ -25,7 +25,7 @@
puts ACTIVESUPPORT
R
- expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- activesupport/)
end
it "installs gems with inline :groups into those groups" do
@@ -36,7 +36,7 @@
puts THIN
R
- expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- thin/)
end
it "sets up everything if Bundler.setup is used with no groups" do
@@ -57,7 +57,7 @@
puts THIN
RUBY
- expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- thin/)
end
it "sets up old groups when they have previously been removed" do
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
index bdc61c2b2694e1..c0b4d98f1c5cc6 100644
--- a/spec/bundler/install/gemfile/sources_spec.rb
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -570,7 +570,7 @@
bundle :install, artifice: "compact_index"
# And then we add some new versions...
- update_repo4 do
+ build_repo4 do
build_gem "foo", "0.2"
build_gem "bar", "0.3"
end
diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb
index 0e3b8477678388..87326f67af693c 100644
--- a/spec/bundler/install/gemfile_spec.rb
+++ b/spec/bundler/install/gemfile_spec.rb
@@ -27,6 +27,35 @@
ENV["BUNDLE_GEMFILE"] = "NotGemfile"
expect(the_bundle).to include_gems "myrack 1.0.0"
end
+
+ it "respects lockfile and BUNDLE_LOCKFILE" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ lockfile "ReallyNotGemfile.lock"
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle :install, gemfile: bundled_app("NotGemfile")
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "respects BUNDLE_LOCKFILE during bundle install" do
+ ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock"
+
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle :install, gemfile: bundled_app("NotGemfile")
+ expect(bundled_app("ReallyNotGemfile.lock")).to exist
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
end
context "with gemfile set via config" do
diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb
index cd64f85f9295a7..bb4d4011f5b94f 100644
--- a/spec/bundler/install/gems/compact_index_spec.rb
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -770,7 +770,7 @@ def start
gem 'myrack', '0.9.1'
G
- update_repo4 do
+ build_repo4 do
build_gem "myrack", "1.0.0"
end
@@ -811,7 +811,7 @@ def start
gem 'myrack', '0.9.1'
G
- update_repo4 do
+ build_repo4 do
build_gem "myrack", "1.0.0"
end
@@ -833,7 +833,7 @@ def start
gem 'myrack', '0.9.1'
G
- update_repo4 do
+ build_repo4 do
build_gem "myrack", "1.0.0"
end
diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb
index 874818fa874f92..7f230d132b9eab 100644
--- a/spec/bundler/install/gems/native_extensions_spec.rb
+++ b/spec/bundler/install/gems/native_extensions_spec.rb
@@ -9,7 +9,7 @@
require "mkmf"
name = "c_extension_bundle"
dir_config(name)
- raise "OMG" unless with_config("c_extension") == "hello"
+ raise ArgumentError unless with_config("c_extension") == "hello"
create_makefile(name)
E
@@ -53,7 +53,7 @@
require "mkmf"
name = "c_extension_bundle"
dir_config(name)
- raise "OMG" unless with_config("c_extension") == "hello"
+ raise ArgumentError unless with_config("c_extension") == "hello"
create_makefile(name)
E
@@ -97,7 +97,7 @@
require "mkmf"
name = "c_extension_bundle_#{n}"
dir_config(name)
- raise "OMG" unless with_config("c_extension_#{n}") == "#{n}"
+ raise ArgumentError unless with_config("c_extension_#{n}") == "#{n}"
create_makefile(name)
E
@@ -149,7 +149,7 @@
require "mkmf"
name = "c_extension_bundle"
dir_config(name)
- raise "OMG" unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola"
+ raise ArgumentError unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola"
create_makefile(name)
E
diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb
index d5f6c896cd6642..37997ffe482409 100644
--- a/spec/bundler/install/gems/standalone_spec.rb
+++ b/spec/bundler/install/gems/standalone_spec.rb
@@ -385,7 +385,7 @@
RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
it "allows `without` configuration to limit the groups used in a standalone" do
@@ -403,7 +403,7 @@
RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
it "allows `path` configuration to change the location of the standalone bundle" do
@@ -437,7 +437,7 @@
RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
end
@@ -519,6 +519,6 @@
RUBY
expect(out).to eq("1.0.0")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
end
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
index 0cddeb09185bc7..6cace961f5228b 100644
--- a/spec/bundler/plugins/install_spec.rb
+++ b/spec/bundler/plugins/install_spec.rb
@@ -168,7 +168,7 @@ def exec(command, args)
build_repo2 do
build_plugin "chaplin" do |s|
s.write "plugins.rb", <<-RUBY
- raise "I got you man"
+ raise RuntimeError, "threw exception on load"
RUBY
end
end
diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb
index 86b4c91a073685..391aa0cef6575a 100644
--- a/spec/bundler/realworld/edgecases_spec.rb
+++ b/spec/bundler/realworld/edgecases_spec.rb
@@ -16,7 +16,7 @@ def rubygems_version(name, requirement)
index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last
end
if rubygem.nil?
- raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
+ raise ArgumentError, "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
"Found specs:\n\#{index.send(:specs).inspect}"
end
puts "#{name} (\#{rubygem.version})"
diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb
index 9268d9d1cc4545..1ffaffef0ed20e 100644
--- a/spec/bundler/runtime/setup_spec.rb
+++ b/spec/bundler/runtime/setup_spec.rb
@@ -728,46 +728,52 @@ def clean_load_path(lp)
G
run <<-R
- File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f|
+ File.open(File.join(Gem.dir, "specifications", "invalid.gemspec"), "w") do |f|
f.write <<-RUBY
# -*- encoding: utf-8 -*-
-# stub: broken 1.0.0 ruby lib
+# stub: invalid 1.0.0 ruby lib
Gem::Specification.new do |s|
- s.name = "broken"
+ s.name = "invalid"
s.version = "1.0.0"
- raise "BROKEN GEMSPEC"
+ s.authors = ["Invalid Author"]
+ s.files = ["lib/invalid.rb"]
+ s.add_dependency "nonexistent-gem", "~> 999.999.999"
+ s.validate!
end
RUBY
end
R
run <<-R
- File.open(File.join(Gem.dir, "specifications", "broken-ext.gemspec"), "w") do |f|
+ File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f|
f.write <<-RUBY
# -*- encoding: utf-8 -*-
-# stub: broken-ext 1.0.0 ruby lib
+# stub: invalid-ext 1.0.0 ruby lib
# stub: a.ext\\0b.ext
Gem::Specification.new do |s|
- s.name = "broken-ext"
+ s.name = "invalid-ext"
s.version = "1.0.0"
- raise "BROKEN GEMSPEC EXT"
+ s.authors = ["Invalid Author"]
+ s.files = ["lib/invalid.rb"]
+ s.required_ruby_version = "~> 0.8.0"
+ s.validate!
end
RUBY
end
# Need to write the gem.build_complete file,
# otherwise the full spec is loaded to check the installed_by_version
extensions_dir = Gem.default_ext_dir_for(Gem.dir) || File.join(Gem.dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version)
- Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "broken-ext-1.0.0"))
- File.open(File.join(extensions_dir, "broken-ext-1.0.0", "gem.build_complete"), "w") {}
+ Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "invalid-ext-1.0.0"))
+ File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {}
R
run <<-R
- puts "WIN"
+ puts "Success"
R
- expect(out).to eq("WIN")
+ expect(out).to eq("Success")
end
it "ignores empty gem paths" do
@@ -1151,7 +1157,7 @@ def clean_load_path(lp)
bundler_module = class << Bundler; self; end
bundler_module.send(:remove_method, :require)
def Bundler.require(path)
- raise "LOSE"
+ raise StandardError, "didn't use binding from top level"
end
Bundler.load
RUBY
diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb
index 08d7b5ec539129..6c621660513b1f 100644
--- a/spec/bundler/support/artifice/compact_index_etag_match.rb
+++ b/spec/bundler/support/artifice/compact_index_etag_match.rb
@@ -4,7 +4,7 @@
class CompactIndexEtagMatch < CompactIndexAPI
get "/versions" do
- raise "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"]
+ raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"]
headers "ETag" => env["HTTP_IF_NONE_MATCH"]
status 304
body ""
diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb
index 31d4f30a3b96c0..6087ea8cc8c652 100644
--- a/spec/bundler/support/builders.rb
+++ b/spec/bundler/support/builders.rb
@@ -187,17 +187,25 @@ def build_repo2(**kwargs, &blk)
# A repo that has no pre-installed gems included. (The caller completely
# determines the contents with the block.)
+ #
+ # If the repo already exists, `#update_repo` will be called.
def build_repo3(**kwargs, &blk)
- raise "gem_repo3 already exists -- use update_repo3 instead" if File.exist?(gem_repo3)
- build_repo gem_repo3, **kwargs, &blk
+ if File.exist?(gem_repo3)
+ update_repo(gem_repo3, &blk)
+ else
+ build_repo gem_repo3, **kwargs, &blk
+ end
end
# Like build_repo3, this is a repo that has no pre-installed gems included.
- # We have two different methods for situations where two different empty
- # sources are needed.
+ #
+ # If the repo already exists, `#udpate_repo` will be called
def build_repo4(**kwargs, &blk)
- raise "gem_repo4 already exists -- use update_repo4 instead" if File.exist?(gem_repo4)
- build_repo gem_repo4, **kwargs, &blk
+ if File.exist?(gem_repo4)
+ update_repo gem_repo4, &blk
+ else
+ build_repo gem_repo4, **kwargs, &blk
+ end
end
def update_repo2(**kwargs, &blk)
@@ -208,10 +216,6 @@ def update_repo3(&blk)
update_repo(gem_repo3, &blk)
end
- def update_repo4(&blk)
- update_repo(gem_repo4, &blk)
- end
-
def build_security_repo
build_repo security_repo do
build_gem "myrack"
@@ -420,8 +424,6 @@ def required_ruby_version=(*reqs)
class BundlerBuilder
def initialize(context, name, version)
- raise "can only build bundler" unless name == "bundler"
-
@context = context
@spec = Spec::Path.loaded_gemspec.dup
@spec.version = version || Bundler::VERSION
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
index 12ff09b714a0fb..52e6ff5d9a3140 100644
--- a/spec/bundler/support/helpers.rb
+++ b/spec/bundler/support/helpers.rb
@@ -28,8 +28,8 @@ def reset!
Gem.clear_paths
end
- def the_bundle(*args)
- TheBundle.new(*args)
+ def the_bundle
+ TheBundle.new
end
MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/
@@ -54,7 +54,7 @@ def load_error_run(ruby, name, *args)
begin
#{ruby}
rescue LoadError => e
- warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
RUBY
opts = args.last.is_a?(Hash) ? args.pop : {}
@@ -132,7 +132,7 @@ def load_error_ruby(ruby, name, opts = {})
begin
#{ruby}
rescue LoadError => e
- warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
R
end
@@ -324,7 +324,7 @@ def self.install_dev_bundler
end
def install_gem(path, install_dir, default = false)
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}"
@@ -415,7 +415,7 @@ def cache_gems(*gems, gem_repo: gem_repo1)
gems.each do |g|
path = "#{gem_repo}/gems/#{g}.gem"
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
FileUtils.cp(path, "#{bundled_app}/vendor/cache")
end
end
diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb
index 9f311fc0d77c39..5a3c38a4db361e 100644
--- a/spec/bundler/support/matchers.rb
+++ b/spec/bundler/support/matchers.rb
@@ -52,7 +52,7 @@ def failing_matcher
end
def self.define_compound_matcher(matcher, preconditions, &declarations)
- raise "Must have preconditions to define a compound matcher" if preconditions.empty?
+ raise ArgumentError, "Must have preconditions to define a compound matcher" if preconditions.empty?
define_method(matcher) do |*expected, &block_arg|
Precondition.new(
RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg),
diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb
index bda717f3b00bf3..452abd7d410171 100644
--- a/spec/bundler/support/the_bundle.rb
+++ b/spec/bundler/support/the_bundle.rb
@@ -8,10 +8,8 @@ class TheBundle
attr_accessor :bundle_dir
- def initialize(opts = {})
- opts = opts.dup
- @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app })
- raise "Too many options! #{opts}" unless opts.empty?
+ def initialize
+ @bundle_dir = Pathname.new(bundled_app)
end
def to_s
@@ -28,7 +26,7 @@ def lockfile
end
def locked_gems
- raise "Cannot read lockfile if it doesn't exist" unless locked?
+ raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked?
Bundler::LockfileParser.new(lockfile.read)
end
diff --git a/string.c b/string.c
index d78d7320be2b2b..b1db9cb528fdfe 100644
--- a/string.c
+++ b/string.c
@@ -5463,33 +5463,7 @@ str_upto_i(VALUE str, VALUE arg)
* upto(other_string, exclusive = false) {|string| ... } -> self
* upto(other_string, exclusive = false) -> new_enumerator
*
- * With a block given, calls the block with each +String+ value
- * returned by successive calls to String#succ;
- * the first value is +self+, the next is self.succ, and so on;
- * the sequence terminates when value +other_string+ is reached;
- * returns +self+:
- *
- * 'a8'.upto('b6') {|s| print s, ' ' } # => "a8"
- * Output:
- *
- * a8 a9 b0 b1 b2 b3 b4 b5 b6
- *
- * If argument +exclusive+ is given as a truthy object, the last value is omitted:
- *
- * 'a8'.upto('b6', true) {|s| print s, ' ' } # => "a8"
- *
- * Output:
- *
- * a8 a9 b0 b1 b2 b3 b4 b5
- *
- * If +other_string+ would not be reached, does not call the block:
- *
- * '25'.upto('5') {|s| fail s }
- * 'aa'.upto('a') {|s| fail s }
- *
- * With no block given, returns a new Enumerator:
- *
- * 'a8'.upto('b6') # => #
+ * :include: doc/string/upto.rdoc
*
*/
@@ -7959,19 +7933,12 @@ upcase_single(VALUE str)
* call-seq:
* upcase!(mapping) -> self or nil
*
- * Upcases the characters in +self+;
- * returns +self+ if any changes were made, +nil+ otherwise:
- *
- * s = 'Hello World!' # => "Hello World!"
- * s.upcase! # => "HELLO WORLD!"
- * s # => "HELLO WORLD!"
- * s.upcase! # => nil
- *
- * The casing may be affected by the given +mapping+;
- * see {Case Mapping}[rdoc-ref:case_mapping.rdoc].
+ * Like String#upcase, except that:
*
- * Related: String#upcase, String#downcase, String#downcase!.
+ * - Changes character casings in +self+ (not in a copy of +self+).
+ * - Returns +self+ if any changes are made, +nil+ otherwise.
*
+ * Related: See {Modifying}[rdoc-ref:String@Modifying].
*/
static VALUE
@@ -8001,16 +7968,7 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str)
* call-seq:
* upcase(mapping) -> string
*
- * Returns a string containing the upcased characters in +self+:
- *
- * s = 'Hello World!' # => "Hello World!"
- * s.upcase # => "HELLO WORLD!"
- *
- * The casing may be affected by the given +mapping+;
- * see {Case Mapping}[rdoc-ref:case_mapping.rdoc].
- *
- * Related: String#upcase!, String#downcase, String#downcase!.
- *
+ * :include: doc/string/upcase.rdoc
*/
static VALUE
@@ -11511,11 +11469,8 @@ rb_str_b(VALUE str)
* call-seq:
* valid_encoding? -> true or false
*
- * Returns +true+ if +self+ is encoded correctly, +false+ otherwise:
+ * :include: doc/string/valid_encoding_p.rdoc
*
- * "\xc2\xa1".force_encoding(Encoding::UTF_8).valid_encoding? # => true
- * "\xc2".force_encoding(Encoding::UTF_8).valid_encoding? # => false
- * "\x80".force_encoding(Encoding::UTF_8).valid_encoding? # => false
*/
static VALUE
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index 9abed9265212d0..bd7a8a638166c5 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -64,6 +64,24 @@ def test_invalid_message_name
assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name
end
+ def test_regexp_encoding_option_mismatch_error
+ # UTF-8 char with ASCII-8BIT modifier
+ result = Prism.parse('/Ȃ/n')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with EUC-JP modifier
+ result = Prism.parse('/Ȃ/e')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with Windows-31J modifier
+ result = Prism.parse('/Ȃ/s')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with UTF-8 modifier
+ result = Prism.parse('/Ȃ/u')
+ assert_empty result.errors
+ end
+
private
def assert_errors(filepath, version)
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 62c46678882a10..997ed52640fb0d 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -73,12 +73,64 @@ def test_new_readonly
def test_file_mapped
buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
- contents = buffer.get_string
+ assert_equal File.size(__FILE__), buffer.size
+ contents = buffer.get_string
assert_include contents, "Hello World"
assert_equal Encoding::BINARY, contents.encoding
end
+ def test_file_mapped_with_size
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)}
+ assert_equal 30, buffer.size
+
+ contents = buffer.get_string
+ assert_equal "# frozen_string_literal: false", contents
+ assert_equal Encoding::BINARY, contents.encoding
+ end
+
+ def test_file_mapped_size_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_size_just_enough
+ File.open(__FILE__) {|file|
+ assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size
+ }
+ end
+
+ def test_file_mapped_offset_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_zero_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_offset
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)}
+ end
+ end
+
def test_file_mapped_invalid
assert_raise TypeError do
IO::Buffer.map("foobar")
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 64372c231cf26f..7472ff77156b95 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -983,7 +983,7 @@ def test = Foo.new
}, insns: [:opt_new]
end
- def test_opt_new_with_redefinition
+ def test_opt_new_with_redefined
assert_compiles '"foo"', %q{
class Foo
def self.new = "foo"
@@ -1040,6 +1040,22 @@ def test(x)
}, insns: [:opt_newarray_send], call_threshold: 1
end
+ def test_opt_newarray_send_include_p_redefined
+ assert_compiles '[:true, :false]', %q{
+ class Array
+ alias_method :old_include?, :include?
+ def include?(x)
+ old_include?(x) ? :true : :false
+ end
+ end
+
+ def test(x)
+ [:y, 1, Object.new].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
def test_opt_duparray_send_include_p
assert_compiles '[true, false]', %q{
def test(x)
@@ -1049,6 +1065,63 @@ def test(x)
}, insns: [:opt_duparray_send], call_threshold: 1
end
+ def test_opt_duparray_send_include_p_redefined
+ assert_compiles '[:true, :false]', %q{
+ class Array
+ alias_method :old_include?, :include?
+ def include?(x)
+ old_include?(x) ? :true : :false
+ end
+ end
+
+ def test(x)
+ [:y, 1].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_duparray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_hash
+ assert_compiles 'Integer', %q{
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20).class
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_hash_redefined
+ assert_compiles '42', %q{
+ Array.class_eval { def hash = 42 }
+
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20)
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_max
+ assert_compiles '[20, 40]', %q{
+ def test(a,b) = [a,b].max
+ [test(10, 20), test(40, 30)]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_max_redefined
+ assert_compiles '[60, 90]', %q{
+ class Array
+ alias_method :old_max, :max
+ def max
+ old_max * 2
+ end
+ end
+
+ def test(a,b) = [a,b].max
+ [test(15, 30), test(45, 35)]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
def test_new_hash_empty
assert_compiles '{}', %q{
def test = {}
@@ -2415,7 +2488,7 @@ def entry(flag)
}, call_threshold: 2
end
- def test_bop_redefinition
+ def test_bop_redefined
assert_runs '[3, :+, 100]', %q{
def test
1 + 2
@@ -2426,7 +2499,7 @@ def test
}, call_threshold: 2
end
- def test_bop_redefinition_with_adjacent_patch_points
+ def test_bop_redefined_with_adjacent_patch_points
assert_runs '[15, :+, 100]', %q{
def test
1 + 2 + 3 + 4 + 5
@@ -2439,7 +2512,7 @@ def test
# ZJIT currently only generates a MethodRedefined patch point when the method
# is called on the top-level self.
- def test_method_redefinition_with_top_self
+ def test_method_redefined_with_top_self
assert_runs '["original", "redefined"]', %q{
def foo
"original"
@@ -2462,7 +2535,7 @@ def foo
}, call_threshold: 2
end
- def test_method_redefinition_with_module
+ def test_method_redefined_with_module
assert_runs '["original", "redefined"]', %q{
module Foo
def self.foo = "original"
diff --git a/thread.c b/thread.c
index 5d75bf41228d3c..3e9bf3192d0c62 100644
--- a/thread.c
+++ b/thread.c
@@ -442,8 +442,8 @@ rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th)
th->keeping_mutexes = mutex->next_mutex;
// rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th);
- VM_ASSERT(mutex->fiber);
- const char *error_message = rb_mutex_unlock_th(mutex, th, mutex->fiber);
+ VM_ASSERT(mutex->fiber_serial);
+ const char *error_message = rb_mutex_unlock_th(mutex, th, NULL);
if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message);
}
}
@@ -5263,7 +5263,7 @@ rb_thread_shield_owned(VALUE self)
rb_mutex_t *m = mutex_ptr(mutex);
- return m->fiber == GET_EC()->fiber_ptr;
+ return m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr);
}
/*
@@ -5282,7 +5282,7 @@ rb_thread_shield_wait(VALUE self)
if (!mutex) return Qfalse;
m = mutex_ptr(mutex);
- if (m->fiber == GET_EC()->fiber_ptr) return Qnil;
+ if (m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr)) return Qnil;
rb_thread_shield_waiting_inc(self);
rb_mutex_lock(mutex);
rb_thread_shield_waiting_dec(self);
@@ -5799,8 +5799,8 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg)
if (th->locking_mutex) {
rb_mutex_t *mutex = mutex_ptr(th->locking_mutex);
- rb_str_catf(msg, " mutex:%p cond:%"PRIuSIZE,
- (void *)mutex->fiber, rb_mutex_num_waiting(mutex));
+ rb_str_catf(msg, " mutex:%llu cond:%"PRIuSIZE,
+ (unsigned long long)mutex->fiber_serial, rb_mutex_num_waiting(mutex));
}
{
@@ -5840,7 +5840,7 @@ rb_check_deadlock(rb_ractor_t *r)
}
else if (th->locking_mutex) {
rb_mutex_t *mutex = mutex_ptr(th->locking_mutex);
- if (mutex->fiber == th->ec->fiber_ptr || (!mutex->fiber && !ccan_list_empty(&mutex->waitq))) {
+ if (mutex->fiber_serial == rb_fiber_serial(th->ec->fiber_ptr) || (!mutex->fiber_serial && !ccan_list_empty(&mutex->waitq))) {
found = 1;
}
}
diff --git a/thread_sync.c b/thread_sync.c
index 0fc70224ff90ed..6cc23f7d87b32d 100644
--- a/thread_sync.c
+++ b/thread_sync.c
@@ -7,7 +7,7 @@ static VALUE rb_eClosedQueueError;
/* Mutex */
typedef struct rb_mutex_struct {
- rb_fiber_t *fiber;
+ rb_serial_t fiber_serial;
VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free
struct rb_mutex_struct *next_mutex;
struct ccan_list_head waitq; /* protected by GVL */
@@ -125,28 +125,7 @@ rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber);
static bool
locked_p(rb_mutex_t *mutex)
{
- return mutex->fiber != 0;
-}
-
-static void
-mutex_mark(void *ptr)
-{
- rb_mutex_t *mutex = ptr;
- VALUE fiber;
- if (locked_p(mutex)) {
- fiber = rb_fiberptr_self(mutex->fiber); // rb_fiber_t* doesn't move along with fiber object
- if (fiber) rb_gc_mark_movable(fiber);
- rb_gc_mark_movable(mutex->thread);
- }
-}
-
-static void
-mutex_compact(void *ptr)
-{
- rb_mutex_t *mutex = ptr;
- if (locked_p(mutex)) {
- mutex->thread = rb_gc_location(mutex->thread);
- }
+ return mutex->fiber_serial != 0;
}
static void
@@ -154,7 +133,7 @@ mutex_free(void *ptr)
{
rb_mutex_t *mutex = ptr;
if (locked_p(mutex)) {
- const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), mutex->fiber);
+ const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), NULL);
if (err) rb_bug("%s", err);
}
ruby_xfree(ptr);
@@ -168,8 +147,8 @@ mutex_memsize(const void *ptr)
static const rb_data_type_t mutex_data_type = {
"mutex",
- {mutex_mark, mutex_free, mutex_memsize, mutex_compact,},
- 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
+ {NULL, mutex_free, mutex_memsize,},
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
static rb_mutex_t *
@@ -265,11 +244,7 @@ mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber)
rb_mutex_t *mutex = mutex_ptr(self);
mutex->thread = th->self;
- mutex->fiber = fiber;
- RB_OBJ_WRITTEN(self, Qundef, th->self);
- if (fiber) {
- RB_OBJ_WRITTEN(self, Qundef, rb_fiberptr_self(fiber));
- }
+ mutex->fiber_serial = rb_fiber_serial(fiber);
}
static void
@@ -293,7 +268,7 @@ rb_mutex_trylock(VALUE self)
{
rb_mutex_t *mutex = mutex_ptr(self);
- if (mutex->fiber == 0) {
+ if (mutex->fiber_serial == 0) {
RUBY_DEBUG_LOG("%p ok", mutex);
rb_fiber_t *fiber = GET_EC()->fiber_ptr;
@@ -311,7 +286,7 @@ rb_mutex_trylock(VALUE self)
static VALUE
mutex_owned_p(rb_fiber_t *fiber, rb_mutex_t *mutex)
{
- return RBOOL(mutex->fiber == fiber);
+ return RBOOL(mutex->fiber_serial == rb_fiber_serial(fiber));
}
static VALUE
@@ -347,12 +322,12 @@ do_mutex_lock(VALUE self, int interruptible_p)
}
if (rb_mutex_trylock(self) == Qfalse) {
- if (mutex->fiber == fiber) {
+ if (mutex->fiber_serial == rb_fiber_serial(fiber)) {
rb_raise(rb_eThreadError, "deadlock; recursive locking");
}
- while (mutex->fiber != fiber) {
- VM_ASSERT(mutex->fiber != NULL);
+ while (mutex->fiber_serial != rb_fiber_serial(fiber)) {
+ VM_ASSERT(mutex->fiber_serial != 0);
VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
@@ -366,12 +341,12 @@ do_mutex_lock(VALUE self, int interruptible_p)
rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter);
- if (!mutex->fiber) {
+ if (!mutex->fiber_serial) {
mutex_set_owner(self, th, fiber);
}
}
else {
- if (!th->vm->thread_ignore_deadlock && rb_fiber_threadptr(mutex->fiber) == th) {
+ if (!th->vm->thread_ignore_deadlock && rb_thread_ptr(mutex->thread) == th) {
rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread");
}
@@ -407,7 +382,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
ccan_list_del(&sync_waiter.node);
// unlocked by another thread while sleeping
- if (!mutex->fiber) {
+ if (!mutex->fiber_serial) {
mutex_set_owner(self, th, fiber);
}
@@ -421,12 +396,12 @@ do_mutex_lock(VALUE self, int interruptible_p)
if (interruptible_p) {
/* release mutex before checking for interrupts...as interrupt checking
* code might call rb_raise() */
- if (mutex->fiber == fiber) {
+ if (mutex->fiber_serial == rb_fiber_serial(fiber)) {
mutex->thread = Qfalse;
- mutex->fiber = NULL;
+ mutex->fiber_serial = 0;
}
RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */
- if (!mutex->fiber) {
+ if (!mutex->fiber_serial) {
mutex_set_owner(self, th, fiber);
}
}
@@ -446,7 +421,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
}
if (saved_ints) th->ec->interrupt_flag = saved_ints;
- if (mutex->fiber == fiber) mutex_locked(th, fiber, self);
+ if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(th, fiber, self);
}
RUBY_DEBUG_LOG("%p locked", mutex);
@@ -496,16 +471,16 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber)
{
RUBY_DEBUG_LOG("%p", mutex);
- if (mutex->fiber == 0) {
+ if (mutex->fiber_serial == 0) {
return "Attempt to unlock a mutex which is not locked";
}
- else if (mutex->fiber != fiber) {
+ else if (fiber && mutex->fiber_serial != rb_fiber_serial(fiber)) {
return "Attempt to unlock a mutex which is locked by another thread/fiber";
}
struct sync_waiter *cur = 0, *next;
- mutex->fiber = 0;
+ mutex->fiber_serial = 0;
thread_mutex_remove(th, mutex);
ccan_list_for_each_safe(&mutex->waitq, cur, next, node) {
@@ -583,7 +558,7 @@ rb_mutex_abandon_all(rb_mutex_t *mutexes)
while (mutexes) {
mutex = mutexes;
mutexes = mutex->next_mutex;
- mutex->fiber = 0;
+ mutex->fiber_serial = 0;
mutex->next_mutex = 0;
ccan_list_head_init(&mutex->waitq);
}
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index ff686d047a163d..97e63387bb7b55 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -6350,15 +6350,15 @@ rb_vm_opt_duparray_include_p(rb_execution_context_t *ec, const VALUE ary, VALUE
}
static VALUE
-vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
if (BASIC_OP_UNREDEFINED_P(BOP_MAX, ARRAY_REDEFINED_OP_FLAG)) {
- if (num == 0) {
+ if (array_len == 0) {
return Qnil;
}
else {
VALUE result = *ptr;
- rb_snum_t i = num - 1;
+ rb_snum_t i = array_len - 1;
while (i-- > 0) {
const VALUE v = *++ptr;
if (OPTIMIZED_CMP(v, result) > 0) {
@@ -6369,26 +6369,26 @@ vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
}
}
else {
- return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idMax, 0, NULL, RB_NO_KEYWORDS);
+ return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idMax, 0, NULL, RB_NO_KEYWORDS);
}
}
VALUE
-rb_vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+rb_vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
- return vm_opt_newarray_max(ec, num, ptr);
+ return vm_opt_newarray_max(ec, array_len, ptr);
}
static VALUE
-vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
if (BASIC_OP_UNREDEFINED_P(BOP_MIN, ARRAY_REDEFINED_OP_FLAG)) {
- if (num == 0) {
+ if (array_len == 0) {
return Qnil;
}
else {
VALUE result = *ptr;
- rb_snum_t i = num - 1;
+ rb_snum_t i = array_len - 1;
while (i-- > 0) {
const VALUE v = *++ptr;
if (OPTIMIZED_CMP(v, result) < 0) {
@@ -6399,63 +6399,63 @@ vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
}
}
else {
- return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idMin, 0, NULL, RB_NO_KEYWORDS);
+ return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idMin, 0, NULL, RB_NO_KEYWORDS);
}
}
VALUE
-rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
- return vm_opt_newarray_min(ec, num, ptr);
+ return vm_opt_newarray_min(ec, array_len, ptr);
}
static VALUE
-vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
// If Array#hash is _not_ monkeypatched, use the optimized call
if (BASIC_OP_UNREDEFINED_P(BOP_HASH, ARRAY_REDEFINED_OP_FLAG)) {
- return rb_ary_hash_values(num, ptr);
+ return rb_ary_hash_values(array_len, ptr);
}
else {
- return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idHash, 0, NULL, RB_NO_KEYWORDS);
+ return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idHash, 0, NULL, RB_NO_KEYWORDS);
}
}
VALUE
-rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
+rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr)
{
- return vm_opt_newarray_hash(ec, num, ptr);
+ return vm_opt_newarray_hash(ec, array_len, ptr);
}
VALUE rb_setup_fake_ary(struct RArray *fake_ary, const VALUE *list, long len);
VALUE rb_ec_pack_ary(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer);
static VALUE
-vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE target)
+vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE target)
{
if (BASIC_OP_UNREDEFINED_P(BOP_INCLUDE_P, ARRAY_REDEFINED_OP_FLAG)) {
struct RArray fake_ary = {RBASIC_INIT};
- VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num);
+ VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, array_len);
return rb_ary_includes(ary, target);
}
else {
VALUE args[1] = {target};
- return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idIncludeP, 1, args, RB_NO_KEYWORDS);
+ return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idIncludeP, 1, args, RB_NO_KEYWORDS);
}
}
VALUE
-rb_vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE target)
+rb_vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE target)
{
- return vm_opt_newarray_include_p(ec, num, ptr, target);
+ return vm_opt_newarray_include_p(ec, array_len, ptr, target);
}
static VALUE
-vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt, VALUE buffer)
+vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt, VALUE buffer)
{
if (BASIC_OP_UNREDEFINED_P(BOP_PACK, ARRAY_REDEFINED_OP_FLAG)) {
struct RArray fake_ary = {RBASIC_INIT};
- VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num);
+ VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, array_len);
return rb_ec_pack_ary(ec, ary, fmt, (UNDEF_P(buffer) ? Qnil : buffer));
}
else {
@@ -6473,20 +6473,20 @@ vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALU
argc++;
}
- return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idPack, argc, args, kw_splat);
+ return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idPack, argc, args, kw_splat);
}
}
VALUE
-rb_vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt, VALUE buffer)
+rb_vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt, VALUE buffer)
{
- return vm_opt_newarray_pack_buffer(ec, num, ptr, fmt, buffer);
+ return vm_opt_newarray_pack_buffer(ec, array_len, ptr, fmt, buffer);
}
VALUE
-rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt)
+rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt)
{
- return vm_opt_newarray_pack_buffer(ec, num, ptr, fmt, Qundef);
+ return vm_opt_newarray_pack_buffer(ec, array_len, ptr, fmt, Qundef);
}
#undef id_cmp
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 8838a72fc887bd..082db3fae44108 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -464,6 +464,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
+ Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
&Insn::ArrayMax { state, .. }
| &Insn::FixnumDiv { state, .. }
| &Insn::Throw { state, .. }
@@ -1427,6 +1428,33 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd {
asm_ccall!(asm, rb_jit_array_len, array)
}
+/// Compile opt_newarray_hash - create a hash from array elements
+fn gen_opt_newarray_hash(
+ jit: &JITState,
+ asm: &mut Assembler,
+ elements: Vec,
+ state: &FrameState,
+) -> lir::Opnd {
+ // `Array#hash` will hash the elements of the array.
+ gen_prepare_non_leaf_call(jit, asm, state);
+
+ let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
+
+ // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
+ // Get a pointer to the first element on the Ruby stack.
+ let stack_bottom = state.stack().len() - elements.len();
+ let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32));
+
+ unsafe extern "C" {
+ fn rb_vm_opt_newarray_hash(ec: EcPtr, array_len: u32, elts: *const VALUE) -> VALUE;
+ }
+
+ asm.ccall(
+ rb_vm_opt_newarray_hash as *const u8,
+ vec![EC, (array_len as u32).into(), elements_ptr],
+ )
+}
+
fn gen_array_include(
jit: &JITState,
asm: &mut Assembler,
@@ -1436,7 +1464,7 @@ fn gen_array_include(
) -> lir::Opnd {
gen_prepare_non_leaf_call(jit, asm, state);
- let num: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
+ let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
// After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
// The elements are at the bottom of the virtual stack, followed by the target.
@@ -1450,7 +1478,7 @@ fn gen_array_include(
asm_ccall!(
asm,
rb_vm_opt_newarray_include_p,
- EC, num.into(), elements_ptr, target
+ EC, array_len.into(), elements_ptr, target
)
}
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index bbe5dd3435b75b..172b177c456f3b 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -9,7 +9,7 @@ use crate::{
cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json
};
use std::{
- cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
+ cell::RefCell, collections::{BTreeSet, HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
};
use crate::hir_type::{Type, types};
use crate::bitset::BitSet;
@@ -217,21 +217,40 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
}
write!(f, ", ")?;
match bop {
- BOP_PLUS => write!(f, "BOP_PLUS")?,
- BOP_MINUS => write!(f, "BOP_MINUS")?,
- BOP_MULT => write!(f, "BOP_MULT")?,
- BOP_DIV => write!(f, "BOP_DIV")?,
- BOP_MOD => write!(f, "BOP_MOD")?,
- BOP_EQ => write!(f, "BOP_EQ")?,
- BOP_NEQ => write!(f, "BOP_NEQ")?,
- BOP_LT => write!(f, "BOP_LT")?,
- BOP_LE => write!(f, "BOP_LE")?,
- BOP_GT => write!(f, "BOP_GT")?,
- BOP_GE => write!(f, "BOP_GE")?,
- BOP_FREEZE => write!(f, "BOP_FREEZE")?,
- BOP_UMINUS => write!(f, "BOP_UMINUS")?,
- BOP_MAX => write!(f, "BOP_MAX")?,
- BOP_AREF => write!(f, "BOP_AREF")?,
+ BOP_PLUS => write!(f, "BOP_PLUS")?,
+ BOP_MINUS => write!(f, "BOP_MINUS")?,
+ BOP_MULT => write!(f, "BOP_MULT")?,
+ BOP_DIV => write!(f, "BOP_DIV")?,
+ BOP_MOD => write!(f, "BOP_MOD")?,
+ BOP_EQ => write!(f, "BOP_EQ")?,
+ BOP_EQQ => write!(f, "BOP_EQQ")?,
+ BOP_LT => write!(f, "BOP_LT")?,
+ BOP_LE => write!(f, "BOP_LE")?,
+ BOP_LTLT => write!(f, "BOP_LTLT")?,
+ BOP_AREF => write!(f, "BOP_AREF")?,
+ BOP_ASET => write!(f, "BOP_ASET")?,
+ BOP_LENGTH => write!(f, "BOP_LENGTH")?,
+ BOP_SIZE => write!(f, "BOP_SIZE")?,
+ BOP_EMPTY_P => write!(f, "BOP_EMPTY_P")?,
+ BOP_NIL_P => write!(f, "BOP_NIL_P")?,
+ BOP_SUCC => write!(f, "BOP_SUCC")?,
+ BOP_GT => write!(f, "BOP_GT")?,
+ BOP_GE => write!(f, "BOP_GE")?,
+ BOP_NOT => write!(f, "BOP_NOT")?,
+ BOP_NEQ => write!(f, "BOP_NEQ")?,
+ BOP_MATCH => write!(f, "BOP_MATCH")?,
+ BOP_FREEZE => write!(f, "BOP_FREEZE")?,
+ BOP_UMINUS => write!(f, "BOP_UMINUS")?,
+ BOP_MAX => write!(f, "BOP_MAX")?,
+ BOP_MIN => write!(f, "BOP_MIN")?,
+ BOP_HASH => write!(f, "BOP_HASH")?,
+ BOP_CALL => write!(f, "BOP_CALL")?,
+ BOP_AND => write!(f, "BOP_AND")?,
+ BOP_OR => write!(f, "BOP_OR")?,
+ BOP_CMP => write!(f, "BOP_CMP")?,
+ BOP_DEFAULT => write!(f, "BOP_DEFAULT")?,
+ BOP_PACK => write!(f, "BOP_PACK")?,
+ BOP_INCLUDE_P => write!(f, "BOP_INCLUDE_P")?,
_ => write!(f, "{bop}")?,
}
write!(f, ")")
@@ -650,6 +669,7 @@ pub enum Insn {
NewRange { low: InsnId, high: InsnId, flag: RangeType, state: InsnId },
NewRangeFixnum { low: InsnId, high: InsnId, flag: RangeType, state: InsnId },
ArrayDup { val: InsnId, state: InsnId },
+ ArrayHash { elements: Vec, state: InsnId },
ArrayMax { elements: Vec, state: InsnId },
ArrayInclude { elements: Vec, target: InsnId, state: InsnId },
DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId },
@@ -1040,6 +1060,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
+ Insn::ArrayHash { elements, .. } => {
+ write!(f, "ArrayHash")?;
+ let mut prefix = " ";
+ for element in elements {
+ write!(f, "{prefix}{element}")?;
+ prefix = ", ";
+ }
+ Ok(())
+ }
Insn::ArrayInclude { elements, target, .. } => {
write!(f, "ArrayInclude")?;
let mut prefix = " ";
@@ -1887,6 +1916,7 @@ impl Function {
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
&ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) },
&DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) },
+ &ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state },
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
&GetIvar { self_val, id, ic, state } => GetIvar { self_val: find!(self_val), id, ic, state },
&LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type },
@@ -2032,6 +2062,7 @@ impl Function {
Insn::ArrayMax { .. } => types::BasicObject,
Insn::ArrayInclude { .. } => types::BoolExact,
Insn::DupArrayInclude { .. } => types::BoolExact,
+ Insn::ArrayHash { .. } => types::Fixnum,
Insn::GetGlobal { .. } => types::BasicObject,
Insn::GetIvar { .. } => types::BasicObject,
Insn::LoadPC => types::CPtr,
@@ -3346,6 +3377,7 @@ impl Function {
worklist.push_back(val)
}
&Insn::ArrayMax { ref elements, state }
+ | &Insn::ArrayHash { ref elements, state }
| &Insn::NewHash { ref elements, state }
| &Insn::NewArray { ref elements, state } => {
worklist.extend(elements);
@@ -4102,6 +4134,7 @@ impl Function {
| Insn::InvokeBuiltin { ref args, .. }
| Insn::InvokeBlock { ref args, .. }
| Insn::NewArray { elements: ref args, .. }
+ | Insn::ArrayHash { elements: ref args, .. }
| Insn::ArrayMax { elements: ref args, .. } => {
for &arg in args {
self.assert_subtype(insn_id, arg, types::BasicObject)?;
@@ -4908,6 +4941,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let elements = state.stack_pop_n(count)?;
let (bop, insn) = match method {
VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }),
+ VM_OPT_NEWARRAY_SEND_HASH => (BOP_HASH, Insn::ArrayHash { elements, state: exit_id }),
VM_OPT_NEWARRAY_SEND_INCLUDE_P => {
let target = elements[elements.len() - 1];
let array_elements = elements[..elements.len() - 1].to_vec();
@@ -5849,7 +5883,13 @@ impl<'a> ControlFlowInfo<'a> {
// Since ZJIT uses extended basic blocks, one must check all instructions
// for their ability to jump to another basic block, rather than just
// the instructions at the end of a given basic block.
- let successors: Vec = block
+ //
+ // Use BTreeSet to avoid duplicates and maintain an ordering. Also
+ // `BTreeSet` provides conversion trivially back to an `Vec`.
+ // Ordering is important so that the expect tests that serialize the predecessors
+ // and successors don't fail intermittently.
+ // todo(aidenfoxivey): Use `BlockSet` in lieu of `BTreeSet`
+ let successors: BTreeSet = block
.insns
.iter()
.map(|&insn_id| uf.find_const(insn_id))
@@ -5867,7 +5907,8 @@ impl<'a> ControlFlowInfo<'a> {
}
// Store successors for this block.
- successor_map.insert(block_id, successors);
+ // Convert successors from a `BTreeSet` to a `Vec`.
+ successor_map.insert(block_id, successors.iter().copied().collect());
}
Self {
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index 5e6ec118922b02..76a74c75eb4613 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -1953,6 +1953,35 @@ pub mod hir_build_tests {
");
}
+ #[test]
+ fn test_opt_newarray_send_max_redefined() {
+ eval("
+ class Array
+ alias_method :old_max, :max
+ def max
+ old_max * 2
+ end
+ end
+
+ def test(a,b) = [a,b].max
+ ");
+ assert_contains_opcode("test", YARVINSN_opt_newarray_send);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:9:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@5
+ v3:BasicObject = GetLocal l0, SP@4
+ Jump bb2(v1, v2, v3)
+ bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v6, v7, v8)
+ bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject):
+ SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX))
+ ");
+ }
+
#[test]
fn test_opt_newarray_send_min() {
eval("
@@ -2013,7 +2042,49 @@ pub mod hir_build_tests {
Jump bb2(v8, v9, v10, v11, v12)
bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
v25:BasicObject = SendWithoutBlock v15, :+, v16
- SideExit UnhandledNewarraySend(HASH)
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH)
+ v32:Fixnum = ArrayHash v15, v16
+ PatchPoint NoEPEscape(test)
+ v39:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v40:ArrayExact = ArrayDup v39
+ v42:BasicObject = SendWithoutBlock v14, :puts, v40
+ PatchPoint NoEPEscape(test)
+ CheckInterrupts
+ Return v32
+ ");
+ }
+
+ #[test]
+ fn test_opt_newarray_send_hash_redefined() {
+ eval("
+ Array.class_eval { def hash = 42 }
+
+ def test(a,b)
+ sum = a+b
+ result = [a,b].hash
+ puts [1,2,3]
+ result
+ end
+ ");
+ assert_contains_opcode("test", YARVINSN_opt_newarray_send);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:5:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@7
+ v3:BasicObject = GetLocal l0, SP@6
+ v4:NilClass = Const Value(nil)
+ v5:NilClass = Const Value(nil)
+ Jump bb2(v1, v2, v3, v4, v5)
+ bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject):
+ EntryPoint JIT(0)
+ v11:NilClass = Const Value(nil)
+ v12:NilClass = Const Value(nil)
+ Jump bb2(v8, v9, v10, v11, v12)
+ bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
+ v25:BasicObject = SendWithoutBlock v15, :+, v16
+ SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH))
");
}
@@ -2081,7 +2152,7 @@ pub mod hir_build_tests {
Jump bb2(v8, v9, v10, v11, v12)
bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
v25:BasicObject = SendWithoutBlock v15, :+, v16
- PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33)
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P)
v33:BoolExact = ArrayInclude v15, v16 | v16
PatchPoint NoEPEscape(test)
v40:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
@@ -2093,6 +2164,45 @@ pub mod hir_build_tests {
");
}
+ #[test]
+ fn test_opt_newarray_send_include_p_redefined() {
+ eval("
+ class Array
+ alias_method :old_include?, :include?
+ def include?(x)
+ old_include?(x)
+ end
+ end
+
+ def test(a,b)
+ sum = a+b
+ result = [a,b].include? b
+ puts [1,2,3]
+ result
+ end
+ ");
+ assert_contains_opcode("test", YARVINSN_opt_newarray_send);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:10:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@7
+ v3:BasicObject = GetLocal l0, SP@6
+ v4:NilClass = Const Value(nil)
+ v5:NilClass = Const Value(nil)
+ Jump bb2(v1, v2, v3, v4, v5)
+ bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject):
+ EntryPoint JIT(0)
+ v11:NilClass = Const Value(nil)
+ v12:NilClass = Const Value(nil)
+ Jump bb2(v8, v9, v10, v11, v12)
+ bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
+ v25:BasicObject = SendWithoutBlock v15, :+, v16
+ SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P))
+ ");
+ }
+
#[test]
fn test_opt_duparray_send_include_p() {
eval("
@@ -2112,7 +2222,7 @@ pub mod hir_build_tests {
EntryPoint JIT(0)
Jump bb2(v5, v6)
bb2(v8:BasicObject, v9:BasicObject):
- PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33)
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P)
v15:BoolExact = DupArrayInclude VALUE(0x1000) | v9
CheckInterrupts
Return v15
@@ -2144,7 +2254,7 @@ pub mod hir_build_tests {
EntryPoint JIT(0)
Jump bb2(v5, v6)
bb2(v8:BasicObject, v9:BasicObject):
- SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33))
+ SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P))
");
}
@@ -3484,6 +3594,27 @@ pub mod hir_build_tests {
assert!(cfi.is_succeeded_by(bb1, bb0));
assert!(cfi.is_succeeded_by(bb3, bb1));
}
+
+ #[test]
+ fn test_cfi_deduplicated_successors_and_predecessors() {
+ let mut function = Function::new(std::ptr::null());
+
+ let bb0 = function.entry_block;
+ let bb1 = function.new_block(0);
+
+ // Construct two separate jump instructions.
+ let v1 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
+ let _ = function.push_insn(bb0, Insn::IfTrue { val: v1, target: edge(bb1)});
+ function.push_insn(bb0, Insn::Jump(edge(bb1)));
+
+ let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) });
+ function.push_insn(bb1, Insn::Return { val: retval });
+
+ let cfi = ControlFlowInfo::new(&function);
+
+ assert_eq!(cfi.predecessors(bb1).collect::>().len(), 1);
+ assert_eq!(cfi.successors(bb0).collect::>().len(), 1);
+ }
}
/// Test dominator set computations.