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
cce5b24
[ruby/rubygems] Fix bundle outdated --groups to actually group output
nevinera Jul 15, 2026
5696161
[ruby/rubygems] Keep bundle outdated --group sorted by gem name
hsbt Sep 15, 2026
fc2bbe0
[ruby/rubygems] Sort the group names of each gem in bundle outdated -…
hsbt Sep 15, 2026
7ae0025
[ruby/rubygems] List gems without groups last in bundle outdated --gr…
hsbt Sep 15, 2026
685144e
Fix crash when source string modified in String#unpack
peterzhu2118 Sep 15, 2026
0153838
[ruby/rubygems] Build a platform gem with gem build --platform for th…
hsbt Sep 15, 2026
ca34d4b
[ruby/rubygems] Support safe.bareRepository=explicit in git sources
hsbt Sep 15, 2026
0b8e0b1
[ruby/rubygems] Stop vendoring resolv for two regexps
hsbt Sep 16, 2026
e60ce57
[ruby/rubygems] Use the vendored SecureRandom in Gem::AtomicFileWriter
hsbt Sep 16, 2026
e0d7be5
Use a pointer-sized length limit in the specs
hsbt Sep 15, 2026
8d2b32d
Accept RangeError as well for a too-large length
hsbt Sep 16, 2026
94f5f02
CI: Add workflow for shadow stack testing. (#18778)
samuel-williams-shopify Sep 16, 2026
5eeaedc
Fix use-after-free when clearing array during flatten
peterzhu2118 Sep 16, 2026
fb02f6e
hash.c: Guard the uninitialized st_table scrub with #if RUBY_DEBUG
hsbt Sep 16, 2026
ab845a8
imemo.c: Guard the uninitialized st_table scrub with #if RUBY_DEBUG
hsbt Sep 16, 2026
5d8cb98
[DOC] Fix shareable proc typo in Ractor documentation
bensheldon Sep 16, 2026
578e8a0
[ruby/rubygems] Stop exporting the default RGV to child processes
hsbt Sep 16, 2026
e22f51b
Fix the check for an already loaded mkmf.rb
hsbt Sep 16, 2026
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
98 changes: 98 additions & 0 deletions .github/workflows/shstk-amd64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: SHSTK AMD64

on:
push:
paths-ignore:
- 'doc/**'
- '**/man/*'
- '**.md'
- '**.rdoc'
- '**/.document'
- '.*.yml'
pull_request:
paths-ignore:
- 'doc/**'
- '**/man/*'
- '**.md'
- '**.rdoc'
- '**/.document'
- '.*.yml'
merge_group:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }}
cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }}

permissions:
contents: read

jobs:
shstk:
name: make (check, SHSTK)
runs-on: ubuntu-24.04

container:
image: fedora:44

env:
CONFIGURE_TTY: never
GLIBC_TUNABLES: glibc.cpu.hwcaps=SHSTK
GNUMAKEFLAGS: -sj4
RUBY_DEBUG: ci

steps:
- name: Install dependencies
run: >-
dnf install -y
autoconf automake binutils gcc gcc-c++ git libffi-devel libtool
libyaml-devel make ncurses-devel openssl-devel procps-ng
readline-devel ruby ruby-devel shadow-utils zlib-devel
working-directory:

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout-cone-mode: false
sparse-checkout: /.github
persist-credentials: false

- uses: $/.github/actions/setup/directories
with:
srcdir: src
builddir: build
makeup: true
clean: true

- name: Create test user
run: |
useradd --create-home ruby
chown -R ruby:ruby "$GITHUB_WORKSPACE"
working-directory:

- name: Run configure
run: >-
runuser -u ruby -- ../src/configure -C
--enable-debug-env
--disable-install-doc
--with-gcc="gcc -fcf-protection=return"

- run: runuser -u ruby -- make

- name: Require active shadow stacks
run: |
readelf --notes ./miniruby | grep 'x86 feature: SHSTK'
runuser -u ruby -- ./miniruby -e '
line = File.foreach("/proc/self/status").find { |entry| entry.start_with?("x86_Thread_features:") }
features = line&.split(":", 2)&.last&.split || []
abort "SHSTK is not active (x86_Thread_features: #{features.join(" ")})" unless features.include?("shstk")
'

- name: make check
env:
RUBY_TESTOPTS: '-q --tty=no'
run: runuser -u ruby -- make -s check RUBYOPT=-w
timeout-minutes: 40

defaults:
run:
working-directory: build
9 changes: 7 additions & 2 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -6697,7 +6697,7 @@ static VALUE
flatten(VALUE ary, int level)
{
long i;
VALUE stack, result, tmp = 0, elt;
VALUE stack, result, tmp = Qnil, elt;
VALUE memo = Qfalse;

for (i = 0; i < RARRAY_LEN(ary); i++) {
Expand All @@ -6707,9 +6707,14 @@ flatten(VALUE ary, int level)
break;
}
}
if (i == RARRAY_LEN(ary)) {
if (NIL_P(tmp)) {
return ary;
}
if (i > RARRAY_LEN(ary)) {
/* ary was shrunk while converting an element with #to_ary, so
the scanned elements may no longer exist in ary */
i = RARRAY_LEN(ary);
}

result = ary_new(0, RARRAY_LEN(ary));
ary_memcpy(result, 0, i, RARRAY_CONST_PTR(ary));
Expand Down
2 changes: 1 addition & 1 deletion doc/language/ractor.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ See [syntax/comments.rdoc](../syntax/comments.rdoc) for more details.

### Shareable procs

Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the proc's block is isolated from its outer environment, so it cannot access variables from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize the value to a different shareable object.
Procs and lambdas are unshareable objects, even when they are frozen. To create a shareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the proc's block is isolated from its outer environment, so it cannot access variables from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize the value to a different shareable object.

```ruby
p = Ractor.shareable_proc { p self }
Expand Down
2 changes: 1 addition & 1 deletion enc/make_encmake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# baseruby's cgi/escape.so and source cgi/escape.rb via erb.
$:.unshift("#{dir}/lib") unless defined?(CROSS_COMPILING)
$:.unshift(Dir.pwd, "#{dir}/tool/lib")
unless $".any? {|feat| File.basename(feat) == "/mkmf.rb"}
unless $".any? {|feat| File.basename(feat) == "mkmf.rb"}
$" << "mkmf.rb"
load File.expand_path("lib/mkmf.rb", dir)
end
Expand Down
2 changes: 1 addition & 1 deletion hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -1555,7 +1555,7 @@ hash_alloc(VALUE klass, VALUE flags, VALUE ifnone, size_t size, bool frozen)

RHASH_AR_TABLE(hash)->ar_hint.word = 0;

#ifdef RUBY_DEBUG
#if RUBY_DEBUG
if (hash_slot_size(size, frozen) >= sizeof(struct RHash) + sizeof(st_table)) {
RHASH_ST_TABLE(hash)->num_entries = 0;
RHASH_ST_TABLE(hash)->entries = NULL;
Expand Down
2 changes: 1 addition & 1 deletion imemo.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ rb_imemo_fields_clone(VALUE fields_obj)
// to mark an uninitialized table.
clone = imemo_fields_new(owner, ROOT_SHAPE_ID, sizeof(struct rb_fields), false /* TODO: check */);
st_table *dest_table = rb_imemo_fields_complex_tbl(clone);
#ifdef RUBY_DEBUG
#if RUBY_DEBUG
dest_table->entries = NULL;
#endif
st_replace(dest_table, src_table);
Expand Down
18 changes: 8 additions & 10 deletions lib/bundler/cli/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def run
dependency = current_dependencies[current_spec.name]
groups = ""
if dependency && !options[:parseable]
groups = dependency.groups.join(", ")
groups = dependency.groups
groups = groups.sort if options_include_groups
groups = groups.join(", ")
end

outdated_gems << {
Expand All @@ -101,13 +103,11 @@ def run
}
end

relevant_outdated_gems = if options_include_groups
outdated_gems.group_by {|g| g[:groups] }.sort.flat_map do |groups, gems|
contains_group = groups.split(", ").include?(options[:group])
next unless options[:groups] || contains_group

gems
end.compact
relevant_outdated_gems = if options[:groups]
without_groups, with_groups = outdated_gems.partition {|g| g[:groups].empty? }
with_groups.group_by {|g| g[:groups] }.sort.flat_map(&:last) + without_groups
elsif options_include_groups
outdated_gems.select {|g| g[:groups].split(", ").include?(options[:group]) }
else
outdated_gems
end
Expand Down Expand Up @@ -333,8 +333,6 @@ def print_indented(matrix)

Bundler.ui.info justify(header, column_sizes)

data.sort_by! {|row| row[0] }

data.each do |row|
Bundler.ui.info justify(row, column_sizes)
end
Expand Down
10 changes: 9 additions & 1 deletion lib/bundler/source/git/git_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,15 @@ def capture3_args_for(cmd, dir)

return ["git", *opts, *cmd] unless dir

["git", "-C", dir.to_s, *opts, *cmd]
# With safe.bareRepository=explicit, git refuses to discover a bare
# repository from -C, so the cache clone is named with --git-dir.
# Working trees, like a local override, still go through -C.
location = bare_repo?(dir) ? "--git-dir" : "-C"
["git", location, dir.to_s, *opts, *cmd]
end

def bare_repo?(dir)
File.exist?(File.join(dir, "objects")) && File.exist?(File.join(dir, "HEAD"))
end

def extra_clone_args
Expand Down
5 changes: 5 additions & 0 deletions lib/rubygems/commands/build_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def build_gem
def build_package(gemspec)
spec = Gem::Specification.load(gemspec)
if spec
# Gem::Specification#initialize applies --platform unless it is the local platform
if options[:added_platform] && spec.platform == Gem::Platform::RUBY && Gem.platforms.last == Gem::Platform.local
spec.platform = Gem::Platform.local
end

Gem::Package.build(
spec,
options[:force],
Expand Down
6 changes: 4 additions & 2 deletions lib/rubygems/util/atomic_file_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class AtomicFileWriter
# want other processes or threads to see half-written files.

def self.open(file_name)
require "securerandom" unless defined?(SecureRandom)
# Vendored, because activating the securerandom default gem here pins it for
# the rest of the process and conflicts with gems that need a newer one.
require_relative "../vendored_securerandom" unless defined?(Gem::SecureRandom)

old_stat = begin
File.stat(file_name)
Expand All @@ -21,7 +23,7 @@ def self.open(file_name)
end

# Names can't be longer than 255B
tmp_suffix = ".tmp.#{SecureRandom.hex}"
tmp_suffix = ".tmp.#{Gem::SecureRandom.hex}"
dirname = File.dirname(file_name)
basename = File.basename(file_name)
base_slice = byteslice_at_char_boundary(basename, 254 - tmp_suffix.bytesize)
Expand Down
26 changes: 24 additions & 2 deletions lib/rubygems/vendor/net-http/lib/net/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

require_relative '../../../net-protocol/lib/net/protocol'
require_relative '../../../uri/lib/uri'
require_relative '../../../resolv/lib/resolv'
autoload :OpenSSL, 'openssl'

module Gem::Net #:nodoc:
Expand Down Expand Up @@ -1535,6 +1534,29 @@ def use_ssl=(flag)

SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc:

# Resolv::IPv4::Regex and Resolv::IPv6::Regex, copied from resolv 0.7.1 rather
# than required, so that net/http does not load a DNS resolver just to keep an
# IP address literal out of the Server Name Indication.
# https://github.com/ruby/resolv/blob/v0.7.1/lib/resolv.rb
ipv4_octet = /0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?/
hex16 = /[0-9A-Fa-f]{1,4}/
hex16_group = /(?:#{hex16}(?::#{hex16})*)?/
dotted_quad = /\d+\.\d+\.\d+\.\d+/
zone_id = /%[-0-9A-Za-z._~]+/

IPV4_ADDRESS = /\A(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\z/ # :nodoc:

IPV6_ADDRESS = /\A(?:
(?:#{hex16}:){7}#{hex16} # a:b:c:d:e:f:g:h
| #{hex16_group}::#{hex16_group} # a::b
| (?:#{hex16}:){6}#{dotted_quad} # a:b:c:d:e:f:w.x.y.z
| #{hex16_group}::(?:#{hex16}:)*#{dotted_quad} # a::b:w.x.y.z
| [Ff][Ee]80(?::#{hex16}){7}#{zone_id} # fe80:b:c:d:e:f:g:h%em1
| [Ff][Ee]80:(?:#{hex16_group}::#{hex16_group}|:#{hex16_group})?:#{hex16}#{zone_id} # fe80::b%em1
)\z/x # :nodoc:

private_constant :IPV4_ADDRESS, :IPV6_ADDRESS

# Sets or returns the path to a CA certification file in PEM format.
attr_accessor :ca_file

Expand Down Expand Up @@ -1734,7 +1756,7 @@ def connect

# Server Name Indication (SNI) RFC 3546/6066
case @address
when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex
when IPV4_ADDRESS, IPV6_ADDRESS
# don't set SNI, as IP addresses in SNI is not valid
# per RFC 6066, section 3.

Expand Down
Loading