From 5b3970f68b9108e0976b75b5d67da8c56eaa9db4 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 [Backport #21094] --- .../core/module/set_temporary_name_spec.rb | 14 ++++ test/ruby/test_module.rb | 9 ++ variable.c | 82 ++++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index f5886a3398..ae6ffb6e85 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -64,5 +64,19 @@ ruby_version_is "3.3" do m::M = m::N m::M.name.should =~ /\A#::M\z/m end + + 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 "m" + m::N.name.should == "m::N" + + m.set_temporary_name nil + m::N.name.should == nil + end + end end end diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index bd15b6a4a1..3b1cdc2cff 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -3379,13 +3379,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 5ab8067548..5bd5808a63 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;