diff --git a/class.c b/class.c index 4dddf08c67af38..5e50a281d9a9fe 100644 --- a/class.c +++ b/class.c @@ -102,7 +102,7 @@ rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_id_table_free(tbl); } - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { RUBY_ASSERT(is_prime); // superclasses should only be used on prime @@ -126,13 +126,30 @@ rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); } - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); if (!is_prime) { // the prime classext will be freed with RClass xfree(ext); } } +static void +iclass_free_orphan_classext(VALUE klass, rb_classext_t *ext) +{ + if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { + /* Method table is not shared for origin iclasses of classes */ + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + } + + if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { + rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); + } + + rb_class_classext_free_subclasses(ext, klass, true); // replacing this classext with a newer one + + xfree(ext); +} + struct rb_class_set_namespace_classext_args { VALUE obj; rb_classext_t *ext; @@ -144,11 +161,11 @@ rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, s struct rb_class_set_namespace_classext_args *args = (struct rb_class_set_namespace_classext_args *)a; if (existing) { - if (BUILTIN_TYPE(args->obj) == T_ICLASS) { - rb_iclass_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + if (LIKELY(BUILTIN_TYPE(args->obj) == T_ICLASS)) { + iclass_free_orphan_classext(args->obj, (rb_classext_t *)*val_ptr); } else { - rb_class_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + rb_bug("Updating existing classext for non-iclass never happen"); } } @@ -375,6 +392,8 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n RCLASSEXT_SET_INCLUDER(ext, iclass, RCLASSEXT_INCLUDER(src)); + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); + first_set = RCLASS_SET_NAMESPACE_CLASSEXT(iclass, ns, ext); if (first_set) { RCLASS_SET_PRIME_CLASSEXT_WRITABLE(iclass, false); @@ -454,6 +473,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace if (RBASIC_CLASS(iclass) == klass) { // Is the subclass an ICLASS including this module into another class // If so we need to re-associate it under our namespace with the new ext + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); class_duplicate_iclass_classext(iclass, ext, ns); } } @@ -574,7 +594,7 @@ void rb_class_remove_subclass_head(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); } static struct rb_subclass_entry * @@ -610,17 +630,18 @@ remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) next->prev = prev; } - xfree(entry); - if (first_entry) { if (next) { st_update(tbl, ns_id, remove_class_from_subclasses_replace_first_entry, (st_data_t)next); } else { - // no subclass entries in this ns + // no subclass entries in this ns after the deletion st_delete(tbl, &ns_id, NULL); } } + + xfree(entry); + break; } else if (first_entry) { @@ -655,7 +676,7 @@ rb_class_remove_from_module_subclasses(VALUE klass) } void -rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass) +rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass, bool replacing) { rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); struct st_table *tbl = anchor->ns_subclasses->tbl; @@ -674,12 +695,12 @@ rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass) rb_ns_subclasses_ref_dec(anchor->ns_subclasses); xfree(anchor); - if (RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { + if (!replacing && RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); rb_ns_subclasses_ref_dec(ns_sub); } - if (RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { + if (!replacing && RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); rb_ns_subclasses_ref_dec(ns_sub); diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 965355df67d87c..0dbf1fa3ec0918 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -614,8 +614,16 @@ strio_close_write(VALUE self) * call-seq: * closed? -> true or false * - * Returns +true+ if +self+ is closed for both reading and writing, - * +false+ otherwise. + * Returns whether +self+ is closed for both reading and writing: + * + * strio = StringIO.new + * strio.closed? # => false # Open for reading and writing. + * strio.close_read + * strio.closed? # => false # Still open for writing. + * strio.close_write + * strio.closed? # => true # Now closed for both. + * + * Related: StringIO.closed_read?, StringIO.closed_write?. */ static VALUE strio_closed(VALUE self) diff --git a/internal/class.h b/internal/class.h index 5d843e58da3923..138620dd6f0469 100644 --- a/internal/class.h +++ b/internal/class.h @@ -490,7 +490,7 @@ void rb_class_classext_foreach(VALUE klass, rb_class_classext_foreach_callback_f void rb_class_subclass_add(VALUE super, VALUE klass); void rb_class_remove_from_super_subclasses(VALUE); void rb_class_remove_from_module_subclasses(VALUE); -void rb_class_classext_free_subclasses(rb_classext_t *, VALUE); +void rb_class_classext_free_subclasses(rb_classext_t *, VALUE, bool); void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE); void rb_class_detach_subclasses(VALUE); void rb_class_detach_module_subclasses(VALUE); diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 5661a98ca2b5f1..bfbbe38e051ab2 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -538,51 +538,6 @@ 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 2, error.size - - 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 2, error.size - - 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? @@ -664,4 +619,76 @@ def test_eval_error_handling assert_equal 4, result end end + + # Tests which run always (w/o RUBY_NAMESPACE=1 globally) + + 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 2, error.size + + 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 2, error.size + + 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_root_and_main_methods + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + pend unless Namespace.respond_to?(:root) and Namespace.respond_to?(:main) # for RUBY_DEBUG > 0 + + assert Namespace.root.respond_to?(:root?) + assert Namespace.main.respond_to?(:main?) + + assert Namespace.root.root? + assert Namespace.main.main? + assert_equal Namespace.main, Namespace.current + + $a = 1 + $LOADED_FEATURES.push("/tmp/foobar") + + assert_equal 2, Namespace.root.eval('$a = 2; $a') + assert !Namespace.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') + assert "FooClass", Namespace.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + + assert_equal 1, $a + assert !$LOADED_FEATURES.include?("/tmp/barbaz") + assert !Object.const_defined?(:FooClass) + end; + end end