Issue #20203 has been updated by Andriy.Tyurnikov (Andriy Tyurnikov). Picking this up unless @XrXr or @ko1 already have it in progress — happy to hand over. PR: https://github.com/ruby/ruby/pull/18359 It stops Array#sort, Array#sort!, Enumerable#sort and Enumerable#sort_by from reaching qsort_r, so there is no scratch buffer left to double-free, and a comparison that raises no longer strands one. glibc does still allocate it: the introsort patch that would have removed the malloc was reverted before 2.39. One deviation from the plan above. Rather than routing to ruby_qsort, it reuses the allocation-free introsort that sort_by has had since Feature #19643, moved into a sort_template.h parameterised on the element type and the comparison so that the comparison expands inline. That measured faster than both ruby_qsort and libc. N=1M on macOS/arm64 against 4.0.6, ns per element: int random 74.7 vs 96.4 int sorted 2.2 vs 5.1 int reversed 2.5 vs 21.7 int two runs 36.2 vs 51.8 int all equal 1.7 vs 2.7 string random 241 vs 244 float random 184 vs 192 with a block 724 vs 691 sort_by Fixnum 94.7 vs 91.6 The last two are slower. Both are the paths where a Ruby call dominates each comparison, and both come from comparison count: this makes about 13% more comparisons than Apple's qsort, which uses a three-way partition and an insertion-sort shortcut that it does not. That looks like the most promising next step. Caveats: - nmin_run still uses ruby_qsort, so Enumerable#min and #max with an argument are unchanged, as are dir.c and compile.c. Happy to fold nmin_run in if you would rather see enum.c converted in one go. - I could not reproduce the original crash. macOS never takes glibc's malloc path, so the TestEnumerable failures do not occur here. The fix is structural rather than demonstrated, so it would be worth someone running it under the GCC 14 configuration that surfaced this. - The POSIX constraint that the comparison function shall not alter the array dissolves rather than gets fixed, since we no longer call a POSIX function. Whether the sort tolerates GC compaction rewriting elements mid-comparison becomes Ruby's own invariant. It does in testing — GC.compact inside a comparison over 3000 elements sorts correctly, as does fiber reentry out of a comparison — but that is observation, not proof. - Tie ordering changes on every platform. Bug #11379 settled that as unspecified. Also note that the partition and insertion scans are bounded rather than using the pivot as a sentinel: Array#sort lets a block return anything, and sort_spec.rb:88 (a.sort { -1 }) segfaults without it. This is the same defect Qualys reported in glibc's own qsort on 2024-01-30, present from 1.04 through 2.38, fixed there the same way. make test-all, make btest and make test-spec all pass. ---------------------------------------- Bug #20203: `TestEnumerable` test failures with GCC 14 https://bugs.ruby-lang.org/issues/20203#change-118573 * Author: vo.x (Vit Ondruch) * Status: Open * ruby -v: ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- There is ongoing mass rebuild in Fedora and that is first time GCC 14 is used and we observe test failures in `TestEnumerable`. Here are a few examples: ~~~ [ 3000/26419] TestEnumerable#test_transient_heap_sort_bymalloc_consolidate(): unaligned fastbin chunk detected ~~~ ~~~ [ 2455/26535] TestEnumerable#test_transient_heap_sort_bycorrupted size vs. prev_size in fastbins ~~~ ~~~ [ 9716/26532] TestEnumerable#test_any_with_unused_blockdouble free or corruption (fasttop) ~~~ The full logs are accessible [here](https://koji.fedoraproject.org/koji/taskinfo?taskID=112176941). Please drill through `Descendants` and `build.log` ---Files-------------------------------- sort-benchmark-ubuntu.png (233 KB) sort-benchmark-macos.png (232 KB) -- https://bugs.ruby-lang.org/