Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,11 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
shareable-object growth, reclaiming dead Ractors' heaps). Allocation-heavy
Ractor programs now scale like forked processes.

* `GC.start(global: false)` can be used when multiple Ractors are running to force
a Ractor-local GC. By default, `GC.start` runs a global GC (all Ractors) like
before. This can also be triggered with `GC.start(global: true)`. The `global`
keyword argument has no effect when a single Ractor is running.

Visible behavior changes:

* `Ractor#value` returns the value only once; a second call raises
Expand Down
6 changes: 5 additions & 1 deletion class.c
Original file line number Diff line number Diff line change
Expand Up @@ -2992,7 +2992,11 @@ singleton_class_of(VALUE obj, bool ensure_eigenclass)
RCLASS_ATTACHED_OBJECT(klass) == obj)) {
klass = rb_make_metaclass(obj, klass);
}
RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj));
if (RB_OBJ_FROZEN_RAW(obj)) {
// Freeze through rb_obj_freeze_inline so the singleton class also
// gets a frozen shape_id, not just the FL_FREEZE flag.
rb_obj_freeze_inline(klass);
}
if (ensure_eigenclass && RB_TYPE_P(obj, T_CLASS)) {
/* ensures an exposed class belongs to its own eigenclass */
(void)ENSURE_EIGENCLASS(klass);
Expand Down
10 changes: 5 additions & 5 deletions ext/win32/resolv/resolv.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,11 @@ InitVM_resolv(void)

reg_key_class = regkey;
rb_undef_alloc_func(regkey);
rb_define_private_method(singl, "get_dns_server_list", get_dns_server_list, 0);
rb_define_private_method(singl, "tcpip_params", tcpip_params_open, 0);
rb_define_method(regkey, "open", reg_open, 1);
rb_define_method(regkey, "each_key", reg_each_key, 0);
rb_define_method(regkey, "value", reg_value, 1);
rb_define_private_method(singl, "get_dns_server_list", RUBY_METHOD_FUNC(get_dns_server_list), 0);
rb_define_private_method(singl, "tcpip_params", RUBY_METHOD_FUNC(tcpip_params_open), 0);
rb_define_method(regkey, "open", RUBY_METHOD_FUNC(reg_open), 1);
rb_define_method(regkey, "each_key", RUBY_METHOD_FUNC(reg_each_key), 0);
rb_define_method(regkey, "value", RUBY_METHOD_FUNC(reg_value), 1);
}

void
Expand Down
5 changes: 3 additions & 2 deletions object.c
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,9 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze)
}

if (RB_OBJ_FROZEN(obj)) {
shape_id_t next_shape_id = rb_obj_shape_transition_frozen(clone);
RBASIC_SET_SHAPE_ID(clone, next_shape_id);
// Not just the shape_id transition: a frozen class or module must
// also have a frozen fields object.
rb_obj_freeze_inline(clone);
}
break;
case Qtrue: {
Expand Down
1 change: 1 addition & 0 deletions re.c
Original file line number Diff line number Diff line change
Expand Up @@ -4895,6 +4895,7 @@ match_setter(VALUE val, ID _x, VALUE *_y)
{
if (!NIL_P(val)) {
Check_Type(val, T_MATCH);
rb_match_busy(val);
}
rb_backref_set(val);
}
Expand Down
2 changes: 1 addition & 1 deletion test/resolv/test_win32_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'test/unit'
require 'resolv'

if defined?(Win32::Resolve)
if defined?(Win32::Resolv)
class TestWin32Config < Test::Unit::TestCase
def test_get_item_property_string
# Test reading a string registry value
Expand Down
27 changes: 27 additions & 0 deletions test/ruby/test_class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,33 @@ def foo
assert_equal("mod#foo", copy.new.foo)
end

def test_dup_of_frozen_class_is_not_frozen
original = Class.new
original.instance_variable_set(:@a, 1)
original.freeze

copy = original.dup
assert_not_predicate(copy, :frozen?)
copy.instance_variable_set(:@b, 2)
assert_equal([1, 2], [copy.instance_variable_get(:@a), copy.instance_variable_get(:@b)])

copy = original.clone(freeze: false)
assert_not_predicate(copy, :frozen?)
copy.instance_variable_set(:@b, 2)
assert_equal([1, 2], [copy.instance_variable_get(:@a), copy.instance_variable_get(:@b)])
end

def test_singleton_class_of_frozen_class_is_frozen
klass = Class.new.freeze
assert_predicate(klass.singleton_class, :frozen?)
assert_raise(FrozenError) {klass.singleton_class.instance_variable_set(:@a, 1)}

copy = Class.new.freeze.clone
assert_predicate(copy, :frozen?)
assert_predicate(copy.singleton_class, :frozen?)
assert_raise(FrozenError) {copy.instance_variable_set(:@a, 1)}
end

def test_nested_class_removal
assert_normal_exit('File.__send__(:remove_const, :Stat); at_exit{File.stat(".")}; GC.start')
end
Expand Down
17 changes: 17 additions & 0 deletions test/ruby/test_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3404,6 +3404,23 @@ def test_clone_freeze
assert_not_predicate m.clone(freeze: false), :frozen?
end

def test_dup_of_frozen_module_is_not_frozen
m = Module.new
m.instance_variable_set(:@a, 1)
m.freeze

copy = m.dup
assert_not_predicate(copy, :frozen?)
copy.instance_variable_set(:@b, 2)
assert_equal([1, 2], [copy.instance_variable_get(:@a), copy.instance_variable_get(:@b)])
end

def test_singleton_class_of_frozen_module_is_frozen
m = Module.new.freeze
assert_predicate(m.singleton_class, :frozen?)
assert_raise(FrozenError) {m.singleton_class.instance_variable_set(:@a, 1)}
end

def test_module_name_in_singleton_method
s = Object.new.singleton_class
mod = s.const_set(:Foo, Module.new)
Expand Down
13 changes: 13 additions & 0 deletions test/ruby/test_regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,19 @@ def test_match_setter
assert_equal("foo", $&)
end

def test_match_setter_with_copy
[:dup, :clone].each do |copy|
[false, true].each do |freeze|
m = /a/.match("a").public_send(copy)
m.freeze if freeze
$~ = m
/b/ =~ "b"
assert_equal("a", m[0], "#{copy}, freeze: #{freeze}")
assert_equal("b", $&)
end
end
end

def test_match_without_regexp
# create a MatchData for each assertion because the internal state may change
test = proc {|&blk| "abc".sub("a", ""); blk.call($~) }
Expand Down
9 changes: 7 additions & 2 deletions variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -4693,8 +4693,13 @@ rb_fields_tbl_copy(VALUE dst, VALUE src)

VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src);
if (fields_obj) {
RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj));
RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src));
VALUE dst_fields_obj = rb_imemo_fields_clone(fields_obj);
// `dst` is a freshly allocated object, so it must not inherit `src`'s
// frozen status. Callers that need it re-freeze `dst` themselves.
shape_id_t shape_id = RBASIC_SHAPE_ID(dst_fields_obj) & ~SHAPE_ID_FL_FROZEN;
RBASIC_SET_SHAPE_ID(dst_fields_obj, shape_id);
RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, dst_fields_obj);
RBASIC_SET_SHAPE_ID(dst, shape_id);
}
}

Expand Down
55 changes: 55 additions & 0 deletions zjit/src/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7855,6 +7855,61 @@ fn test_send_caller_splat_arguments_with_block_literal() {
assert_snapshot!(assert_compiles("entry([1, 2, 3])"), @"7");
}

#[test]
fn test_send_polymorphic_caller_splat_arguments() {
set_call_threshold(3);
eval("
def test(*args) = args
def entry(args) = test(*args)
entry([1])
entry([2, 3])
");
// Unprofiled lengths use the original Send without leaving compiled code.
assert_snapshot!(assert_compiles("[entry([4]), entry([5, 6]), entry([]), entry([7, 8, 9])]"), @"[[4], [5, 6], [], [7, 8, 9]]");
}

#[test]
fn test_send_polymorphic_receiver_with_polymorphic_caller_splat() {
set_call_threshold(5);
eval("
class CallerSplatA
def target(*args) = args
end
class CallerSplatB
def target(*args) = args
end
class CallerSplatC
def target(*args) = args
end
def entry(recv, args) = recv.target(*args)
entry(CallerSplatA.new, [1])
entry(CallerSplatB.new, [2, 3])
entry(CallerSplatA.new, [4, 5])
entry(CallerSplatB.new, [6])
");
// Both a new length and an unprofiled receiver use the shared original Send.
assert_snapshot!(assert_compiles("
[entry(CallerSplatA.new, [7]), entry(CallerSplatB.new, [8, 9]),
entry(CallerSplatA.new, [1, 2, 3]), entry(CallerSplatC.new, [10, 11, 12])]
"), @"[[7], [8, 9], [1, 2, 3], [10, 11, 12]]");
}

#[test]
fn test_send_polymorphic_caller_splat_with_cfunc_receiver() {
set_call_threshold(5);
eval("
class CallerSplatFetch
def fetch(*args) = args
end
def entry(recv, args) = recv.fetch(*args)
entry(CallerSplatFetch.new, [1])
entry([10], [0])
entry(CallerSplatFetch.new, [2, 3])
entry([], [0, 20])
");
assert_snapshot!(assert_compiles("[entry(CallerSplatFetch.new, [4]), entry([10], [0]), entry([], [0, 20])]"), @"[[4], 10, 20]");
}

#[test]
fn test_send_caller_splat_length_mismatch_side_exits() {
eval("
Expand Down
Loading