compat: add qsort_s()

The function qsort_s() was introduced with C11 Annex K; it provides the ability to pass a context pointer to the comparison function, supports the convention of using a NULL pointer for an empty array and performs a few safety checks. Add an implementation based on compat/qsort.c for platforms that lack a native standards-compliant qsort_s() (i.e. basically everyone). It doesn't perform the full range of possible checks: It uses size_t instead of rsize_t and doesn't check nmemb and size against RSIZE_MAX because we probably don't have the restricted size type defined. For the same reason it returns int instead of errno_t. Signed-off-by: Rene Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Jan 22, 2017 at 18:51 UTC 04ee8b875b96c096c479132adca02fa734c3fa96
3 files changed +83
Makefile
+8
@@ -279,6 +279,9 @@ all::
279 # is a simplified version of the merge sort used in glibc. This is
280 # recommended if Git triggers O(n^2) behavior in your platform's qsort().
281 #
282 +# Define HAVE_ISO_QSORT_S if your platform provides a qsort_s() that's
283 +# compatible with the one described in C11 Annex K.
284 +#
285 # Define UNRELIABLE_FSTAT if your system's fstat does not return the same
286 # information on a not yet closed file that lstat would return for the same
287 # file after it was closed.
@@ -1423,6 +1426,11 @@ ifdef INTERNAL_QSORT
1426 COMPAT_CFLAGS += -DINTERNAL_QSORT
1427 COMPAT_OBJS += compat/qsort.o
1428 endif
1429 +ifdef HAVE_ISO_QSORT_S
1430 + COMPAT_CFLAGS += -DHAVE_ISO_QSORT_S
1431 +else
1432 + COMPAT_OBJS += compat/qsort_s.o
1433 +endif
1434 ifdef RUNTIME_PREFIX
1435 COMPAT_CFLAGS += -DRUNTIME_PREFIX
1436 endif
compat/qsort_s.c new
+69
@@ -0,0 +1,69 @@
1 +#include "../git-compat-util.h"
2 +
3 +/*
4 + * A merge sort implementation, simplified from the qsort implementation
5 + * by Mike Haertel, which is a part of the GNU C Library.
6 + * Added context pointer, safety checks and return value.
7 + */
8 +
9 +static void msort_with_tmp(void *b, size_t n, size_t s,
10 + int (*cmp)(const void *, const void *, void *),
11 + char *t, void *ctx)
12 +{
13 + char *tmp;
14 + char *b1, *b2;
15 + size_t n1, n2;
16 +
17 + if (n <= 1)
18 + return;
19 +
20 + n1 = n / 2;
21 + n2 = n - n1;
22 + b1 = b;
23 + b2 = (char *)b + (n1 * s);
24 +
25 + msort_with_tmp(b1, n1, s, cmp, t, ctx);
26 + msort_with_tmp(b2, n2, s, cmp, t, ctx);
27 +
28 + tmp = t;
29 +
30 + while (n1 > 0 && n2 > 0) {
31 + if (cmp(b1, b2, ctx) <= 0) {
32 + memcpy(tmp, b1, s);
33 + tmp += s;
34 + b1 += s;
35 + --n1;
36 + } else {
37 + memcpy(tmp, b2, s);
38 + tmp += s;
39 + b2 += s;
40 + --n2;
41 + }
42 + }
43 + if (n1 > 0)
44 + memcpy(tmp, b1, n1 * s);
45 + memcpy(b, t, (n - n2) * s);
46 +}
47 +
48 +int git_qsort_s(void *b, size_t n, size_t s,
49 + int (*cmp)(const void *, const void *, void *), void *ctx)
50 +{
51 + const size_t size = st_mult(n, s);
52 + char buf[1024];
53 +
54 + if (!n)
55 + return 0;
56 + if (!b || !cmp)
57 + return -1;
58 +
59 + if (size < sizeof(buf)) {
60 + /* The temporary array fits on the small on-stack buffer. */
61 + msort_with_tmp(b, n, s, cmp, buf, ctx);
62 + } else {
63 + /* It's somewhat large, so malloc it. */
64 + char *tmp = xmalloc(size);
65 + msort_with_tmp(b, n, s, cmp, tmp, ctx);
66 + free(tmp);
67 + }
68 + return 0;
69 +}
git-compat-util.h
+6
@@ -988,6 +988,12 @@ static inline void sane_qsort(void *base, size_t nmemb, size_t size,
988 qsort(base, nmemb, size, compar);
989 }
990
991 +#ifndef HAVE_ISO_QSORT_S
992 +int git_qsort_s(void *base, size_t nmemb, size_t size,
993 + int (*compar)(const void *, const void *, void *), void *ctx);
994 +#define qsort_s git_qsort_s
995 +#endif
996 +
997 #ifndef REG_STARTEND
998 #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd"
999 #endif