Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 33 additions & 12 deletions class.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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");
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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 *
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
12 changes: 10 additions & 2 deletions ext/stringio/stringio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion internal/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
117 changes: 72 additions & 45 deletions test/ruby/test_namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -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