Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .document
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ lib
ext

# GC is split between gc.rb and gc/default/default.c
gc/default
gc/default/*.c

# For `prism`, ruby code is in lib and c in the prism folder
prism
prism/*.c

# rdoc files
NEWS.md
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ jobs:
run_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=1'
specopts: '-T --zjit-inline-threshold=0 -T --zjit-call-threshold=1'
configure: '--enable-zjit=dev'
tests: '--exclude=ruby/test_hash.rb'

# The optimizer benefits from at least 1 iteration of profiling. Also, many
# regression tests in bootstraptest/test_yjit.rb assume call-threshold=2.
Expand Down
19 changes: 11 additions & 8 deletions bootstraptest/test_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
assert_equal %q{2}, %q{
[1,2,3].find{|x| x == 2}
}
assert_equal %q{2}, %q{
class E
include Enumerable
def each(&block)
[1, 2, 3].each(&block)
# TODO: Remove this clang 17 CI workaround once #17953's regression is fixed.
unless ENV["GITHUB_ACTIONS"] == "true" && ENV["INPUT_WITH_GCC"] == "clang-17"
assert_equal %q{2}, %q{
class E
include Enumerable
def each(&block)
[1, 2, 3].each(&block)
end
end
end
E.new.find {|x| x == 2 }
}
E.new.find {|x| x == 2 }
}
end
assert_equal %q{6}, %q{
sum = 0
for x in [1, 2, 3]
Expand Down
9 changes: 6 additions & 3 deletions bootstraptest/test_flow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -557,9 +557,12 @@ def each(&block)
end
e = Bug6460.new
}]].each do |bug, src|
assert_equal "foo", src + %q{e.detect {true}}, bug
assert_equal "true", src + %q{e.any? {true}}, bug
assert_equal "false", src + %q{e.all? {false}}, bug
# TODO: Remove this clang 17 CI workaround once #17953's regression is fixed.
unless ENV["GITHUB_ACTIONS"] == "true" && ENV["INPUT_WITH_GCC"] == "clang-17"
assert_equal "foo", src + %q{e.detect {true}}, bug
assert_equal "true", src + %q{e.any? {true}}, bug
assert_equal "false", src + %q{e.all? {false}}, bug
end
assert_equal "true", src + %q{e.include?(:foo)}, bug
end

Expand Down
13 changes: 8 additions & 5 deletions eval_intern.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ extern int select_large_fdset(int, fd_set *, fd_set *, fd_set *, struct timeval
EC_SAVE_TAG_CFP(_tag, _ec); \
rb_vm_tag_jmpbuf_init(&_tag.buf); \

// Remember the CFP as of EC_PUSH_TAG so that ZJIT can materialize frames
// only up to longjmp's target CFP. When a C method does longjmp inside it,
// the target CFP may not be equal to the VM_FRAME_FLAG_FINISH frame.
// Remember the CFP and whether it was already running in ZJIT as of
// EC_PUSH_TAG. When a C method does longjmp inside it, the target CFP may not
// be equal to the VM_FRAME_FLAG_FINISH frame. If its ZJIT frame predates this
// tag, its native stack survives the jump and should not be materialized.
#if USE_ZJIT
# define EC_SAVE_TAG_CFP(_tag, _ec) _tag.cfp = _ec->cfp
# define EC_SAVE_TAG_CFP(_tag, _ec) \
_tag.cfp = _ec->cfp; \
_tag.zjit_frame_active = CFP_ZJIT_FRAME_P(_ec->cfp)
#else
# define EC_SAVE_TAG_CFP(_tag, _ec)
#endif
Expand Down Expand Up @@ -166,7 +169,7 @@ rb_ec_tag_jump(const rb_execution_context_t *ec, enum ruby_tag_type st)
{
RUBY_ASSERT(st > TAG_NONE && st <= TAG_FATAL, ": Invalid tag jump: %d", (int)st);
#if USE_ZJIT
rb_zjit_materialize_frames(ec, ec->cfp);
rb_zjit_materialize_frames_for_longjmp(ec, ec->cfp);
#endif
ec->tag->state = st;
ruby_longjmp(RB_VM_TAG_JMPBUF_GET(ec->tag->buf), 1);
Expand Down
2 changes: 2 additions & 0 deletions internal/struct.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ struct RStruct {
VALUE rb_struct_init_copy(VALUE copy, VALUE s);
VALUE rb_struct_lookup(VALUE s, VALUE idx);
VALUE rb_struct_s_keyword_init(VALUE klass);
void rb_struct_define_aref_method(VALUE nstr, ID name, unsigned int off);

static inline long RSTRUCT_EMBED_LEN(VALUE st);
static inline long RSTRUCT_LEN_RAW(VALUE st);
static inline int RSTRUCT_LENINT(VALUE st);
Expand Down
14 changes: 12 additions & 2 deletions lib/bundler/cli/open.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@ def run
Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist."
else
root_path = spec.full_gem_path
require "shellwords"
command = Shellwords.split(editor) << File.join([root_path, path].compact)
command = editor_command(editor) << File.join([root_path, path].compact)
Bundler.with_original_env do
system(*command, { chdir: root_path })
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
end
end

def editor_command(editor)
# On Windows an editor is often configured with a full path such as
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
# would corrupt. Take a value that names an existing file as a
# single word.
return [editor] if Gem.win_platform? && File.file?(editor)

require "shellwords"
Shellwords.split(editor)
end
end
end
22 changes: 11 additions & 11 deletions lib/bundler/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def cache(custom_path = nil, local = false)
end
end

Dir[cache_path.join("*/.git")].each do |git_dir|
Gem::Util.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir|
FileUtils.rm_rf(git_dir)
FileUtils.touch(File.expand_path("../.bundlecache", git_dir))
end
Expand All @@ -159,13 +159,13 @@ def prune_cache(cache_path)
end

def clean(dry_run = false)
gem_bins = Dir["#{Gem.dir}/bin/*"]
git_dirs = Dir["#{Gem.dir}/bundler/gems/*"]
git_cache_dirs = Dir["#{Gem.dir}/cache/bundler/git/*"]
gem_dirs = Dir["#{Gem.dir}/gems/*"]
gem_files = Dir["#{Gem.dir}/cache/*.gem"]
gemspec_files = Dir["#{Gem.dir}/specifications/*.gemspec"]
extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]
gem_bins = Gem::Util.glob_files_in_dir("bin/*", Gem.dir)
git_dirs = Gem::Util.glob_files_in_dir("bundler/gems/*", Gem.dir)
git_cache_dirs = Gem::Util.glob_files_in_dir("cache/bundler/git/*", Gem.dir)
gem_dirs = Gem::Util.glob_files_in_dir("gems/*", Gem.dir)
gem_files = Gem::Util.glob_files_in_dir("cache/*.gem", Gem.dir)
gemspec_files = Gem::Util.glob_files_in_dir("specifications/*.gemspec", Gem.dir)
extension_dirs = Gem::Util.glob_files_in_dir("extensions/*/*/*", Gem.dir) + Gem::Util.glob_files_in_dir("bundler/gems/extensions/*/*/*", Gem.dir)
spec_gem_paths = []
# need to keep git sources around
spec_git_paths = @definition.spec_git_paths
Expand Down Expand Up @@ -232,7 +232,7 @@ def clean(dry_run = false)
private

def prune_gem_cache(resolve, cache_path)
cached = Dir["#{cache_path}/*.gem"]
cached = Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s)

cached = cached.delete_if do |path|
spec = Bundler.rubygems.spec_from_gem path
Expand All @@ -257,7 +257,7 @@ def prune_gem_cache(resolve, cache_path)
end

def prune_git_and_path_cache(resolve, cache_path)
cached = Dir["#{cache_path}/*/.bundlecache"]
cached = Gem::Util.glob_files_in_dir("*/.bundlecache", cache_path.to_s)

cached = cached.delete_if do |path|
name = File.basename(File.dirname(path))
Expand All @@ -283,7 +283,7 @@ def setup_manpath
# Add man/ subdirectories from activated bundles to MANPATH for man(1)
manuals = $LOAD_PATH.filter_map do |path|
man_subdir = path.sub(/lib$/, "man")
man_subdir unless Dir[man_subdir + "/man?/"].empty?
man_subdir unless Dir.glob("man?/", base: man_subdir).empty?
end

return if manuals.empty?
Expand Down
7 changes: 6 additions & 1 deletion lib/bundler/self_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ def restart_with(version)

argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
cmd = [argv0, *ARGV]
cmd.unshift(Gem.ruby) unless File.executable?(argv0)
unless File.executable?(argv0)
# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the exec'd command.
require "shellwords"
cmd.unshift(*Shellwords.split(Gem.ruby))
end

Bundler.with_original_env do
Kernel.exec(
Expand Down
7 changes: 6 additions & 1 deletion lib/bundler/shared_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,12 @@ def set_path

def set_rubyopt
rubyopt = [ENV["RUBYOPT"]].compact
setup_require = "-r#{File.expand_path("setup", __dir__)}"
setup_path = File.expand_path("setup", __dir__)
# RUBYOPT is split on whitespace with no quoting mechanism, so an
# absolute path containing spaces would be torn apart. Fall back to
# requiring by feature name; set_rubylib puts our lib directory first
# on the child's load path.
setup_require = /\s/.match?(setup_path) ? "-rbundler/setup" : "-r#{setup_path}"
return if !rubyopt.empty? && rubyopt.first.include?(setup_require)
rubyopt.unshift setup_require
Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/source/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def humanized_ref

def serialize_gemspecs_in(destination)
destination = destination.expand_path(Bundler.root) if destination.relative?
Dir["#{destination}/#{@glob}"].each do |spec_path|
Gem::Util.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
# Evaluate gemspecs and cache the result. Gemspecs
# in git might require git or other dependencies.
# The gemspecs we cache should already be evaluated.
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/source/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def cached_specs
@cached_specs ||= begin
idx = Index.new

Dir["#{cache_path}/*.gem"].each do |gemfile|
Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
s ||= Bundler.rubygems.spec_from_gem(gemfile)
s.source = self
idx << s
Expand Down
32 changes: 18 additions & 14 deletions lib/rubygems/basic_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,7 @@ def source_paths
# Return all files in this gem that match for +glob+.

def matches_for_glob(glob) # TODO: rename?
glob = File.join(lib_dirs_glob, glob)

Dir[glob]
Gem::Util.glob_files_in_dir(File.join(lib_dirs, glob), full_gem_path)
end

##
Expand All @@ -323,17 +321,7 @@ def plugins
# for this spec.

def lib_dirs_glob
dirs = if raw_require_paths
if raw_require_paths.size > 1
"{#{raw_require_paths.join(",")}}"
else
raw_require_paths.first
end
else
"lib" # default value for require_paths for bundler/inline
end

"#{full_gem_path}/#{dirs}"
"#{full_gem_path}/#{lib_dirs}"
end

##
Expand Down Expand Up @@ -364,6 +352,22 @@ def this

private

##
# Returns the require_paths of this gem as a string usable in Dir.glob,
# relative to full_gem_path.

def lib_dirs
if raw_require_paths
if raw_require_paths.size > 1
"{#{raw_require_paths.join(",")}}"
else
raw_require_paths.first
end
else
"lib" # default value for require_paths for bundler/inline
end
end

def have_extensions?
!extensions.empty?
end
Expand Down
13 changes: 12 additions & 1 deletion lib/rubygems/commands/open_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,18 @@ def open_gem(name)
end

def open_editor(path)
system(*@editor.split(/\s+/) + [path], { chdir: path })
system(*editor_command(@editor), path, { chdir: path })
end

def editor_command(editor) # :nodoc:
# On Windows an editor is often configured with a full path such as
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
# would corrupt. Take a value that names an existing file as a
# single word.
return [editor] if Gem.win_platform? && File.file?(editor)

require "shellwords"
Shellwords.split(editor)
end

def spec_for(name)
Expand Down
5 changes: 4 additions & 1 deletion lib/rubygems/commands/push_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,19 @@ def send_push_request_with_attestation(name, args)

def attest!(name)
require "open3"
require "shellwords"
require "tempfile"

tempfile = Tempfile.new([File.basename(name, ".*"), ".sigstore.json"])
bundle = tempfile.path
tempfile.close(false)

env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h
# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the spawned command.
out, st = Open3.capture2e(
env,
Gem.ruby, "-S", "gem", "exec", "--conservative",
*Shellwords.split(Gem.ruby), "-S", "gem", "exec", "--conservative",
"sigstore-cli", "sign", name, "--bundle", bundle,
unsetenv_others: true
)
Expand Down
5 changes: 4 additions & 1 deletion lib/rubygems/commands/update_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ def install_rubygems(spec) # :nodoc:
say "Installing RubyGems #{version}" unless options[:silent]

installed = preparing_gem_layout_for(version) do
system Gem.ruby, "--disable-gems", "setup.rb", *args
# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the spawned command.
require "shellwords"
system(*Shellwords.split(Gem.ruby), "--disable-gems", "setup.rb", *args)
end

unless options[:silent]
Expand Down
21 changes: 20 additions & 1 deletion lib/rubygems/ext/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = [
target_rbconfig["configure_args"] =~ /with-make-prog\=(\w+)/
make_program_name = ENV["MAKE"] || ENV["make"] || $1
make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
make_program = shellsplit(make_program_name)
make_program = shellsplit_command(make_program_name)

is_nmake = /\bnmake/i.match?(make_program_name)
# The installation of the bundled gems is failed when DESTDIR is empty in mswin platform.
Expand Down Expand Up @@ -102,6 +102,10 @@ def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {})
require "open3"
# Set $SOURCE_DATE_EPOCH for the subprocess.
build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env)
# A single-element command would be parsed as a shell command line,
# splitting an unquoted command path containing spaces. Use the
# [cmdname, argv0] form to keep exec semantics.
command = [[command.first, command.first]] if command.size == 1
output, status = begin
Open3.popen2e(build_env, *command, chdir: dir) do |stdin, stdouterr, wait_thread|
stdin.close
Expand Down Expand Up @@ -148,6 +152,21 @@ def self.shellsplit(command)
Shellwords.split(command)
end

##
# Splits a command string such as ENV["MAKE"] into an argument list.
#
# On Windows, a value that names an existing file, such as
# <tt>C:\path\nmake.exe</tt>, is taken as a single word because POSIX
# shell splitting would consume the backslashes as escape characters.
# Any other value is split like a POSIX shell command line, where a
# path containing spaces must be quoted.

def self.shellsplit_command(command)
return [command] if Gem.win_platform? && File.file?(command)

shellsplit(command)
end

def self.shelljoin(command)
require "shellwords"

Expand Down
6 changes: 4 additions & 2 deletions lib/rubygems/ext/rake_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio
end

if /mkrf_conf/i.match?(File.basename(extension))
run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir)
# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the spawned command.
run([*shellsplit(Gem.ruby), File.basename(extension), *args], results, class_name, extension_dir)
end

rake = ENV["rake"]

if rake
rake = shellsplit(rake)
rake = shellsplit_command(rake)
else
begin
rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake")
Expand Down
Loading