From dd4e7801f3f2b7e258016b8e22c6d8870516166b Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Sat, 30 Aug 2025 21:26:16 +0900 Subject: [PATCH 1/2] Add NODE SCLASS locations Add locations to struct `RNode_SCLASS`. memo: ``` @ ProgramNode (location: (1,0)-(1,18)) +-- locals: [] +-- statements: @ StatementsNode (location: (1,0)-(1,18)) +-- body: (length: 1) +-- @ SingletonClassNode (location: (1,0)-(1,18)) +-- locals: [] +-- class_keyword_loc: (1,0)-(1,5) = "class" +-- operator_loc: (1,6)-(1,8) = "<<" +-- expression: | @ SelfNode (location: (1,9)-(1,13)) +-- body: nil +-- end_keyword_loc: (1,15)-(1,18) = "end" ``` --- ast.c | 8 ++++++++ node_dump.c | 5 ++++- parse.y | 11 +++++++---- rubyparser.h | 3 +++ test/ruby/test_ast.rb | 8 ++++++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ast.c b/ast.c index 04b954385477e1..449b21aa2d1023 100644 --- a/ast.c +++ b/ast.c @@ -918,6 +918,14 @@ node_locations(VALUE ast_value, const NODE *node) return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), location_new(&RNODE_RETURN(node)->keyword_loc)); + + case NODE_SCLASS: + return rb_ary_new_from_args(4, + location_new(nd_code_loc(node)), + location_new(&RNODE_SCLASS(node)->class_keyword_loc), + location_new(&RNODE_SCLASS(node)->operator_loc), + location_new(&RNODE_SCLASS(node)->end_keyword_loc)); + case NODE_SPLAT: return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), diff --git a/node_dump.c b/node_dump.c index 18ac3d7b35b4e5..82a7d78c2842d5 100644 --- a/node_dump.c +++ b/node_dump.c @@ -1023,8 +1023,11 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: class << [nd_recv]; [nd_body]; end"); ANN("example: class << obj; ..; end"); F_NODE(nd_recv, RNODE_SCLASS, "receiver"); - LAST_NODE; F_NODE(nd_body, RNODE_SCLASS, "singleton class definition"); + F_LOC(class_keyword_loc, RNODE_SCLASS); + F_LOC(operator_loc, RNODE_SCLASS); + LAST_NODE; + F_LOC(end_keyword_loc, RNODE_SCLASS); return; case NODE_COLON2: diff --git a/parse.y b/parse.y index 765b4bdfd0890d..a6c5e2e5b94f3f 100644 --- a/parse.y +++ b/parse.y @@ -1146,7 +1146,7 @@ static rb_node_valias_t *rb_node_valias_new(struct parser_params *p, ID nd_alias static rb_node_undef_t *rb_node_undef_new(struct parser_params *p, NODE *nd_undef, const YYLTYPE *loc); static rb_node_class_t *rb_node_class_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, NODE *nd_super, const YYLTYPE *loc, const YYLTYPE *class_keyword_loc, const YYLTYPE *inheritance_operator_loc, const YYLTYPE *end_keyword_loc); static rb_node_module_t *rb_node_module_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *module_keyword_loc, const YYLTYPE *end_keyword_loc); -static rb_node_sclass_t *rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc); +static rb_node_sclass_t *rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *class_keyword_loc, const YYLTYPE *operator_loc, const YYLTYPE *end_keyword_loc); static rb_node_colon2_t *rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); static rb_node_colon3_t *rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); static rb_node_dot2_t *rb_node_dot2_new(struct parser_params *p, NODE *nd_beg, NODE *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc); @@ -1254,7 +1254,7 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_UNDEF(i,loc) (NODE *)rb_node_undef_new(p,i,loc) #define NEW_CLASS(n,b,s,loc,ck_loc,io_loc,ek_loc) (NODE *)rb_node_class_new(p,n,b,s,loc,ck_loc,io_loc,ek_loc) #define NEW_MODULE(n,b,loc,mk_loc,ek_loc) (NODE *)rb_node_module_new(p,n,b,loc,mk_loc,ek_loc) -#define NEW_SCLASS(r,b,loc) (NODE *)rb_node_sclass_new(p,r,b,loc) +#define NEW_SCLASS(r,b,loc,ck_loc,op_loc,ek_loc) (NODE *)rb_node_sclass_new(p,r,b,loc,ck_loc,op_loc,ek_loc) #define NEW_COLON2(c,i,loc,d_loc,n_loc) (NODE *)rb_node_colon2_new(p,c,i,loc,d_loc,n_loc) #define NEW_COLON3(i,loc,d_loc,n_loc) (NODE *)rb_node_colon3_new(p,i,loc,d_loc,n_loc) #define NEW_DOT2(b,e,loc,op_loc) (NODE *)rb_node_dot2_new(p,b,e,loc,op_loc) @@ -4605,7 +4605,7 @@ primary : inline_primary bodystmt k_end { - $$ = NEW_SCLASS($expr_value, $bodystmt, &@$); + $$ = NEW_SCLASS($expr_value, $bodystmt, &@$, &@k_class, &@tLSHFT, &@k_end); nd_set_line(RNODE_SCLASS($$)->nd_body, @k_end.end_pos.lineno); set_line_body($bodystmt, nd_line($expr_value)); fixpos($$, $expr_value); @@ -11431,7 +11431,7 @@ rb_node_class_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, NODE * } static rb_node_sclass_t * -rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc) +rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *class_keyword_loc, const YYLTYPE *operator_loc, const YYLTYPE *end_keyword_loc) { /* Keep the order of node creation */ NODE *scope = NEW_SCOPE(0, nd_body, NULL, loc); @@ -11439,6 +11439,9 @@ rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const RNODE_SCOPE(scope)->nd_parent = &n->node; n->nd_recv = nd_recv; n->nd_body = scope; + n->class_keyword_loc = *class_keyword_loc; + n->operator_loc = *operator_loc; + n->end_keyword_loc = *end_keyword_loc; return n; } diff --git a/rubyparser.h b/rubyparser.h index 4ab2480b7de770..ee4fe2e44da03d 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -914,6 +914,9 @@ typedef struct RNode_SCLASS { struct RNode *nd_recv; struct RNode *nd_body; + rb_code_location_t class_keyword_loc; + rb_code_location_t operator_loc; + rb_code_location_t end_keyword_loc; } rb_node_sclass_t; typedef struct RNode_COLON2 { diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index ef078c35759a1e..55e5915d821546 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1636,6 +1636,14 @@ def test_return_locations assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]]) end + def test_sclass_locations + node = ast_parse("class << self; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 5], [1, 6, 1, 8], [1, 15, 1, 18]]) + + node = ast_parse("class << obj; foo; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 22], [1, 0, 1, 5], [1, 6, 1, 8], [1, 19, 1, 22]]) + end + def test_splat_locations node = ast_parse("a = *1") assert_locations(node.children[-1].children[1].locations, [[1, 4, 1, 6], [1, 4, 1, 5]]) From 5c7dfe85a1dc49334e2828791f0ade42eee662db Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 31 Aug 2025 03:24:25 +0900 Subject: [PATCH 2/2] Initialize class dup/clone before calling initialize_dup/initialize_clone Previously, you could override the class initialize_dup/initialize_clone method and the class hierarchy would not be set correctly inside the method before calling super. This removes Module#initialize_copy, and instead makes Object#dup/clone call the underlying C function (rb_mod_init_copy) before calling the appropriate initialize_dup/initialize_clone method. This results in the following fixes: * The appropriate initialize_dup method is called (dup on a class will respect superclass initialize_dup). * Inside class initialize_dup/initialize_clone/initialize_copy, class ancestor hierarchy is correct. * Calling singleton_class inside initialize_dup no longer raises a TypeError later in dup. * Calling singleton_class.ancestors inside initialize_dup no longer results in missing ancestors. Fixes [Bug #21538] --- object.c | 3 +-- test/ruby/test_class.rb | 40 ++++++++++++++++++++++++++++++++++++++++ test/ruby/test_module.rb | 8 -------- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/object.c b/object.c index 6aa9c27f9dd876..56afc7e99b8ca2 100644 --- a/object.c +++ b/object.c @@ -409,7 +409,7 @@ init_copy(VALUE dest, VALUE obj) break; case T_CLASS: case T_MODULE: - // noop: handled in class.c: rb_mod_init_copy + rb_mod_init_copy(dest, obj); break; case T_OBJECT: rb_obj_copy_ivar(dest, obj); @@ -4571,7 +4571,6 @@ InitVM_Object(void) rb_define_method(rb_cModule, "<=", rb_class_inherited_p, 1); rb_define_method(rb_cModule, ">", rb_mod_gt, 1); rb_define_method(rb_cModule, ">=", rb_mod_ge, 1); - rb_define_method(rb_cModule, "initialize_copy", rb_mod_init_copy, 1); /* in class.c */ rb_define_method(rb_cModule, "to_s", rb_mod_to_s, 0); rb_define_alias(rb_cModule, "inspect", "to_s"); rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */ diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 74541bba3f7d87..f40817e7a1ef54 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -259,6 +259,46 @@ def test_initialize_copy assert_raise(TypeError) { BasicObject.dup } end + def test_class_hierarchy_inside_initialize_dup_bug_21538 + ancestors = sc_ancestors = nil + b = Class.new + b.define_singleton_method(:initialize_dup) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + a = Class.new(b) + + c = a.dup + + expected_ancestors = [c, b, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + + def test_class_hierarchy_inside_initialize_clone_bug_21538 + ancestors = sc_ancestors = nil + a = Class.new + a.define_singleton_method(:initialize_clone) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + c = a.clone + + expected_ancestors = [c, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + def test_singleton_class assert_raise(TypeError) { 1.extend(Module.new) } assert_raise(TypeError) { 1.0.extend(Module.new) } diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 35476967d78944..5805ad7d9b8860 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -418,9 +418,6 @@ def test_initialize_copy instance = klass.new assert_equal(:first, instance.foo) new_mod = Module.new { define_method(:foo) { :second } } - assert_raise(TypeError) do - mod.send(:initialize_copy, new_mod) - end 4.times { GC.start } assert_equal(:first, instance.foo) # [BUG] unreachable end @@ -435,11 +432,6 @@ def x assert_equal([:x], m.instance_methods) assert_equal([:@x], m.instance_variables) assert_equal([:X], m.constants) - assert_raise(TypeError) do - m.module_eval do - initialize_copy(Module.new) - end - end m = Class.new(Module) do def initialize_copy(other)