From d90f78b567ffa6cde84d26d8594c771bd1e4bd86 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 1 Feb 2026 11:33:45 +0900 Subject: [PATCH 01/11] Compare pointers with the end of path --- file.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/file.c b/file.c index 0af968cefb805a..6b4e1378600b0d 100644 --- a/file.c +++ b/file.c @@ -3674,13 +3674,15 @@ enc_path_skip_prefix(const char *path, const char *end, bool mb_enc, rb_encoding if (path + 2 <= end && isdirsep(path[0]) && isdirsep(path[1])) { path += 2; while (path < end && isdirsep(*path)) path++; - if ((path = enc_path_next(path, end, mb_enc, enc)) < end && path[0] && path[1] && !isdirsep(path[1])) + if ((path = enc_path_next(path, end, mb_enc, enc)) < end && + path + 2 <= end && !isdirsep(path[1])) { path = enc_path_next(path + 1, end, mb_enc, enc); + } return (char *)path; } #endif #ifdef DOSISH_DRIVE_LETTER - if (has_drive_letter(path)) + if (path + 2 <= end && has_drive_letter(path)) return (char *)(path + 2); #endif #endif /* defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) */ @@ -4022,13 +4024,13 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na enc = rb_enc_get(fname); BUFINIT(); - if (s[0] == '~' && abs_mode == 0) { /* execute only if NOT absolute_path() */ + if (s < fend && s[0] == '~' && abs_mode == 0) { /* execute only if NOT absolute_path() */ long userlen = 0; - if (isdirsep(s[1]) || s[1] == '\0') { + if (s + 1 == fend || isdirsep(s[1])) { buf = 0; b = 0; rb_str_set_len(result, 0); - if (*++s) ++s; + if (++s < fend) ++s; rb_default_home_dir(result); } else { @@ -4058,8 +4060,8 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na } #ifdef DOSISH_DRIVE_LETTER /* skip drive letter */ - else if (has_drive_letter(s)) { - if (isdirsep(s[2])) { + else if (s + 1 < fend && has_drive_letter(s)) { + if (s + 2 < fend && isdirsep(s[2])) { /* specified drive letter, and full path */ /* skip drive letter */ BUFCHECK(bdiff + 2 >= buflen); @@ -4093,7 +4095,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na } } #endif /* DOSISH_DRIVE_LETTER */ - else if (!rb_is_absolute_path(s)) { + else if (s == fend || !rb_is_absolute_path(s)) { if (!NIL_P(dname)) { rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result); rb_enc_associate(result, fs_enc_check(result, fname)); @@ -4106,7 +4108,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na p = e; } #if defined DOSISH || defined __CYGWIN__ - if (isdirsep(*s)) { + if (s < fend && isdirsep(*s)) { /* specified full path, but not drive letter nor UNC */ /* we need to get the drive letter or UNC share name */ p = skipprefix(buf, p, true, enc); @@ -4118,7 +4120,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na else { size_t len; b = s; - do s++; while (isdirsep(*s)); + do s++; while (s < fend && isdirsep(*s)); len = s - b; p = buf + len; BUFCHECK(bdiff >= buflen); @@ -4140,16 +4142,17 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na root = skipprefix(buf, p+1, true, enc); b = s; - while (*s) { + while (s < fend) { switch (*s) { case '.': if (b == s++) { /* beginning of path element */ - switch (*s) { - case '\0': + if (s == fend) { b = s; break; + } + switch (*s) { case '.': - if (*(s+1) == '\0' || isdirsep(*(s+1))) { + if (s+1 == fend || isdirsep(*(s+1))) { /* We must go back to the parent */ char *n; *p = '\0'; @@ -4163,7 +4166,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na } #if USE_NTFS else { - do ++s; while (istrailinggarbage(*s)); + do ++s; while (s < fend && istrailinggarbage(*s)); } #endif /* USE_NTFS */ break; @@ -4931,11 +4934,11 @@ enc_find_basename(const char *name, long *baselen, long *alllen, bool mb_enc, rb root = name; #endif - while (isdirsep(*name)) { + while (name < end && isdirsep(*name)) { name++; } - if (!*name) { + if (name == end) { p = name - 1; f = 1; #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC @@ -5139,7 +5142,7 @@ rb_file_dirname_n(VALUE fname, int n) return rb_enc_str_new(".", 1, enc); } #ifdef DOSISH_DRIVE_LETTER - if (has_drive_letter(name) && isdirsep(*(name + 2))) { + if (name + 3 < end && has_drive_letter(name) && isdirsep(*(name + 2))) { const char *top = skiproot(name + 2, end); dirname = rb_enc_str_new(name, 3, enc); rb_str_cat(dirname, top, p - top); @@ -5148,7 +5151,7 @@ rb_file_dirname_n(VALUE fname, int n) #endif dirname = rb_enc_str_new(name, p - name, enc); #ifdef DOSISH_DRIVE_LETTER - if (has_drive_letter(name) && root == name + 2 && p - name == 2) + if (root == name + 2 && p == root && name[1] == ':') rb_str_cat(dirname, ".", 1); #endif return dirname; From 3b2ad4b741a103d34fc309591ba9573989bb7888 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 1 Feb 2026 17:37:33 +0900 Subject: [PATCH 02/11] Check the suffix argument always if given It also must be ASCII-compatible and must not contain null byte, as well as the path argument. --- file.c | 20 ++++++++------------ test/ruby/test_file_exhaustive.rb | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/file.c b/file.c index 6b4e1378600b0d..70c3892986c1fa 100644 --- a/file.c +++ b/file.c @@ -5020,23 +5020,21 @@ ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encodin static VALUE rb_file_s_basename(int argc, VALUE *argv, VALUE _) { - VALUE fname, fext; - const char *name, *p; + VALUE fname, fext = Qnil; + const char *name, *p, *fp = 0; long f = 0, n; rb_encoding *enc; - fext = Qnil; - if (rb_check_arity(argc, 1, 2) == 2) { + argc = rb_check_arity(argc, 1, 2); + fname = argv[0]; + CheckPath(fname, name); + if (argc == 2) { fext = argv[1]; - StringValue(fext); + fp = StringValueCStr(fext); check_path_encoding(fext); - enc = rb_str_enc_get(fext); } - fname = argv[0]; - CheckPath(fname, name); if (NIL_P(fext) || !(enc = rb_enc_compatible(fname, fext))) { enc = rb_str_enc_get(fname); - fext = Qnil; } n = RSTRING_LEN(fname); @@ -5047,12 +5045,10 @@ rb_file_s_basename(int argc, VALUE *argv, VALUE _) bool mb_enc = !rb_str_encindex_fastpath(rb_enc_to_index(enc)); p = enc_find_basename(name, &f, &n, mb_enc, enc); if (n >= 0) { - if (NIL_P(fext)) { + if (!fp) { f = n; } else { - const char *fp; - fp = StringValueCStr(fext); if (!(f = rmext(p, f, n, fp, RSTRING_LEN(fext), enc))) { f = n; } diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 394dc47603f782..be9e6bd44e702d 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -1235,6 +1235,7 @@ def test_basename assert_equal("foo", File.basename("foo", ".ext")) assert_equal("foo", File.basename("foo.ext", ".ext")) assert_equal("foo", File.basename("foo.ext", ".*")) + assert_raise(ArgumentError) {File.basename("", "\0")} end if NTFS From 3fcd36ff5d149f05cab4cbade20bc40a59989740 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 31 Jan 2026 18:29:39 +0100 Subject: [PATCH 03/11] memory_view.c: Use ruby_sized_xfree --- memory_view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memory_view.c b/memory_view.c index 7bcb39972f03a8..9f5d6715804b22 100644 --- a/memory_view.c +++ b/memory_view.c @@ -845,7 +845,7 @@ rb_memory_view_release(rb_memory_view_t* view) if (rv) { unregister_exported_object(view->obj); view->obj = Qnil; - xfree((void *)view->item_desc.components); + SIZED_FREE_N((rb_memory_view_item_component_t *)view->item_desc.components, view->item_desc.length); } return rv; } From b39c1af417a27874373d8b936b535bcf8ec3276f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 31 Jan 2026 19:08:47 +0100 Subject: [PATCH 04/11] cont.c: use ruby_sized_xfree to free the stack Also use mimalloc rather than raw malloc for `rb_jit_cont`. --- cont.c | 13 ++++++++----- internal/gc.h | 2 -- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cont.c b/cont.c index a23b551d83aebd..f519ab3dc9d4e5 100644 --- a/cont.c +++ b/cont.c @@ -79,6 +79,7 @@ enum context_type { struct cont_saved_vm_stack { VALUE *ptr; + size_t size; #ifdef CAPTURE_JUST_VALID_VM_STACK size_t slen; /* length of stack (head of ec->vm_stack) */ size_t clen; /* length of control frames (tail of ec->vm_stack) */ @@ -1089,7 +1090,7 @@ cont_free(void *ptr) if (cont->type == CONTINUATION_CONTEXT) { SIZED_FREE_N(cont->saved_ec.vm_stack, cont->saved_ec.vm_stack_size); - RUBY_FREE_UNLESS_NULL(cont->machine.stack); + SIZED_FREE_N(cont->machine.stack, cont->machine.stack_size); } else { rb_fiber_t *fiber = (rb_fiber_t*)cont; @@ -1097,7 +1098,7 @@ cont_free(void *ptr) fiber_stack_release_locked(fiber); } - RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr); + SIZED_FREE_N(cont->saved_vm_stack.ptr, cont->saved_vm_stack.size); VM_ASSERT(cont->jit_cont != NULL); jit_cont_free(cont->jit_cont); @@ -1293,7 +1294,7 @@ jit_cont_new(rb_execution_context_t *ec) // We need to use calloc instead of something like ZALLOC to avoid triggering GC here. // When this function is called from rb_thread_alloc through rb_threadptr_root_fiber_setup, // the thread is still being prepared and marking it causes SEGV. - cont = calloc(1, sizeof(struct rb_jit_cont)); + cont = ruby_mimcalloc(1, sizeof(struct rb_jit_cont)); if (cont == NULL) rb_memerror(); cont->ec = ec; @@ -1332,7 +1333,7 @@ jit_cont_free(struct rb_jit_cont *cont) } rb_native_mutex_unlock(&jit_cont_lock); - free(cont); + ruby_mimfree(cont); } // Call a given callback against all on-stack ISEQs. @@ -1383,7 +1384,7 @@ rb_jit_cont_finish(void) struct rb_jit_cont *cont, *next; for (cont = first_jit_cont; cont != NULL; cont = next) { next = cont->next; - free(cont); // Don't use xfree because it's allocated by calloc. + ruby_mimfree(cont); // Don't use xfree because it's allocated by mimcalloc. } rb_native_mutex_destroy(&jit_cont_lock); } @@ -1492,6 +1493,7 @@ cont_capture(volatile int *volatile stat) #ifdef CAPTURE_JUST_VALID_VM_STACK cont->saved_vm_stack.slen = ec->cfp->sp - ec->vm_stack; cont->saved_vm_stack.clen = ec->vm_stack + ec->vm_stack_size - (VALUE*)ec->cfp; + cont->saved_vm_stack.size = cont->saved_vm_stack.slen + cont->saved_vm_stack.clen; cont->saved_vm_stack.ptr = ALLOC_N(VALUE, cont->saved_vm_stack.slen + cont->saved_vm_stack.clen); MEMCPY(cont->saved_vm_stack.ptr, ec->vm_stack, @@ -1501,6 +1503,7 @@ cont_capture(volatile int *volatile stat) VALUE, cont->saved_vm_stack.clen); #else + cont->saved_vm_stack.size = ec->vm_stack_size; cont->saved_vm_stack.ptr = ALLOC_N(VALUE, ec->vm_stack_size); MEMCPY(cont->saved_vm_stack.ptr, ec->vm_stack, VALUE, ec->vm_stack_size); #endif diff --git a/internal/gc.h b/internal/gc.h index e719a3ddc35ba1..5fa07a13fed125 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -83,8 +83,6 @@ rb_gc_debug_body(const char *mode, const char *msg, int st, void *ptr) #define RUBY_GC_INFO if(0)printf #endif -#define RUBY_FREE_UNLESS_NULL(ptr) if(ptr){ruby_xfree(ptr);(ptr)=NULL;} - #if STACK_GROW_DIRECTION > 0 # define STACK_UPPER(x, a, b) (a) #elif STACK_GROW_DIRECTION < 0 From dc6cdb1759166630f29c456279c84fee1c0351d5 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 31 Jan 2026 20:57:10 +0100 Subject: [PATCH 05/11] proc.c: use ruby_sized_xfree to free rb_binding_t --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index 298497401dfe88..f0cdcae7b1da7f 100644 --- a/proc.c +++ b/proc.c @@ -258,7 +258,7 @@ static void binding_free(void *ptr) { RUBY_FREE_ENTER("binding"); - ruby_xfree(ptr); + SIZED_FREE((rb_binding_t *)ptr); RUBY_FREE_LEAVE("binding"); } From d328412da84cdd26adb9a162bbf745bd43f7ee75 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 31 Jan 2026 18:41:55 +0100 Subject: [PATCH 06/11] Use ruby_sized_xfree to free embedded Hash and Set --- hash.c | 6 +----- internal/set_table.h | 2 ++ internal/st.h | 3 +++ parser_st.c | 2 ++ set.c | 15 ++++----------- st.c | 19 ++++++++++++++++--- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/hash.c b/hash.c index 83a55913fa7ce4..dbd8f76950fb28 100644 --- a/hash.c +++ b/hash.c @@ -1173,17 +1173,13 @@ hash_st_free(VALUE hash) { HASH_ASSERT(RHASH_ST_TABLE_P(hash)); - st_table *tab = RHASH_ST_TABLE(hash); - - xfree(tab->bins); - xfree(tab->entries); + rb_st_free_embedded_table(RHASH_ST_TABLE(hash)); } static void hash_st_free_and_clear_table(VALUE hash) { hash_st_free(hash); - RHASH_ST_CLEAR(hash); } diff --git a/internal/set_table.h b/internal/set_table.h index 3c29abb4f50e66..cb83e96accf2f2 100644 --- a/internal/set_table.h +++ b/internal/set_table.h @@ -58,6 +58,8 @@ int rb_set_foreach_check(set_table *, set_foreach_check_callback_func *, st_data st_index_t rb_set_keys(set_table *table, st_data_t *keys, st_index_t size); #define set_free_table rb_set_free_table void rb_set_free_table(set_table *); +#define set_free_embedded_table rb_set_free_embedded_table +void set_free_embedded_table(set_table *tab); #define set_table_clear rb_set_table_clear void rb_set_table_clear(set_table *); #define set_copy rb_set_copy diff --git a/internal/st.h b/internal/st.h index c220edd9f04d3f..e4cd29a5b01a0d 100644 --- a/internal/st.h +++ b/internal/st.h @@ -8,4 +8,7 @@ st_table *rb_st_replace(st_table *new_tab, st_table *old_tab); st_table *rb_st_init_existing_table_with_size(st_table *tab, const struct st_hash_type *type, st_index_t size); #define st_init_existing_table_with_size rb_st_init_existing_table_with_size +void rb_st_free_embedded_table(st_table *tab); +#define st_free_embedded_table rb_st_free_embedded_table + #endif diff --git a/parser_st.c b/parser_st.c index c2340261226d91..8deb61adb6c06c 100644 --- a/parser_st.c +++ b/parser_st.c @@ -80,6 +80,8 @@ nonempty_memcpy(void *dest, const void *src, size_t n) #define st_init_table_with_size rb_parser_st_init_table_with_size #undef st_init_existing_table_with_size #define st_init_existing_table_with_size rb_parser_st_init_existing_table_with_size +#undef st_free_embedded_table +#define st_free_embedded_table rb_parser_st_free_embedded_table #undef st_insert #define st_insert rb_parser_st_insert #undef st_lookup diff --git a/set.c b/set.c index 0fcfb1ef14748d..ea04a8495a1a56 100644 --- a/set.c +++ b/set.c @@ -138,18 +138,11 @@ set_mark(void *ptr) if (sobj->table.entries) set_table_foreach(&sobj->table, mark_key, 0); } -static void -set_free_embedded(struct set_object *sobj) -{ - xfree((&sobj->table)->entries); -} - static void set_free(void *ptr) { struct set_object *sobj = ptr; - set_free_embedded(sobj); - memset(&sobj->table, 0, sizeof(sobj->table)); + set_free_embedded_table(&sobj->table); } static size_t @@ -546,7 +539,7 @@ set_i_initialize_copy(VALUE set, VALUE other) struct set_object *sobj; TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj); - set_free_embedded(sobj); + set_free_embedded_table(&sobj->table); set_copy(&sobj->table, RSET_TABLE(other)); rb_gc_writebarrier_remember(set); @@ -1185,9 +1178,9 @@ set_reset_table_with_type(VALUE set, const struct st_hash_type *type) .into = new }; set_iter(set, set_merge_i, (st_data_t)&args); - set_free_embedded(sobj); + set_free_embedded_table(&sobj->table); memcpy(&sobj->table, new, sizeof(*new)); - xfree(new); + SIZED_FREE(new); } else { sobj->table.type = type; diff --git a/st.c b/st.c index 7891947549aa31..09aef44c6089e8 100644 --- a/st.c +++ b/st.c @@ -692,12 +692,19 @@ st_free_bins(const st_table *tab) { sized_free(tab->bins, st_bins_memsize(tab)); } -/* Free table TAB space. */ + void -st_free_table(st_table *tab) +st_free_embedded_table(st_table *tab) { st_free_bins(tab); st_free_entries(tab); +} + +/* Free table TAB space. */ +void +st_free_table(st_table *tab) +{ + st_free_embedded_table(tab); free_fixed_ptr(tab); } @@ -2560,12 +2567,18 @@ set_table_clear(set_table *tab) tab->rebuilds_num++; } +void +set_free_embedded_table(set_table *tab) +{ + sized_free(tab->entries, set_entries_memsize(tab)); +} + /* Free table TAB space. This should only be used if you passed NULL to set_init_table_with_size/set_copy when creating the table. */ void set_free_table(set_table *tab) { - sized_free(tab->entries, set_entries_memsize(tab)); + set_free_embedded_table(tab); free_fixed_ptr(tab); } From 44b5971e474e1975526c6792955af8457da8f4c1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 1 Feb 2026 10:05:21 +0100 Subject: [PATCH 07/11] string.c: use ruby_sized_xfree in a few more cases --- marshal.c | 2 +- string.c | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/marshal.c b/marshal.c index 9d9a83097ce267..1a9e817872eb02 100644 --- a/marshal.c +++ b/marshal.c @@ -2590,7 +2590,7 @@ marshal_compat_table_mark_and_move(void *tbl) static int marshal_compat_table_free_i(st_data_t key, st_data_t value, st_data_t _) { - xfree((marshal_compat_t *)value); + SIZED_FREE((marshal_compat_t *)value); return ST_CONTINUE; } diff --git a/string.c b/string.c index 448594278d9e05..c99362b74d8d52 100644 --- a/string.c +++ b/string.c @@ -303,7 +303,7 @@ rb_str_make_embedded(VALUE str) if (len > 0) { memcpy(RSTRING_PTR(str), buf, len); - ruby_sized_xfree(buf, old_capa); + SIZED_FREE_N(buf, old_capa); } TERM_FILL(RSTRING(str)->as.embed.ary + len, termlen); @@ -1464,7 +1464,7 @@ str_replace_shared_without_enc(VALUE str2, VALUE str) } char *ptr2 = STR_HEAP_PTR(str2); if (ptr2 != ptr) { - ruby_sized_xfree(ptr2, STR_HEAP_SIZE(str2)); + SIZED_FREE_N(ptr2, STR_HEAP_SIZE(str2)); } } FL_SET(str2, STR_NOEMBED); @@ -1743,7 +1743,7 @@ rb_str_free(VALUE str) } else { RB_DEBUG_COUNTER_INC(obj_str_ptr); - ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); + SIZED_FREE_N(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); } } @@ -2705,7 +2705,7 @@ str_make_independent_expand(VALUE str, long len, long expand, const int termlen) memcpy(ptr, oldptr, len); } if (FL_TEST_RAW(str, STR_NOEMBED|STR_NOFREE|STR_SHARED) == STR_NOEMBED) { - xfree(oldptr); + SIZED_FREE_N(oldptr, STR_HEAP_SIZE(str)); } STR_SET_NOEMBED(str); FL_UNSET(str, STR_SHARED|STR_NOFREE); @@ -2763,7 +2763,7 @@ str_discard(VALUE str) { str_modifiable(str); if (!STR_EMBED_P(str) && !FL_TEST(str, STR_SHARED|STR_NOFREE)) { - ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); + SIZED_FREE_N(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); RSTRING(str)->as.heap.ptr = 0; STR_SET_LEN(str, 0); } @@ -3484,7 +3484,7 @@ rb_str_resize(VALUE str, long len) TERM_FILL(RSTRING(str)->as.embed.ary + len, termlen); STR_SET_LEN(str, len); if (independent) { - ruby_sized_xfree(ptr, capa + termlen); + SIZED_FREE_N(ptr, capa + termlen); } return str; } @@ -5770,11 +5770,14 @@ rb_str_drop_bytes(VALUE str, long len) nlen = olen - len; if (str_embed_capa(str) >= nlen + TERM_LEN(str)) { char *oldptr = ptr; + size_t old_capa = RSTRING(str)->as.heap.aux.capa + TERM_LEN(str); int fl = (int)(RBASIC(str)->flags & (STR_NOEMBED|STR_SHARED|STR_NOFREE)); STR_SET_EMBED(str); ptr = RSTRING(str)->as.embed.ary; memmove(ptr, oldptr + len, nlen); - if (fl == STR_NOEMBED) xfree(oldptr); + if (fl == STR_NOEMBED) { + SIZED_FREE_N(oldptr, old_capa); + } } else { if (!STR_SHARED_P(str)) { @@ -8409,7 +8412,7 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) int r = rb_enc_precise_mbclen((char *)s, (char *)send, e1); if (!MBCLEN_CHARFOUND_P(r)) { - xfree(buf); + SIZED_FREE_N(buf, max + termlen); rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(e1)); } clen = MBCLEN_CHARFOUND_LEN(r); @@ -8461,7 +8464,7 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) t += tlen; } if (!STR_EMBED_P(str)) { - ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); + SIZED_FREE_N(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); } TERM_FILL((char *)t, termlen); RSTRING(str)->as.heap.ptr = (char *)buf; @@ -8497,7 +8500,7 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) int r = rb_enc_precise_mbclen((char *)s, (char *)send, e1); if (!MBCLEN_CHARFOUND_P(r)) { - xfree(buf); + SIZED_FREE_N(buf, max + termlen); rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(e1)); } clen = MBCLEN_CHARFOUND_LEN(r); @@ -8545,7 +8548,7 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) t += tlen; } if (!STR_EMBED_P(str)) { - ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); + SIZED_FREE_N(STR_HEAP_PTR(str), STR_HEAP_SIZE(str)); } TERM_FILL((char *)t, termlen); RSTRING(str)->as.heap.ptr = (char *)buf; From 9fa0cdc9239a9f56158d2c593780a1000444b82a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 1 Feb 2026 10:10:44 +0100 Subject: [PATCH 08/11] vm_method.c: use sized free for css and method_definition --- vm_method.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm_method.c b/vm_method.c index dcf35527f7b956..5289bf03f8e741 100644 --- a/vm_method.c +++ b/vm_method.c @@ -40,7 +40,7 @@ mark_cc_entry_i(VALUE ccs_ptr, void *data) VM_ASSERT(!vm_cc_super_p(cc) && !vm_cc_refinement_p(cc)); vm_cc_invalidate(cc); } - ruby_xfree(ccs); + ruby_sized_xfree(ccs, vm_ccs_alloc_size(ccs->capa)); return ID_TABLE_DELETE; } else { @@ -71,7 +71,7 @@ cc_table_free_i(VALUE ccs_ptr, void *data) struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr; VM_ASSERT(vm_ccs_p(ccs)); - ruby_xfree(ccs); + ruby_sized_xfree(ccs, vm_ccs_alloc_size(ccs->capa)); return ID_TABLE_CONTINUE; } @@ -201,7 +201,7 @@ rb_vm_ccs_invalidate_and_free(struct rb_class_cc_entries *ccs) { RB_DEBUG_COUNTER_INC(ccs_free); vm_ccs_invalidate(ccs); - ruby_xfree(ccs); + ruby_sized_xfree(ccs, vm_ccs_alloc_size(ccs->capa)); } void @@ -572,7 +572,7 @@ invalidate_ccs_in_iclass_cc_tbl(VALUE value, void *data) { struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)value; vm_cme_invalidate((rb_callable_method_entry_t *)ccs->cme); - xfree(ccs); + ruby_sized_xfree(ccs, vm_ccs_alloc_size(ccs->capa)); return ID_TABLE_DELETE; } @@ -845,7 +845,7 @@ method_definition_release(rb_method_definition_t *def) if (reference_count_was == 1) { if (METHOD_DEBUG) fprintf(stderr, "-%p-%s:1->0 (remove)\n", (void *)def, rb_id2name(def->original_id)); - xfree(def); + SIZED_FREE(def); } else { if (METHOD_DEBUG) fprintf(stderr, "-%p-%s:%d->%d (dec)\n", (void *)def, rb_id2name(def->original_id), From ecb28ce3ab49b8039680f1b1624abd4bddf4956c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 1 Feb 2026 10:26:00 +0100 Subject: [PATCH 09/11] prism_compile.c: use ruby_sized_xfree --- prism_compile.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 771db13f8909e7..731859b35aee80 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -4956,7 +4956,7 @@ pm_multi_target_state_update(pm_multi_target_state_t *state) previous = current; current = current->next; - xfree(previous); + SIZED_FREE(previous); } } @@ -10538,7 +10538,7 @@ pm_parse_result_free(pm_parse_result_t *result) } if (result->parsed) { - xfree(result->node.constants); + SIZED_FREE_N(result->node.constants, result->node.parser->constant_pool.size); pm_scope_node_destroy(&result->node); } @@ -10942,7 +10942,7 @@ pm_parse_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, p } // Finally, we'll free the array of errors that we allocated. - xfree(errors); + SIZED_FREE_N(errors, error_list->size); } #undef PM_ERROR_TRUNCATE @@ -11232,9 +11232,9 @@ pm_read_file(pm_string_t *string, const char *filepath) int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0); if (length == 0) return PM_STRING_INIT_ERROR_GENERIC; - WCHAR *wfilepath = xmalloc(sizeof(WCHAR) * ((size_t) length)); + WCHAR *wfilepath = ALLOC_N(WCHAR, length); if ((wfilepath == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, wfilepath, length) == 0)) { - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); return PM_STRING_INIT_ERROR_GENERIC; } @@ -11249,7 +11249,7 @@ pm_read_file(pm_string_t *string, const char *filepath) } } - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); return result; } @@ -11257,7 +11257,7 @@ pm_read_file(pm_string_t *string, const char *filepath) DWORD file_size = GetFileSize(file, NULL); if (file_size == INVALID_FILE_SIZE) { CloseHandle(file); - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); return PM_STRING_INIT_ERROR_GENERIC; } @@ -11265,7 +11265,7 @@ pm_read_file(pm_string_t *string, const char *filepath) // the source to a constant empty string and return. if (file_size == 0) { CloseHandle(file); - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); const uint8_t source[] = ""; *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; return PM_STRING_INIT_SUCCESS; @@ -11275,7 +11275,7 @@ pm_read_file(pm_string_t *string, const char *filepath) HANDLE mapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); if (mapping == NULL) { CloseHandle(file); - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); return PM_STRING_INIT_ERROR_GENERIC; } @@ -11283,7 +11283,7 @@ pm_read_file(pm_string_t *string, const char *filepath) uint8_t *source = (uint8_t *) MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); CloseHandle(mapping); CloseHandle(file); - xfree(wfilepath); + SIZED_FREE_N(wfilepath, length); if (source == NULL) { return PM_STRING_INIT_ERROR_GENERIC; From 4d5cd811656bd34a68db9990bebd9fc75043780d Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 1 Feb 2026 10:35:33 +0100 Subject: [PATCH 10/11] ractor_sync.c: use ruby_sized_xfree --- ractor_sync.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ractor_sync.c b/ractor_sync.c index a63df7c407b194..a346ea497bc4a4 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -1,4 +1,3 @@ - // this file is included by ractor.c struct ractor_port { @@ -32,7 +31,7 @@ ractor_port_mark(void *ptr) static void ractor_port_free(void *ptr) { - xfree(ptr); + SIZED_FREE((struct ractor_port *)ptr); } static size_t @@ -231,7 +230,7 @@ ractor_basket_mark(const struct ractor_basket *b) static void ractor_basket_free(struct ractor_basket *b) { - xfree(b); + SIZED_FREE(b); } static struct ractor_basket * @@ -285,7 +284,7 @@ ractor_queue_free(struct ractor_queue *rq) VM_ASSERT(ccan_list_empty(&rq->set)); - xfree(rq); + SIZED_FREE(rq); } RBIMPL_ATTR_MAYBE_UNUSED() @@ -575,7 +574,7 @@ ractor_monitor(rb_execution_context_t *ec, VALUE self, VALUE port) RACTOR_UNLOCK(r); if (terminated) { - xfree(rm); + SIZED_FREE(rm); ractor_port_send(ec, port, ractor_exit_token(r->sync.legacy_exc), Qfalse); return Qfalse; @@ -603,7 +602,7 @@ ractor_unmonitor(rb_execution_context_t *ec, VALUE self, VALUE port) (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r)); ccan_list_del(&rm->node); - xfree(rm); + SIZED_FREE(rm); } } } @@ -641,7 +640,7 @@ ractor_notify_exit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE legacy, bo ractor_try_send(ec, &rm->port, token, false); ccan_list_del(&rm->node); - xfree(rm); + SIZED_FREE(rm); } VM_ASSERT(ccan_list_empty(&cr->sync.monitors)); @@ -1260,7 +1259,7 @@ ractor_selector_free(void *ptr) { struct ractor_selector *s = ptr; st_free_table(s->ports); - ruby_xfree(ptr); + SIZED_FREE(s); } static size_t From 1276c233eac42d78016027a35b9e728427b215d2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 31 Jan 2026 22:57:35 +0100 Subject: [PATCH 11/11] thread.c: use ruby_sized_xfree for fdset and interupt tasks --- thread.c | 54 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/thread.c b/thread.c index e66352c03f6c4a..bdca1e24694fdb 100644 --- a/thread.c +++ b/thread.c @@ -4251,13 +4251,20 @@ rb_fd_init(rb_fdset_t *fds) FD_ZERO(fds->fdset); } +static inline size_t +fdset_memsize(int maxfd) +{ + size_t o = howmany(maxfd, NFDBITS) * sizeof(fd_mask); + if (o < sizeof(fd_set)) { + return sizeof(fd_set); + } + return o; +} + void rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) { - size_t size = howmany(rb_fd_max(src), NFDBITS) * sizeof(fd_mask); - - if (size < sizeof(fd_set)) - size = sizeof(fd_set); + size_t size = fdset_memsize(rb_fd_max(src)); dst->maxfd = src->maxfd; dst->fdset = xmalloc(size); memcpy(dst->fdset, src->fdset, size); @@ -4266,7 +4273,7 @@ rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) void rb_fd_term(rb_fdset_t *fds) { - xfree(fds->fdset); + ruby_sized_xfree(fds->fdset, fdset_memsize(fds->maxfd)); fds->maxfd = 0; fds->fdset = 0; } @@ -4281,14 +4288,11 @@ rb_fd_zero(rb_fdset_t *fds) static void rb_fd_resize(int n, rb_fdset_t *fds) { - size_t m = howmany(n + 1, NFDBITS) * sizeof(fd_mask); - size_t o = howmany(fds->maxfd, NFDBITS) * sizeof(fd_mask); - - if (m < sizeof(fd_set)) m = sizeof(fd_set); - if (o < sizeof(fd_set)) o = sizeof(fd_set); + size_t m = fdset_memsize(n + 1); + size_t o = fdset_memsize(fds->maxfd); if (m > o) { - fds->fdset = xrealloc(fds->fdset, m); + fds->fdset = ruby_sized_xrealloc(fds->fdset, m, o); memset((char *)fds->fdset + o, 0, m - o); } if (n >= fds->maxfd) fds->maxfd = n + 1; @@ -4318,23 +4322,18 @@ rb_fd_isset(int n, const rb_fdset_t *fds) void rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int max) { - size_t size = howmany(max, NFDBITS) * sizeof(fd_mask); - - if (size < sizeof(fd_set)) size = sizeof(fd_set); + size_t size = fdset_memsize(max); + dst->fdset = ruby_sized_xrealloc(dst->fdset, size, fdset_memsize(dst->maxfd)); dst->maxfd = max; - dst->fdset = xrealloc(dst->fdset, size); memcpy(dst->fdset, src, size); } void rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src) { - size_t size = howmany(rb_fd_max(src), NFDBITS) * sizeof(fd_mask); - - if (size < sizeof(fd_set)) - size = sizeof(fd_set); + size_t size = fdset_memsize(rb_fd_max(src)); + dst->fdset = ruby_sized_xrealloc(dst->fdset, size, fdset_memsize(dst->maxfd)); dst->maxfd = src->maxfd; - dst->fdset = xrealloc(dst->fdset, size); memcpy(dst->fdset, src->fdset, size); } @@ -4386,10 +4385,19 @@ rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) rb_fd_dup(dst, src); } +static inline size_t +fdset_memsize(int capa) +{ + if (capa == FD_SETSIZE) { + return sizeof(fd_set); + } + return sizeof(unsigned int) + (capa * sizeof(SOCKET)); +} + void rb_fd_term(rb_fdset_t *set) { - xfree(set->fdset); + ruby_sized_xfree(set->fdset, fdset_memsize(set->capa)); set->fdset = NULL; set->capa = 0; } @@ -6265,7 +6273,7 @@ threadptr_interrupt_exec_exec(rb_thread_t *th) else { (*task->func)(task->data); } - ruby_xfree(task); + SIZED_FREE(task); } else { break; @@ -6281,7 +6289,7 @@ threadptr_interrupt_exec_cleanup(rb_thread_t *th) struct rb_interrupt_exec_task *task; while ((task = ccan_list_pop(&th->interrupt_exec_tasks, struct rb_interrupt_exec_task, node)) != NULL) { - ruby_xfree(task); + SIZED_FREE(task); } } rb_native_mutex_unlock(&th->interrupt_lock);