Fix crash in rb_gc_register_address

	[Bug #19584]

	Some C extensions pass a pointer to a global variable to
	rb_gc_register_address. However, if a GC is triggered inside of
	rb_gc_register_address, then the object could get swept since it does
	not exist on the stack.

	[Bug #19584] Register global variable address before assignment

	[Bug #19584] Register global variables before assignment

	[Bug #19584] [DOC] Tweek description of `rb_gc_register_address`
This commit is contained in:
Hiroshi SHIBATA 2025-03-13 13:26:38 +09:00
parent 2c49513291
commit f89a334b55
6 changed files with 30 additions and 13 deletions

13
gc.c
View file

@ -9281,10 +9281,23 @@ rb_gc_register_address(VALUE *addr)
rb_objspace_t *objspace = &rb_objspace; rb_objspace_t *objspace = &rb_objspace;
struct gc_list *tmp; struct gc_list *tmp;
VALUE obj = *addr;
tmp = ALLOC(struct gc_list); tmp = ALLOC(struct gc_list);
tmp->next = global_list; tmp->next = global_list;
tmp->varptr = addr; tmp->varptr = addr;
global_list = tmp; global_list = tmp;
/*
* Because some C extensions have assignment-then-register bugs,
* we guard `obj` here so that it would not get swept defensively.
*/
RB_GC_GUARD(obj);
if (0 && !SPECIAL_CONST_P(obj)) {
rb_warn("Object is assigned to registering address already: %"PRIsVALUE,
rb_obj_class(obj));
rb_print_backtrace();
}
} }
void void

View file

@ -26,10 +26,15 @@
RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_SYMBOL_EXPORT_BEGIN()
/** /**
* Inform the garbage collector that `valptr` points to a live Ruby object that * Inform the garbage collector that the global or static variable pointed by
* should not be moved. Note that extensions should use this API on global * `valptr` stores a live Ruby object that should not be moved. Note that
* constants instead of assuming constants defined in Ruby are always alive. * extensions should use this API on global constants instead of assuming
* Ruby code can remove global constants. * constants defined in Ruby are always alive. Ruby code can remove global
* constants.
*
* Because this registration itself has a possibility to trigger a GC, this
* function must be called before any GC-able objects is assigned to the
* address pointed by `valptr`.
*/ */
void rb_gc_register_address(VALUE *valptr); void rb_gc_register_address(VALUE *valptr);

7
io.c
View file

@ -15547,13 +15547,12 @@ Init_IO(void)
rb_gvar_ractor_local("$>"); rb_gvar_ractor_local("$>");
rb_gvar_ractor_local("$stderr"); rb_gvar_ractor_local("$stderr");
rb_stdin = rb_io_prep_stdin();
rb_stdout = rb_io_prep_stdout();
rb_stderr = rb_io_prep_stderr();
rb_global_variable(&rb_stdin); rb_global_variable(&rb_stdin);
rb_stdin = rb_io_prep_stdin();
rb_global_variable(&rb_stdout); rb_global_variable(&rb_stdout);
rb_stdout = rb_io_prep_stdout();
rb_global_variable(&rb_stderr); rb_global_variable(&rb_stderr);
rb_stderr = rb_io_prep_stderr();
orig_stdout = rb_stdout; orig_stdout = rb_stdout;
orig_stderr = rb_stderr; orig_stderr = rb_stderr;

2
ruby.c
View file

@ -647,8 +647,8 @@ ruby_init_loadpath(void)
# endif # endif
rb_obj_hide(selfpath); rb_obj_hide(selfpath);
OBJ_FREEZE_RAW(selfpath); OBJ_FREEZE_RAW(selfpath);
rb_libruby_selfpath = selfpath;
rb_gc_register_address(&rb_libruby_selfpath); rb_gc_register_address(&rb_libruby_selfpath);
rb_libruby_selfpath = selfpath;
# endif # endif
#endif #endif

View file

@ -28,8 +28,8 @@ static VALUE get_registered_before_rb_global_variable(VALUE self) {
} }
static VALUE gc_spec_rb_gc_register_address(VALUE self) { static VALUE gc_spec_rb_gc_register_address(VALUE self) {
rb_gc_register_address_outside_init = rb_str_new_cstr("rb_gc_register_address() outside Init_");
rb_gc_register_address(&rb_gc_register_address_outside_init); rb_gc_register_address(&rb_gc_register_address_outside_init);
rb_gc_register_address_outside_init = rb_str_new_cstr("rb_gc_register_address() outside Init_");
return rb_gc_register_address_outside_init; return rb_gc_register_address_outside_init;
} }
@ -67,14 +67,14 @@ static VALUE gc_spec_rb_gc_register_mark_object(VALUE self, VALUE obj) {
void Init_gc_spec(void) { void Init_gc_spec(void) {
VALUE cls = rb_define_class("CApiGCSpecs", rb_cObject); VALUE cls = rb_define_class("CApiGCSpecs", rb_cObject);
registered_tagged_value = INT2NUM(10);
registered_reference_value = rb_str_new2("Globally registered data");
rb_gc_register_address(&registered_tagged_value); rb_gc_register_address(&registered_tagged_value);
rb_gc_register_address(&registered_reference_value); rb_gc_register_address(&registered_reference_value);
rb_gc_register_address(&registered_before_rb_gc_register_address); rb_gc_register_address(&registered_before_rb_gc_register_address);
rb_global_variable(&registered_before_rb_global_variable); rb_global_variable(&registered_before_rb_global_variable);
registered_tagged_value = INT2NUM(10);
registered_reference_value = rb_str_new2("Globally registered data");
registered_before_rb_gc_register_address = rb_str_new_cstr("registered before rb_gc_register_address()"); registered_before_rb_gc_register_address = rb_str_new_cstr("registered before rb_gc_register_address()");
registered_before_rb_global_variable = rb_str_new_cstr("registered before rb_global_variable()"); registered_before_rb_global_variable = rb_str_new_cstr("registered before rb_global_variable()");

View file

@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 7 #define RUBY_VERSION_TEENY 7
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
#define RUBY_PATCHLEVEL 258 #define RUBY_PATCHLEVEL 259
#include "ruby/version.h" #include "ruby/version.h"
#include "ruby/internal/abi.h" #include "ruby/internal/abi.h"