Prevent uninitialized classes from being allocated

This commit is contained in:
John Hawthorn committed 2026-10-13 17:02:46 -07:00
1 parent 540eaae321
commit 0adee861cb
6 files changed
+100 -83
Notes: git 2026-10-14 00:03:21 +00:00

No files matched your search

-14
View File
@@ -1277,20 +1277,6 @@ assert_equal 'Ractor::IsolationError', %q{
res
}
# Class#initialize writes the superclass of an uninitialized class, so it is
# owner-only like any other modification
assert_equal 'can not modify K because it is created by another Ractor', %q{
K = Class.allocate
r = Ractor.new { K.send(:initialize, Struct) }
begin
r.join
rescue Ractor::RemoteError => e
e.cause.message
end
}
# Module#refine writes its refinement tables into the receiver, so the receiver
# must be owned too
assert_equal 'can not modify M because it is created by another Ractor', %q{
+28 -7
View File
@@ -2200,15 +2200,16 @@ rb_mod_initialize_clone(int argc, VALUE* argv, VALUE clone)
* want to treat it like a regular class.
*/
/* :nodoc: */
static VALUE
rb_class_initialize(int argc, VALUE *argv, VALUE klass)
rb_class_s_new(int argc, VALUE *argv, VALUE self)
{
VALUE super;
VALUE klass;
// an uninitialized class is still somebody's, and this writes its superclass
rb_class_modify_check(klass);
if (RCLASS_SUPER(klass) != 0 || klass == rb_cBasicObject) {
rb_raise(rb_eTypeError, "already initialized class");
// #<Class:Class> is an ancestor of every metaclass, so this also receives Foo.singleton_class.new
if (self != rb_cClass) {
rb_raise(rb_eTypeError, "can't create instance of singleton class");
}
if (rb_check_arity(argc, 0, 1) == 0) {
super = rb_cObject;
@@ -2220,6 +2221,7 @@ rb_class_initialize(int argc, VALUE *argv, VALUE klass)
rb_raise(rb_eTypeError, "can't inherit uninitialized class");
}
}
klass = rb_class_s_alloc(self);
rb_class_set_super(klass, super);
RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super));
RCLASS_SET_ALLOCATOR(klass, RCLASS_ALLOCATOR(super));
@@ -2231,6 +2233,23 @@ rb_class_initialize(int argc, VALUE *argv, VALUE klass)
return klass;
}
/* :nodoc: */
static VALUE
rb_class_dup(VALUE self)
{
VALUE dup = rb_class_s_alloc(rb_obj_class(self));
return rb_obj_dup_setup(self, dup);
}
/* :nodoc: */
static VALUE
rb_class_clone(int argc, VALUE *argv, VALUE self)
{
VALUE kwfreeze = rb_get_freeze_opt(argc, argv);
VALUE clone = rb_class_s_alloc(rb_obj_class(self));
return rb_obj_clone_setup(self, clone, kwfreeze);
}
/*! \private */
void
rb_undefined_alloc(VALUE klass)
@@ -4691,11 +4710,13 @@ InitVM_Object(void)
rb_define_method(rb_singleton_class(rb_cClass), "allocate", rb_class_alloc, 0);
rb_define_method(rb_cClass, "allocate", rb_class_alloc, 0);
rb_define_method(rb_cClass, "new", rb_class_new_instance_pass_kw, -1);
rb_define_method(rb_cClass, "initialize", rb_class_initialize, -1);
rb_define_method(rb_cClass, "superclass", rb_class_superclass, 0);
rb_define_method(rb_cClass, "subclasses", rb_class_subclasses, 0); /* in class.c */
rb_define_method(rb_cClass, "attached_object", rb_class_attached_object, 0); /* in class.c */
rb_define_alloc_func(rb_cClass, rb_class_s_alloc);
rb_define_singleton_method(rb_cClass, "new", rb_class_s_new, -1);
rb_define_method(rb_cClass, "dup", rb_class_dup, 0);
rb_define_method(rb_cClass, "clone", rb_class_clone, -1);
rb_undef_alloc_func(rb_cClass);
rb_undef_method(rb_cClass, "extend_object");
rb_undef_method(rb_cClass, "append_features");
rb_undef_method(rb_cClass, "prepend_features");
+25 -17
View File
@@ -6,19 +6,6 @@ describe "Class#allocate" do
klass.allocate.should.instance_of?(klass)
end
it "returns a fully-formed instance of Module" do
klass = Class.allocate
klass.constants.should_not == nil
klass.methods.should_not == nil
end
it "throws an exception when calling a method on a new instance" do
klass = Class.allocate
-> do
klass.new
end.should.raise(Exception)
end
it "does not call initialize on the new instance" do
klass = Class.new do
def initialize(*args)
@@ -33,9 +20,30 @@ describe "Class#allocate" do
klass.allocate.should_not.initialized?
end
it "raises TypeError for #superclass" do
-> do
Class.allocate.superclass
end.should.raise(TypeError)
ruby_version_is ""..."4.1" do
it "returns a fully-formed instance of Module" do
klass = Class.allocate
klass.constants.should_not == nil
klass.methods.should_not == nil
end
it "throws an exception when calling a method on a new instance" do
klass = Class.allocate
-> do
klass.new
end.should.raise(Exception)
end
it "raises TypeError for #superclass" do
-> do
Class.allocate.superclass
end.should.raise(TypeError)
end
end
ruby_version_is "4.1" do
it "raises TypeError when called on Class itself" do
-> { Class.allocate }.should.raise(TypeError)
end
end
end
+38 -27
View File
@@ -1,34 +1,45 @@
require_relative '../../spec_helper'
describe "Class#initialize" do
it "is private" do
Class.private_methods(false).should.include?(:initialize)
end
it "raises a TypeError when called on already initialized classes" do
->{
Integer.send :initialize
}.should.raise(TypeError)
->{
Object.send :initialize
}.should.raise(TypeError)
end
# See [redmine:2601]
it "raises a TypeError when called on BasicObject" do
->{
BasicObject.send :initialize
}.should.raise(TypeError)
end
describe "when given the Class" do
before :each do
@uninitialized = Class.allocate
ruby_version_is ""..."4.1" do
describe "Class#initialize" do
it "is private" do
Class.private_methods(false).should.include?(:initialize)
end
it "raises a TypeError" do
->{@uninitialized.send(:initialize, Class)}.should.raise(TypeError)
it "raises a TypeError when called on already initialized classes" do
->{
Integer.send :initialize
}.should.raise(TypeError)
->{
Object.send :initialize
}.should.raise(TypeError)
end
# See [redmine:2601]
it "raises a TypeError when called on BasicObject" do
->{
BasicObject.send :initialize
}.should.raise(TypeError)
end
describe "when given the Class" do
before :each do
@uninitialized = Class.allocate
end
it "raises a TypeError" do
->{@uninitialized.send(:initialize, Class)}.should.raise(TypeError)
end
end
end
end
ruby_version_is "4.1" do
describe "Class#initialize" do
it "is not defined" do
Class.private_instance_methods(false).should_not.include?(:initialize)
Class.instance_methods(false).should_not.include?(:initialize)
end
end
end
+6 -18
View File
@@ -83,12 +83,6 @@ class TestClass < Test::Unit::TestCase
assert_nil(Class.new <=> 1)
end
def test_class_initialize
assert_raise(TypeError) do
Class.new.instance_eval { initialize }
end
end
def test_instantiate_singleton_class
c = class << Object.new; self; end
assert_raise(TypeError) { c.new }
@@ -317,24 +311,18 @@ class TestClass < Test::Unit::TestCase
end
def test_uninitialized
assert_raise(TypeError) { Class.allocate.new }
assert_raise(TypeError) { Class.allocate.superclass }
bug6863 = '[ruby-core:47148]'
assert_raise(TypeError, bug6863) { Class.new(Class.allocate) }
assert_raise(TypeError) { Class.allocate }
allocator = Class.instance_method(:allocate)
assert_raise(TypeError) { allocator.bind_call(Class) }
assert_nothing_raised { allocator.bind(Rational).call }
assert_nothing_raised { allocator.bind_call(Rational) }
end
def test_uninitialized_with_singleton_class
c = Class.allocate
c.singleton_class
assert_raise(TypeError) { c.new }
end
def test_uninitialized_dup
assert_raise(TypeError) { Class.allocate.dup.new }
def test_singleton_class_new
assert_raise_with_message(TypeError, /singleton class/) { Integer.singleton_class.new }
assert_raise_with_message(TypeError, /singleton class/) { Class.new.singleton_class.new }
assert_raise_with_message(TypeError, /singleton class/) { Class.singleton_class.new }
end
def test_nonascii_name
+3
View File
@@ -42,6 +42,9 @@ test_TOPDIR(RbConfigSingletonTest) `TOPDIR` is `nil` during CI while RBS type is
# Failing because ObjectSpace.count_nodes has been removed
test_count_nodes(ObjectSpaceTest)
# Class#initialize has been removed
test_initialize(ClassInstanceTest)
## Unknown failures
# NoMethodError: undefined method 'inspect' for an instance of RBS::UnitTest::Convertibles::ToInt