From d0395bd0ead968e194e268f8c5f9db59d8831c02 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 12 Jul 2025 11:51:31 +0900 Subject: [PATCH 01/10] [ruby/uri] Clear user info totally at setting any of authority info Fix CVE-2025-27221. https://hackerone.com/reports/3221142 https://github.com/ruby/uri/commit/5cec76b9e8 --- lib/uri/generic.rb | 10 ++++++---- test/uri/test_generic.rb | 15 ++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index d811c5b9440bf1..abdf5b437729c9 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -186,18 +186,18 @@ def initialize(scheme, if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil) user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil) # See also URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -639,6 +639,7 @@ def set_host(v) def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -729,6 +730,7 @@ def set_port(v) def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index c725116e964e50..94eea71b511161 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -283,6 +283,9 @@ def test_merge_authority u0 = URI.parse('http://new.example.org/path') u1 = u.merge('//new.example.org/path') assert_equal(u0, u1) + u0 = URI.parse('http://other@example.net') + u1 = u.merge('//other@example.net') + assert_equal(u0, u1) end def test_route @@ -748,17 +751,18 @@ def test_join def test_set_component uri = URI.parse('http://foo:bar@baz') assert_equal('oof', uri.user = 'oof') - assert_equal('http://oof:bar@baz', uri.to_s) + assert_equal('http://oof@baz', uri.to_s) assert_equal('rab', uri.password = 'rab') assert_equal('http://oof:rab@baz', uri.to_s) assert_equal('foo', uri.userinfo = 'foo') - assert_equal('http://foo:rab@baz', uri.to_s) + assert_equal('http://foo@baz', uri.to_s) assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar']) assert_equal('http://foo:bar@baz', uri.to_s) assert_equal(['foo'], uri.userinfo = ['foo']) - assert_equal('http://foo:bar@baz', uri.to_s) + assert_equal('http://foo@baz', uri.to_s) assert_equal('zab', uri.host = 'zab') - assert_equal('http://foo:bar@zab', uri.to_s) + assert_equal('http://zab', uri.to_s) + uri.userinfo = ['foo', 'bar'] uri.port = "" assert_nil(uri.port) uri.port = "80" @@ -768,7 +772,8 @@ def test_set_component uri.port = " 080 " assert_equal(80, uri.port) assert_equal(8080, uri.port = 8080) - assert_equal('http://foo:bar@zab:8080', uri.to_s) + assert_equal('http://zab:8080', uri.to_s) + uri = URI.parse('http://foo:bar@zab:8080') assert_equal('/', uri.path = '/') assert_equal('http://foo:bar@zab:8080/', uri.to_s) assert_equal('a=1', uri.query = 'a=1') From eccc54b4fa437f896cde1bdee7f855b6e541cb82 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 26 Jun 2025 01:21:50 +0900 Subject: [PATCH 02/10] [ruby/uri] Add authority accessor https://github.com/ruby/uri/commit/6c6449e15f --- lib/uri/generic.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index abdf5b437729c9..634da49fe98f90 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -574,6 +574,12 @@ def password @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after URI decoding. def decoded_user URI.decode_uri_component(@user) if @user @@ -615,6 +621,13 @@ def set_host(v) end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -1123,7 +1136,7 @@ def merge(oth) base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1136,9 +1149,7 @@ def merge(oth) # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) From 6a58c4fbb653ad05e2da2f85d79797f6d5c87251 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 7 Oct 2025 09:48:35 +0900 Subject: [PATCH 03/10] [ruby/uri] Bump up to v1.0.4 https://github.com/ruby/uri/commit/e5074739c3 --- lib/uri/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/version.rb b/lib/uri/version.rb index b6a8ce15435601..60ada985f91ca8 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '010003'.freeze + VERSION_CODE = '010004'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end From def07dca82736bddb123c9adb02b8e1df02e3c5c Mon Sep 17 00:00:00 2001 From: git Date: Tue, 7 Oct 2025 01:14:41 +0000 Subject: [PATCH 04/10] Update default gems list at 6a58c4fbb653ad05e2da2f85d79797 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index dafce164998b18..6c9de939704722 100644 --- a/NEWS.md +++ b/NEWS.md @@ -197,7 +197,7 @@ The following default gems are updated. * resolv 0.6.2 * stringio 3.1.8.dev * strscan 3.1.6.dev -* uri 1.0.3 +* uri 1.0.4 * weakref 0.1.4 The following bundled gems are added. From c6a119c751305ce75133dad3d14be69f859cd6bb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 10:30:16 +0900 Subject: [PATCH 05/10] Update rubyspec as of CVE-2025-27221 --- spec/ruby/library/uri/set_component_spec.rb | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/spec/ruby/library/uri/set_component_spec.rb b/spec/ruby/library/uri/set_component_spec.rb index 642a5d6fcf0543..1d4165c5351b13 100644 --- a/spec/ruby/library/uri/set_component_spec.rb +++ b/spec/ruby/library/uri/set_component_spec.rb @@ -6,25 +6,27 @@ it "conforms to the MatzRuby tests" do uri = URI.parse('http://foo:bar@baz') (uri.user = 'oof').should == 'oof' - uri.to_s.should == 'http://oof:bar@baz' - (uri.password = 'rab').should == 'rab' - uri.to_s.should == 'http://oof:rab@baz' - (uri.userinfo = 'foo').should == 'foo' - uri.to_s.should == 'http://foo:rab@baz' - (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar'] - uri.to_s.should == 'http://foo:bar@baz' - (uri.userinfo = ['foo']).should == ['foo'] - uri.to_s.should == 'http://foo:bar@baz' - (uri.host = 'zab').should == 'zab' - uri.to_s.should == 'http://foo:bar@zab' - (uri.port = 8080).should == 8080 - uri.to_s.should == 'http://foo:bar@zab:8080' - (uri.path = '/').should == '/' - uri.to_s.should == 'http://foo:bar@zab:8080/' - (uri.query = 'a=1').should == 'a=1' - uri.to_s.should == 'http://foo:bar@zab:8080/?a=1' - (uri.fragment = 'b123').should == 'b123' - uri.to_s.should == 'http://foo:bar@zab:8080/?a=1#b123' + version_is(URI::VERSION, "1.0.4") do + uri.to_s.should == 'http://oof@baz' + (uri.password = 'rab').should == 'rab' + uri.to_s.should == 'http://oof:rab@baz' + (uri.userinfo = 'foo').should == 'foo' + uri.to_s.should == 'http://foo@baz' + (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar'] + uri.to_s.should == 'http://foo:bar@baz' + (uri.userinfo = ['foo']).should == ['foo'] + uri.to_s.should == 'http://foo@baz' + (uri.host = 'zab').should == 'zab' + uri.to_s.should == 'http://zab' + (uri.port = 8080).should == 8080 + uri.to_s.should == 'http://zab:8080' + (uri.path = '/').should == '/' + uri.to_s.should == 'http://zab:8080/' + (uri.query = 'a=1').should == 'a=1' + uri.to_s.should == 'http://zab:8080/?a=1' + (uri.fragment = 'b123').should == 'b123' + uri.to_s.should == 'http://zab:8080/?a=1#b123' + end uri = URI.parse('http://example.com') -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError) From 03f714de626cad7c8c45a60bffad2ce66228b524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 6 Oct 2025 16:50:01 +0200 Subject: [PATCH 06/10] Remove warning when generating $(arch)-fake.rb This happens if BASERUBY is Ruby 3.4. $ rm -f *-fake.rb && make test-precheck RUBYOPT=-w >/dev/null build/arm64-darwin24-fake.rb:28: warning: ::Ruby is reserved for Ruby 3.5 --- template/fake.rb.in | 1 + 1 file changed, 1 insertion(+) diff --git a/template/fake.rb.in b/template/fake.rb.in index a02582a9dc012f..fed640aee7e02b 100644 --- a/template/fake.rb.in +++ b/template/fake.rb.in @@ -49,6 +49,7 @@ class Object else%><%=v.inspect%><%end%> % } end +v=$VERBOSE;$VERBOSE=nil;module Ruby; end;$VERBOSE=v module Ruby constants.each {|n| remove_const n} % arg['versions'].each {|n, v| From 4cdf5f493362df1261ecc87f9644f2b0c2f2a409 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 12:44:13 +0900 Subject: [PATCH 07/10] Verify that RubyGems is enabled by default --- test/ruby/test_rubyoptions.rb | 4 +++- tool/lib/core_assertions.rb | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 4ee6633c2b6560..8126cb3c268b37 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -210,6 +210,8 @@ def test_enable assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [], /unknown argument for --enable: 'foobarbazqux'/) assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil) end def test_disable @@ -219,7 +221,7 @@ def test_disable assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [], /unknown argument for --disable: 'foobarbazqux'/) assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/) - assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], []) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false) assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], []) assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], []) end diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 47cc6574c878d1..934ab080372d67 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -97,9 +97,11 @@ def assert_file end def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, - success: nil, failed: nil, **opt) + success: nil, failed: nil, gems: false, **opt) args = Array(args).dup - args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + unless gems.nil? + args.insert((Hash === args[0] ? 1 : 0), "--#{gems ? 'enable' : 'disable'}=gems") + end stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) desc = failed[status, message, stderr] if failed desc ||= FailDesc[status, message, stderr] From 0f059792997dc9cb3007064d1e21eebe5872edc0 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 6 Oct 2025 11:35:13 +0900 Subject: [PATCH 08/10] ns_id of main is already initialized in Namespace.new --- namespace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/namespace.c b/namespace.c index b48914ac0ab004..0c437be7711989 100644 --- a/namespace.c +++ b/namespace.c @@ -755,7 +755,6 @@ rb_initialize_main_namespace(void) VM_ASSERT(NAMESPACE_OBJ_P(main_ns)); ns = rb_get_namespace_t(main_ns); ns->ns_object = main_ns; - ns->ns_id = namespace_generate_id(); ns->is_user = true; ns->is_optional = false; From 52c6b32f806b1b812069235fde48e68167eaa0d1 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 7 Oct 2025 12:38:27 +0900 Subject: [PATCH 09/10] Initialize the main namespace after loading builtin libraries * For having the common set of loaded libraries between root and main namespaces * To have the consistent $LOADED_FEATURES in the main namespace --- namespace.c | 3 +++ ruby.c | 5 +++- test/ruby/test_namespace.rb | 49 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index 0c437be7711989..577a1759d25573 100644 --- a/namespace.c +++ b/namespace.c @@ -761,6 +761,9 @@ rb_initialize_main_namespace(void) rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_ns); vm->main_namespace = main_namespace = ns; + + // create the writable classext of ::Object explicitly to finalize the set of visible top-level constants + RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns); } static VALUE diff --git a/ruby.c b/ruby.c index 8c1fb5719bc02e..05a9fd4191d8bb 100644 --- a/ruby.c +++ b/ruby.c @@ -1826,10 +1826,13 @@ ruby_opt_init(ruby_cmdline_options_t *opt) GET_VM()->running = 1; memset(ruby_vm_redefined_flag, 0, sizeof(ruby_vm_redefined_flag)); + ruby_init_prelude(); + + /* Initialize the main namespace after loading libraries (including rubygems) + * to enable those in both root and main */ if (rb_namespace_available()) rb_initialize_main_namespace(); rb_namespace_init_done(); - ruby_init_prelude(); // Initialize JITs after ruby_init_prelude() because JITing prelude is typically not optimal. #if USE_YJIT diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index cd593068675de6..af308ab15c25e3 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -3,6 +3,10 @@ require 'test/unit' class TestNamespace < Test::Unit::TestCase + EXPERIMENTAL_WARNINGS = [ + "warning: Namespace is experimental, and the behavior may change in the future!", + "See doc/namespace.md for known issues, etc." + ].join("\n") ENV_ENABLE_NAMESPACE = {'RUBY_NAMESPACE' => '1'} def setup @@ -534,6 +538,51 @@ def test_load_path_and_loaded_features assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb')) end + def test_prelude_gems_and_loaded_features + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + # No additional warnings except for experimental warnings + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal error.size, 2 + + assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_prelude_gems_and_loaded_features_with_disable_gems + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal error.size, 2 + + refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + def test_eval_basic pend unless Namespace.enabled? From 2548c476a379cebe85166f20e264ae68c2a68dc4 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 7 Oct 2025 12:42:03 +0900 Subject: [PATCH 10/10] Add namespace debug methods and assertions --- internal/class.h | 14 ++++++++++---- namespace.c | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/internal/class.h b/internal/class.h index 29b3526cf55cfd..f5c5142b452d78 100644 --- a/internal/class.h +++ b/internal/class.h @@ -336,10 +336,14 @@ RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t return first_set; } +#define VM_ASSERT_NAMESPACEABLE_TYPE(klass) \ + VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS), "%s is not namespaceable type", rb_type_str(BUILTIN_TYPE(klass))) + static inline bool RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); // if the lookup table exists, then it means the prime classext is NOT directly readable. return !FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE) || RCLASS_CLASSEXT_TBL(klass) == NULL; } @@ -347,15 +351,16 @@ RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) static inline bool RCLASS_PRIME_CLASSEXT_WRITABLE_P(VALUE klass) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); return FL_TEST(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } static inline void RCLASS_SET_PRIME_CLASSEXT_WRITABLE(VALUE klass, bool writable) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); - + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); if (writable) { FL_SET(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } @@ -429,6 +434,7 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) ext = rb_class_duplicate_classext(RCLASS_EXT_PRIME(obj), obj, ns); first_set = RCLASS_SET_NAMESPACE_CLASSEXT(obj, ns, ext); if (first_set) { + // TODO: are there any case that a class/module become non-writable after its birthtime? RCLASS_SET_PRIME_CLASSEXT_WRITABLE(obj, false); } } diff --git a/namespace.c b/namespace.c index 577a1759d25573..32249d00bc161b 100644 --- a/namespace.c +++ b/namespace.c @@ -845,11 +845,15 @@ rb_namespace_s_main(VALUE recv) static const char * classname(VALUE klass) { - VALUE p = RCLASS_CLASSPATH(klass); + VALUE p; + if (!klass) { + return "Qfalse"; + } + p = RCLASSEXT_CLASSPATH(RCLASS_EXT_PRIME(klass)); if (RTEST(p)) return RSTRING_PTR(p); if (RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)) - return RSTRING_PTR(rb_inspect(klass)); + return "AnyClassValue"; return "NonClassValue"; } @@ -973,6 +977,27 @@ rb_f_dump_classext(VALUE recv, VALUE klass) return res; } +static VALUE +rb_namespace_root_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_ROOT_P(ns)); +} + +static VALUE +rb_namespace_main_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_MAIN_P(ns)); +} + +static VALUE +rb_namespace_user_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_USER_P(ns)); +} + #endif /* RUBY_DEBUG */ /* @@ -1010,6 +1035,10 @@ Init_Namespace(void) rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); rb_define_global_function("dump_classext", rb_f_dump_classext, 1); + + rb_define_method(rb_cNamespace, "root?", rb_namespace_root_p, 0); + rb_define_method(rb_cNamespace, "main?", rb_namespace_main_p, 0); + rb_define_method(rb_cNamespace, "user?", rb_namespace_user_p, 0); #endif }