From 966dbba8db970f13065a35d893662a981d0abae5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 30 Dec 2025 22:01:54 +0900 Subject: [PATCH 01/12] Box: skip checking the current box is the root box Because checking the current box is not a cheap process. --- class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class.c b/class.c index 8c773125e19b00..9c1bd86dc3e405 100644 --- a/class.c +++ b/class.c @@ -820,7 +820,7 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) static VALUE class_alloc(enum ruby_value_type type, VALUE klass) { - bool boxable = BOX_ROOT_P(rb_current_box()); + bool boxable = rb_box_available() && BOX_ROOT_P(rb_current_box()); return class_alloc0(type, klass, boxable); } From 19e539c9ee1701b34189fa0c1feb942adeb0e326 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 30 Dec 2025 23:00:18 +0900 Subject: [PATCH 02/12] [Bug #21814] Fix negative bignum modulo If modulo is zero, do not apply bias even if the divisor is zero. `BIGNUM_POSITIVE_P` is true even on bignum zero. --- bignum.c | 9 +++++++-- test/ruby/test_numeric.rb | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bignum.c b/bignum.c index ed53d75149085d..ee2fa1ed30a2a6 100644 --- a/bignum.c +++ b/bignum.c @@ -7070,7 +7070,7 @@ int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg) zn = mn; z = bignew(zn, 1); bary_powm_gmp(BDIGITS(z), zn, BDIGITS(x), xn, BDIGITS(y), yn, BDIGITS(m), mn); - if (nega_flg & BIGNUM_POSITIVE_P(z)) { + if (nega_flg && BIGNUM_POSITIVE_P(z) && !BIGZEROP(z)) { z = rb_big_minus(z, m); } RB_GC_GUARD(x); @@ -7098,7 +7098,7 @@ int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg) x = rb_int_modulo(x, m); } - if (nega_flg && rb_int_positive_p(tmp)) { + if (nega_flg && rb_int_positive_p(tmp) && !rb_int_zero_p(tmp)) { tmp = rb_int_minus(tmp, m); } return tmp; @@ -7210,6 +7210,11 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) rb_raise(rb_eTypeError, "Integer#pow() 2nd argument not allowed unless all arguments are integers"); } + if (rb_int_zero_p(a) && !rb_int_zero_p(b)) { + /* shortcut; 0**x => 0 except for x == 0 */ + return INT2FIX(0); + } + if (rb_int_negative_p(m)) { m = rb_int_uminus(m); nega_flg = 1; diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 3bf93ef20dc0bd..35496ac875815a 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -489,6 +489,10 @@ def test_pow assert_equal(0, 0.pow(3, 1)) assert_equal(0, 2.pow(3, 1)) assert_equal(0, -2.pow(3, 1)) + + min, max = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX") + assert_equal(0, 0.pow(2, min)) + assert_equal(0, Integer.sqrt(max+1).pow(2, min)) end end From d82fc3360d7cfa7e1e1a4dddb668b4c38808538a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 09:48:41 +0100 Subject: [PATCH 03/12] Reapply "[Feature #6012] Extend `source_location` for end position * This reverts commit 065c48cdf11a1c4cece84db44ed8624d294f8fd5. * This functionality is very valuable and has already taken 14 years to agree on the API. * Let's just document it's byte columns (in the next commit). * See https://bugs.ruby-lang.org/issues/21783#note-9 --- NEWS.md | 10 ++++ proc.c | 16 ++++-- spec/ruby/core/method/source_location_spec.rb | 16 ++++-- spec/ruby/core/proc/source_location_spec.rb | 51 ++++++++++++------- .../unboundmethod/source_location_spec.rb | 18 ++++--- test/ruby/test_lambda.rb | 12 ++--- test/ruby/test_proc.rb | 18 ++++--- 7 files changed, 93 insertions(+), 48 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8ac808c2e7b6a4..e37e4ebf5b435a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,15 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Method + + * `Method#source_location`, `Proc#source_location`, and + `UnboundMethod#source_location` now return extended location + information with 5 elements: `[path, start_line, start_column, + end_line, end_column]`. The previous 2-element format `[path, + line]` can still be obtained by calling `.take(2)` on the result. + [[Feature #6012]] + * Set * A deprecated behavior, `Set#to_set`, `Range#to_set`, and @@ -66,4 +75,5 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT +[Feature #6012]: https://bugs.ruby-lang.org/issues/6012 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 diff --git a/proc.c b/proc.c index a0fc3a7e0837f2..715f346853b8aa 100644 --- a/proc.c +++ b/proc.c @@ -1513,14 +1513,20 @@ proc_eq(VALUE self, VALUE other) static VALUE iseq_location(const rb_iseq_t *iseq) { - VALUE loc[2]; + VALUE loc[5]; + int i = 0; if (!iseq) return Qnil; rb_iseq_check(iseq); - loc[0] = rb_iseq_path(iseq); - loc[1] = RB_INT2NUM(ISEQ_BODY(iseq)->location.first_lineno); - - return rb_ary_new4(2, loc); + loc[i++] = rb_iseq_path(iseq); + const rb_code_location_t *cl = &ISEQ_BODY(iseq)->location.code_location; + loc[i++] = RB_INT2NUM(cl->beg_pos.lineno); + loc[i++] = RB_INT2NUM(cl->beg_pos.column); + loc[i++] = RB_INT2NUM(cl->end_pos.lineno); + loc[i++] = RB_INT2NUM(cl->end_pos.column); + RUBY_ASSERT_ALWAYS(i == numberof(loc)); + + return rb_ary_new_from_values(i, loc); } VALUE diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index c5b296f6e2a7b0..1b175ebabac044 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -11,23 +11,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - MethodSpecs::SourceLocation.method(:redefined).source_location.last.should == 13 + MethodSpecs::SourceLocation.method(:redefined).source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - MethodSpecs::SourceLocation.new.method(:aka).source_location.last.should == 17 + MethodSpecs::SourceLocation.new.method(:aka).source_location[1].should == 17 end it "works for methods defined with a block" do @@ -108,7 +108,13 @@ def f c = Class.new do eval('def self.m; end', nil, "foo", 100) end - c.method(:m).source_location.should == ["foo", 100] + location = c.method(:m).source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 0, 100, 15] + end end describe "for a Method generated by respond_to_missing?" do diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index a8b99287d5c380..69b4e2fd8273b6 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -17,57 +17,64 @@ end it "sets the first value to the path of the file in which the proc was defined" do - file = @proc.source_location.first + file = @proc.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @proc_new.source_location.first + file = @proc_new.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @lambda.source_location.first + file = @lambda.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the proc was defined" do - line = @proc.source_location.last + it "sets the second value to an Integer representing the line on which the proc was defined" do + line = @proc.source_location[1] line.should be_an_instance_of(Integer) line.should == 4 - line = @proc_new.source_location.last + line = @proc_new.source_location[1] line.should be_an_instance_of(Integer) line.should == 12 - line = @lambda.source_location.last + line = @lambda.source_location[1] line.should be_an_instance_of(Integer) line.should == 8 - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 15 end it "works even if the proc was created on the same line" do - proc { true }.source_location.should == [__FILE__, __LINE__] - Proc.new { true }.source_location.should == [__FILE__, __LINE__] - -> { true }.source_location.should == [__FILE__, __LINE__] + ruby_version_is(""..."4.0") do + proc { true }.source_location.should == [__FILE__, __LINE__] + Proc.new { true }.source_location.should == [__FILE__, __LINE__] + -> { true }.source_location.should == [__FILE__, __LINE__] + end + ruby_version_is("4.0") do + proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] + Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] + -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] + end end it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do - ProcSpecs::SourceLocation.my_multiline_proc.source_location.last.should == 20 - ProcSpecs::SourceLocation.my_multiline_proc_new.source_location.last.should == 34 - ProcSpecs::SourceLocation.my_multiline_lambda.source_location.last.should == 27 + ProcSpecs::SourceLocation.my_multiline_proc.source_location[1].should == 20 + ProcSpecs::SourceLocation.my_multiline_proc_new.source_location[1].should == 34 + ProcSpecs::SourceLocation.my_multiline_lambda.source_location[1].should == 27 end it "returns the location of the proc's body; not necessarily the proc itself" do - ProcSpecs::SourceLocation.my_detached_proc.source_location.last.should == 41 - ProcSpecs::SourceLocation.my_detached_proc_new.source_location.last.should == 51 - ProcSpecs::SourceLocation.my_detached_lambda.source_location.last.should == 46 + ProcSpecs::SourceLocation.my_detached_proc.source_location[1].should == 41 + ProcSpecs::SourceLocation.my_detached_proc_new.source_location[1].should == 51 + ProcSpecs::SourceLocation.my_detached_lambda.source_location[1].should == 46 end it "returns the same value for a proc-ified method as the method reports" do @@ -86,6 +93,12 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) - proc.source_location.should == ["foo", 100] + location = proc.source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 2, 100, 5] + end end end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 5c2f14362c40b4..85078ff34e8cd5 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -7,23 +7,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + it "sets the second value to an Integer representing the line on which the method was defined" do + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13 + UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17 + UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location[1].should == 17 end it "works for define_method methods" do @@ -54,6 +54,12 @@ c = Class.new do eval('def m; end', nil, "foo", 100) end - c.instance_method(:m).source_location.should == ["foo", 100] + location = c.instance_method(:m).source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 0, 100, 10] + end end end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 7738034240497d..3cbb54306c8afa 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -276,27 +276,27 @@ def test_break end def test_do_lambda_source_location - exp_lineno = __LINE__ + 3 + exp = [__LINE__ + 1, 12, __LINE__ + 5, 7] lmd = ->(x, y, z) do # end - file, lineno = lmd.source_location + file, *loc = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + assert_equal(exp, loc) end def test_brace_lambda_source_location - exp_lineno = __LINE__ + 3 + exp = [__LINE__ + 1, 12, __LINE__ + 5, 5] lmd = ->(x, y, z) { # } - file, lineno = lmd.source_location + file, *loc = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + assert_equal(exp, loc) end def test_not_orphan_return diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index f74342322f5b78..959ea87f25d667 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -513,7 +513,7 @@ def test_binding_source_location file, lineno = method(:source_location_test).to_proc.binding.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + assert_equal(@@line_of_source_location_test[0], lineno, 'Bug #2427') end def test_binding_error_unless_ruby_frame @@ -1499,15 +1499,19 @@ def test_to_s assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name) end - @@line_of_source_location_test = __LINE__ + 1 + @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5] def source_location_test a=1, b=2 end def test_source_location - file, lineno = method(:source_location_test).source_location + file, *loc = method(:source_location_test).source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') + + file, *loc = self.class.instance_method(:source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') end @@line_of_attr_reader_source_location_test = __LINE__ + 3 @@ -1540,13 +1544,13 @@ def block_source_location_test(*args, &block) end def test_block_source_location - exp_lineno = __LINE__ + 3 - file, lineno = block_source_location_test(1, + exp_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49] + file, *loc = block_source_location_test(1, 2, 3) do end assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno) + assert_equal(exp_loc, loc) end def test_splat_without_respond_to From cd66d15858a06406d1de854f3e9690d3557a9864 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 09:52:31 +0100 Subject: [PATCH 04/12] [Bug #21783] Fix documentation of {Method,UnboundMethod,Proc}#source_location --- proc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proc.c b/proc.c index 715f346853b8aa..4fa48196caccc2 100644 --- a/proc.c +++ b/proc.c @@ -1543,9 +1543,9 @@ rb_iseq_location(const rb_iseq_t *iseq) * The returned Array contains: * (1) the Ruby source filename * (2) the line number where the definition starts - * (3) the column number where the definition starts + * (3) the position where the definition starts, in number of bytes from the start of the line * (4) the line number where the definition ends - * (5) the column number where the definitions ends + * (5) the position where the definitions ends, in number of bytes from the start of the line * * This method will return +nil+ if the Proc was not defined in Ruby (i.e. native). */ @@ -3203,9 +3203,9 @@ rb_method_entry_location(const rb_method_entry_t *me) * The returned Array contains: * (1) the Ruby source filename * (2) the line number where the definition starts - * (3) the column number where the definition starts + * (3) the position where the definition starts, in number of bytes from the start of the line * (4) the line number where the definition ends - * (5) the column number where the definitions ends + * (5) the position where the definitions ends, in number of bytes from the start of the line * * This method will return +nil+ if the method was not defined in Ruby (i.e. native). */ From c970d2941d56a862bb9bb3b808cb588c2982f436 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 15 Dec 2025 21:46:17 +0100 Subject: [PATCH 05/12] [Bug #21784] Fix the Proc#source_location start_column for stabby lambdas * Consistent with plain `blocks` and `for` blocks and methods where the source_location covers their entire definition. * Matches the documentation which mentions "where the definition starts/ends". * Partially reverts d357d50f0a74409446f4cccec78593373f5adf2f which was a workaround to be compatible with parse.y. --- parse.y | 2 +- prism_compile.c | 7 ------- spec/ruby/core/proc/source_location_spec.rb | 4 ++-- test/ruby/test_ast.rb | 2 +- test/ruby/test_lambda.rb | 4 ++-- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/parse.y b/parse.y index 067f45e6d6044a..03dd1c6f926067 100644 --- a/parse.y +++ b/parse.y @@ -5149,7 +5149,7 @@ lambda : tLAMBDA[lpar] CMDARG_POP(); $args = args_with_numbered(p, $args, max_numparam, it_id); { - YYLTYPE loc = code_loc_gen(&@args, &@body); + YYLTYPE loc = code_loc_gen(&@lpar, &@body); $$ = NEW_LAMBDA($args, $body->node, &loc, &@lpar, &$body->opening_loc, &$body->closing_loc); nd_set_line(RNODE_LAMBDA($$)->nd_body, @body.end_pos.lineno); nd_set_line($$, @args.end_pos.lineno); diff --git a/prism_compile.c b/prism_compile.c index 77b940ce6c6008..36a0b6ce50a77b 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3284,13 +3284,6 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ scope->parameters = cast->parameters; scope->body = cast->body; scope->locals = cast->locals; - - if (cast->parameters != NULL) { - scope->base.location.start = cast->parameters->location.start; - } - else { - scope->base.location.start = cast->operator_loc.end; - } break; } case PM_MODULE_NODE: { diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 69b4e2fd8273b6..18f1ca274c5488 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -61,7 +61,7 @@ ruby_version_is("4.0") do proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] - -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] + -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] end end @@ -98,7 +98,7 @@ location.should == ["foo", 100] end ruby_version_is("4.0") do - location.should == ["foo", 100, 2, 100, 5] + location.should == ["foo", 100, 0, 100, 5] end end end diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index c7a946dec868bb..22ccbfb604d0e4 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1230,7 +1230,7 @@ def test_error_tolerant_end_is_short_for_do_LAMBDA args: nil body: (LAMBDA@1:0-2:3 - (SCOPE@1:2-2:3 + (SCOPE@1:0-2:3 tbl: [] args: (ARGS@1:2-1:2 diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 3cbb54306c8afa..6a0dd9915e32a6 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -276,7 +276,7 @@ def test_break end def test_do_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 7] + exp = [__LINE__ + 1, 10, __LINE__ + 5, 7] lmd = ->(x, y, z) do @@ -288,7 +288,7 @@ def test_do_lambda_source_location end def test_brace_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 5] + exp = [__LINE__ + 1, 10, __LINE__ + 5, 5] lmd = ->(x, y, z) { From a7fec4d6619384b0b0277e751a56f7b91e5d8d5b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 30 Dec 2025 12:47:01 +0100 Subject: [PATCH 06/12] Update version guards in ruby/spec --- spec/ruby/core/method/source_location_spec.rb | 4 ++-- spec/ruby/core/proc/source_location_spec.rb | 8 ++++---- spec/ruby/core/unboundmethod/source_location_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index 1b175ebabac044..87413a2ab6780d 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -109,10 +109,10 @@ def f eval('def self.m; end', nil, "foo", 100) end location = c.method(:m).source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 15] end end diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 18f1ca274c5488..fd33f21a26e8b3 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -53,12 +53,12 @@ end it "works even if the proc was created on the same line" do - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do proc { true }.source_location.should == [__FILE__, __LINE__] Proc.new { true }.source_location.should == [__FILE__, __LINE__] -> { true }.source_location.should == [__FILE__, __LINE__] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] @@ -94,10 +94,10 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) location = proc.source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 5] end end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 85078ff34e8cd5..9cc219801738d7 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -55,10 +55,10 @@ eval('def m; end', nil, "foo", 100) end location = c.instance_method(:m).source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 10] end end From c05e10605e46106397fb4af4ea0f322c4d6d68ea Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 30 Dec 2025 15:30:31 +0100 Subject: [PATCH 07/12] Exclude rbs tests which need updates for {Method,UnboundMethod,Proc}#source_location * See https://github.com/ruby/ruby/pull/15580 --- tool/rbs_skip_tests | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index d5139705b3c4d3..e03eecbfedcc4d 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -45,3 +45,6 @@ test_linear_time?(RegexpSingletonTest) test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby +test_source_location(MethodInstanceTest) +test_source_location(ProcInstanceTest) +test_source_location(UnboundMethodInstanceTest) From f2833e358cf58c3e69038cab80e87e40b7694541 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 09:46:42 -0500 Subject: [PATCH 08/12] Fix generational GC for weak references Fixes issue pointed out in https://bugs.ruby-lang.org/issues/21084#note-7. The following script crashes: wmap = ObjectSpace::WeakMap.new GC.disable # only manual GCs GC.start GC.start retain = [] 50.times do k = Object.new wmap[k] = true retain << k end GC.start # wmap promoted, other objects still young retain.clear GC.start(full_mark: false) wmap.keys.each(&:itself) # call method on keys to cause crash --- gc/default/default.c | 10 +++++++++- test/ruby/test_weakmap.rb | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index c33570f920f647..f6628fe9fd7104 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -5329,7 +5329,13 @@ rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj) { rb_objspace_t *objspace = objspace_ptr; - return RVALUE_MARKED(objspace, obj); + bool marked = RVALUE_MARKED(objspace, obj); + + if (marked) { + rgengc_check_relation(objspace, obj); + } + + return marked; } static void @@ -5337,7 +5343,9 @@ gc_update_weak_references(rb_objspace_t *objspace) { VALUE *obj_ptr; rb_darray_foreach(objspace->weak_references, i, obj_ptr) { + gc_mark_set_parent(objspace, *obj_ptr); rb_gc_handle_weak_references(*obj_ptr); + gc_mark_set_parent_invalid(objspace); } size_t capa = rb_darray_capa(objspace->weak_references); diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index a2904776bc905f..1050c74b3dc4cd 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -265,4 +265,27 @@ def test_use_after_free_bug_20688 10_000.times { weakmap[Object.new] = Object.new } RUBY end + + def test_generational_gc + EnvUtil.without_gc do + wmap = ObjectSpace::WeakMap.new + + (GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] - 1).times { GC.start } + + retain = [] + 50.times do + k = Object.new + wmap[k] = true + retain << k + end + + GC.start # WeakMap promoted, other objects still young + + retain.clear + + GC.start(full_mark: false) + + wmap.keys.each(&:itself) # call method on keys to cause crash + end + end end From b7bf8c20b045c4dce5daa894d95d48c0983b9481 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 09:48:20 -0500 Subject: [PATCH 09/12] Add RVALUE_OLD_AGE to GC::INTERNAL_CONSTANTS for MMTk --- gc/mmtk/mmtk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index b507e337b8a132..d69a5cda2df2ae 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -550,6 +550,8 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); + // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0)); OBJ_FREEZE(gc_constants); rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants); From 3086d58263b36d364a1187cd7678e6020806e6f3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 01:34:43 +0900 Subject: [PATCH 10/12] Run also test-tool on mingw --- .github/workflows/mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 8c3d16fbc9c120..5c639ad48bdf38 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -207,7 +207,7 @@ jobs: - name: test timeout-minutes: 30 - run: make test + run: make test test-tool env: GNUMAKEFLAGS: '' RUBY_TESTOPTS: '-v --tty=no' From d40e056cc8cb5c21fb3f7c25bf9463548dd14873 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 02:24:21 +0900 Subject: [PATCH 11/12] Skip the hang-up test on Windows --- test/ruby/test_thread.rb | 2 +- tool/test/testunit/test_assertion.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 3796e12f083feb..8edebfb5c219da 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1480,7 +1480,7 @@ def test_thread_native_thread_id_across_fork_on_linux end def test_thread_interrupt_for_killed_thread - pend "hang-up" if RUBY_PLATFORM.include?("mingw") + pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM opts = { timeout: 5, timeout_error: nil } diff --git a/tool/test/testunit/test_assertion.rb b/tool/test/testunit/test_assertion.rb index b0c2267b31ade3..7b1f28a8698b15 100644 --- a/tool/test/testunit/test_assertion.rb +++ b/tool/test/testunit/test_assertion.rb @@ -8,6 +8,8 @@ def test_wrong_assertion end def test_timeout_separately + pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM + assert_raise(Timeout::Error) do assert_separately([], <<~"end;", timeout: 0.1) sleep From 9d37155cfc74258374134cbcc64f796b01f807d5 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 13:01:06 -0500 Subject: [PATCH 12/12] [ruby/mmtk] Use MMTK_HEAP_COUNT for SIZE_POOL_COUNT https://github.com/ruby/mmtk/commit/290a2aec4e --- gc/mmtk/mmtk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index d69a5cda2df2ae..b532d3a774cca1 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -549,7 +549,7 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools - rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(MMTK_HEAP_COUNT)); // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0)); OBJ_FREEZE(gc_constants);