Feature #21963
openA solution to completely avoid allocated-but-uninitialized objects
Description
A common issue when defining a class is to handle allocated-but-uninitialized objects.
For example:
obj = MyClass.allocate
obj.some_method
This can easily segfault for classes defined in C and raise an unclear exception for classes defined in Ruby.
As a workaround many core (and non-core) classes add a check that they are initialized in every instance method.
This is suboptimal for performance and correctness, classes should not need to care about allocated-but-uninitialized objects.
Fundamentally, to solve this we need to guarantee that after the allocation function is used that either initialize, initialize_dup or initialize_clone is called.
And we can't guarantee that for Class#allocate.
The current workarounds are:
-
undef allocate, but this does not preventClass.instance_method(:allocate).bind_call(Foo). -
rb_undef_alloc_func()but this breaksdup,cloneandMarshal.
The idea is to have in addition of the public alloc function (in rb_classext_struct.as.class.allocator) an internal alloc function.
Then:
-
Class#new,dup,cloneandMarshalalways use the internal alloc function, because they guarantee to callinitialize,initialize_duporinitialize_clone. -
rb_define_alloc_func()sets both fields. -
rb_undef_alloc_func()sets both fields. -
rb_get_alloc_func()reads the public alloc function (unchanged) -
Class#allocateuses the public alloc function (unchanged)
We add a new method on Class, for example Class#safe_initialization, which:
- Sets the public alloc function to
UNDEF_ALLOC_FUNC, same asrb_undef_alloc_func(), soClass#allocateandrb_get_alloc_func()will raise if they are used (as they are unsafe). - Preserves the internal alloc function so
Class#new,dup,cloneandMarshalkeep working.
After that the class has fully safe intialization and does not need to worry about allocated-but-uninitialized objects anymore.