glibc/stdlib/tst-qsort4.c

110 lines
3.1 KiB
C
Raw Normal View History

/* Test the heapsort implementation behind qsort.
Copyright (C) 2023-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include "qsort.c"
#include <stdio.h>
#include <support/check.h>
#include <support/support.h>
static int
cmp (const void *a1, const void *b1, void *closure)
{
const signed char *a = a1;
const signed char *b = b1;
return *a - *b;
}
static void
check_one_sort (signed char *array, int length)
{
signed char *copy = xmalloc (length);
memcpy (copy, array, length);
stdlib: Reinstate stable mergesort implementation on qsort The mergesort removal from qsort implementation (commit 03bf8357e8) had the side-effect of making sorting nonstable. Although neither POSIX nor C standard specify that qsort should be stable, it seems that it has become an instance of Hyrum's law where multiple programs expect it. Also, the resulting introsort implementation is not faster than the previous mergesort (which makes the change even less appealing). This patch restores the previous mergesort implementation, with the exception of machinery that checks the resulting allocation against the _SC_PHYS_PAGES (it only adds complexity and the heuristic not always make sense depending on the system configuration and load). The alloca usage was replaced with a fixed-size buffer. For the fallback mechanism, the implementation uses heapsort. It is simpler than quicksort, and it does not suffer from adversarial inputs. With memory overcommit, it should be rarely triggered. The drawback is mergesort requires O(n) extra space, and since it is allocated with malloc the function is AS-signal-unsafe. It should be feasible to change it to use mmap, although I am not sure how urgent it is. The heapsort is also nonstable, so programs that require a stable sort would still be subject to this latent issue. The tst-qsort5 is removed since it will not create quicksort adversarial inputs with the current qsort_r implementation. Checked on x86_64-linux-gnu and aarch64-linux-gnu. Reviewed-by: Florian Weimer <fweimer@redhat.com>
2024-01-15 14:07:21 +00:00
heapsort_r (copy, length - 1, 1, cmp, NULL);
/* Verify that the result is sorted. */
for (int i = 1; i < length; ++i)
if (copy[i] < copy[i - 1])
{
support_record_failure ();
printf ("error: sorting failure for length %d at offset %d\n",
length, i - 1);
printf ("input:");
for (int i = 0; i < length; ++i)
printf (" %d", array[i]);
printf ("\noutput:");
for (int i = 0; i < length; ++i)
printf (" %d", copy[i]);
putchar ('\n');
break;
}
/* Verify that no elements went away or were added. */
{
int expected_counts[256];
for (int i = 0; i < length; ++i)
++expected_counts[array[i] & 0xff];
int actual_counts[256];
for (int i = 0; i < length; ++i)
++actual_counts[copy[i] & 0xff];
for (int i = 0; i < 256; ++i)
TEST_COMPARE (expected_counts[i], expected_counts[i]);
}
free (copy);
}
/* Enumerate all possible combinations of LENGTH elements. */
static void
check_combinations (int length, signed char *start, int offset)
{
if (offset == length)
check_one_sort (start, length);
else
for (int i = 0; i < length; ++i)
{
start[offset] = i;
check_combinations(length, start, offset + 1);
}
}
static int
do_test (void)
{
/* A random permutation of 20 values. */
check_one_sort ((signed char[20]) {5, 12, 16, 10, 14, 11, 9, 13, 8, 15,
0, 17, 3, 7, 1, 18, 2, 19, 4, 6}, 20);
/* A permutation that appeared during adversarial testing for the
quicksort pass. */
check_one_sort ((signed char[16]) {15, 3, 4, 2, 1, 0, 8, 7, 6, 5, 14,
13, 12, 11, 10, 9}, 16);
for (int i = 2; i <= 8; ++i)
{
signed char *buf = xmalloc (i);
check_combinations (i, buf, 0);
free (buf);
}
return 0;
}
#include <support/test-driver.c>