From f69ad0e810e1fdc18dc12f77bbecfa49999ef3bf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Jan 2025 16:55:44 +0900 Subject: [PATCH] [Bug #21094] Update nested module names when setting temporary name --- .../core/module/set_temporary_name_spec.rb | 17 ++-- test/ruby/test_module.rb | 9 ++ variable.c | 82 ++++++++++++++++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index 12541374ae..12c1c214dd 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -108,13 +108,18 @@ ruby_version_is "3.3" do m.name.should == "fake_name_2" end - it "does not affect a name of a module nested into an anonymous module with a temporary name" do - m = Module.new - m::N = Module.new - m::N.name.should =~ /\A#::N\z/ + ruby_bug "#21094", ""..."3.5" do + it "also updates a name of a nested module" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#::N\z/ - m.set_temporary_name("foo") - m::N.name.should =~ /\A#::N\z/ + m.set_temporary_name "m" + m::N.name.should == "m::N" + + m.set_temporary_name nil + m::N.name.should == nil + end end it "keeps temporary name when assigned in an anonymous module" do diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 5d254452c6..9a21113fe0 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -3378,13 +3378,22 @@ class TestModule < Test::Unit::TestCase m::N.set_temporary_name(nil) assert_nil(m::N.name) + m::N.const_set(:O, Module.new) + m.const_set(:Recursive, m) + m::N.const_set(:Recursive, m) + m.const_set(:A, 42) + m.set_temporary_name(name = "fake_name") name.upcase! assert_equal("fake_name", m.name) assert_raise(FrozenError) {m.name.upcase!} + assert_equal("fake_name::N", m::N.name) + assert_equal("fake_name::N::O", m::N::O.name) m.set_temporary_name(nil) assert_nil m.name + assert_nil m::N.name + assert_nil m::N::O.name assert_raise_with_message(ArgumentError, "empty class/module name") do m.set_temporary_name("") diff --git a/variable.c b/variable.c index 2d022d59b1..23602687e2 100644 --- a/variable.c +++ b/variable.c @@ -166,6 +166,80 @@ is_constant_path(VALUE name) return true; } +struct sub_temporary_name_args { + VALUE names; + ID last; +}; + +static VALUE build_const_path(VALUE head, ID tail); +static void set_sub_temporary_name_foreach(VALUE mod, struct sub_temporary_name_args *args, VALUE name); + +static VALUE +set_sub_temporary_name_recursive(VALUE mod, VALUE data, int recursive) +{ + if (recursive) return Qfalse; + + struct sub_temporary_name_args *args = (void *)data; + VALUE name = 0; + if (args->names) { + name = build_const_path(rb_ary_last(0, 0, args->names), args->last); + } + set_sub_temporary_name_foreach(mod, args, name); + return Qtrue; +} + +static VALUE +set_sub_temporary_name_topmost(VALUE mod, VALUE data, int recursive) +{ + if (recursive) return Qfalse; + + struct sub_temporary_name_args *args = (void *)data; + VALUE name = args->names; + if (name) { + args->names = rb_ary_hidden_new(0); + } + set_sub_temporary_name_foreach(mod, args, name); + return Qtrue; +} + +static enum rb_id_table_iterator_result +set_sub_temporary_name_i(ID id, VALUE val, void *data) +{ + val = ((rb_const_entry_t *)val)->value; + if (rb_namespace_p(val) && !RCLASS_EXT(val)->permanent_classpath) { + VALUE arg = (VALUE)data; + struct sub_temporary_name_args *args = data; + args->last = id; + rb_exec_recursive_paired(set_sub_temporary_name_recursive, val, arg, arg); + } + return ID_TABLE_CONTINUE; +} + +static void +set_sub_temporary_name_foreach(VALUE mod, struct sub_temporary_name_args *args, VALUE name) +{ + RCLASS_SET_CLASSPATH(mod, name, FALSE); + struct rb_id_table *tbl = RCLASS_CONST_TBL(mod); + if (!tbl) return; + if (!name) { + rb_id_table_foreach(tbl, set_sub_temporary_name_i, args); + } + else { + long names_len = RARRAY_LEN(args->names); // paranoiac check? + rb_ary_push(args->names, name); + rb_id_table_foreach(tbl, set_sub_temporary_name_i, args); + rb_ary_set_len(args->names, names_len); + } +} + +static void +set_sub_temporary_name(VALUE mod, VALUE name) +{ + struct sub_temporary_name_args args = {name}; + VALUE arg = (VALUE)&args; + rb_exec_recursive_paired(set_sub_temporary_name_topmost, mod, arg, arg); +} + /* * call-seq: * mod.set_temporary_name(string) -> self @@ -224,7 +298,9 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) if (NIL_P(name)) { // Set the temporary classpath to NULL (anonymous): - RCLASS_SET_CLASSPATH(mod, 0, FALSE); + RB_VM_LOCK_ENTER(); + set_sub_temporary_name(mod, 0); + RB_VM_LOCK_LEAVE(); } else { // Ensure the name is a string: @@ -241,7 +317,9 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) name = rb_str_new_frozen(name); // Set the temporary classpath to the given name: - RCLASS_SET_CLASSPATH(mod, name, FALSE); + RB_VM_LOCK_ENTER(); + set_sub_temporary_name(mod, name); + RB_VM_LOCK_LEAVE(); } return mod;