Skip to content

Commit 8cbef12

Browse files
committed
util: do not perform allocations in insertsort
Our hand-rolled fallback sorting function `git__insertsort_r` does an in-place sort of the given array. As elements may not necessarily be pointers, it needs a way of swapping two values of arbitrary size, which is currently implemented by allocating a temporary buffer of the element's size. This is problematic, though, as the emulated `qsort` interface doesn't provide any return values and thus cannot signal an error if allocation of that temporary buffer has failed. Convert the function to swap via a temporary buffer allocated on the stack. Like this, it can `memcpy` contents of both elements in small batches without requiring a heap allocation. The buffer size has been chosen such that in most cases, a single iteration of copying will suffice. Most importantly, it can fully contain `git_oid` structures and pointers. Add a bunch of tests for the `git__qsort_r` interface to verify nothing breaks. Furthermore, this removes the declaration of `git__insertsort_r` and makes it static as it is not used anywhere else.
1 parent f3b3e54 commit 8cbef12

File tree

3 files changed

+117
-28
lines changed

3 files changed

+117
-28
lines changed

src/util.c

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,32 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
719719
}
720720
#endif
721721

722+
static void swap(uint8_t *a, uint8_t *b, size_t elsize)
723+
{
724+
char tmp[256];
725+
726+
while (elsize) {
727+
size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp);
728+
memcpy(tmp, a + elsize - n, n);
729+
memcpy(a + elsize - n, b + elsize - n, n);
730+
memcpy(b + elsize - n, tmp, n);
731+
elsize -= n;
732+
}
733+
}
734+
735+
static void insertsort(
736+
void *els, size_t nel, size_t elsize,
737+
git__sort_r_cmp cmp, void *payload)
738+
{
739+
uint8_t *base = els;
740+
uint8_t *end = base + nel * elsize;
741+
uint8_t *i, *j;
742+
743+
for (i = base + elsize; i < end; i += elsize)
744+
for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize)
745+
swap(j, j - elsize, elsize);
746+
}
747+
722748
void git__qsort_r(
723749
void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
724750
{
@@ -731,33 +757,10 @@ void git__qsort_r(
731757
git__qsort_r_glue glue = { cmp, payload };
732758
qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
733759
#else
734-
git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
760+
insertsort(els, nel, elsize, cmp, payload);
735761
#endif
736762
}
737763

738-
void git__insertsort_r(
739-
void *els, size_t nel, size_t elsize, void *swapel,
740-
git__sort_r_cmp cmp, void *payload)
741-
{
742-
uint8_t *base = els;
743-
uint8_t *end = base + nel * elsize;
744-
uint8_t *i, *j;
745-
bool freeswap = !swapel;
746-
747-
if (freeswap)
748-
swapel = git__malloc(elsize);
749-
750-
for (i = base + elsize; i < end; i += elsize)
751-
for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {
752-
memcpy(swapel, j, elsize);
753-
memcpy(j, j - elsize, elsize);
754-
memcpy(j - elsize, swapel, elsize);
755-
}
756-
757-
if (freeswap)
758-
git__free(swapel);
759-
}
760-
761764
/*
762765
* git__utf8_iterate is taken from the utf8proc project,
763766
* http://www.public-software-group.org/utf8proc

src/util.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,6 @@ extern void git__tsort_r(
137137
extern void git__qsort_r(
138138
void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
139139

140-
extern void git__insertsort_r(
141-
void *els, size_t nel, size_t elsize, void *swapel,
142-
git__sort_r_cmp cmp, void *payload);
143-
144140
/**
145141
* @param position If non-NULL, this will be set to the position where the
146142
* element is or would be inserted if not found.

tests/core/qsort.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include "clar_libgit2.h"
2+
3+
#define assert_sorted(a, cmp) \
4+
_assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp)
5+
6+
struct big_entries {
7+
char c[311];
8+
};
9+
10+
static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp)
11+
{
12+
int8_t *p = els;
13+
14+
git__qsort_r(p, n, elsize, cmp, NULL);
15+
while (n-- > 1) {
16+
cl_assert(cmp(p, p + elsize, NULL) <= 0);
17+
p += elsize;
18+
}
19+
}
20+
21+
static int cmp_big(const void *_a, const void *_b, void *payload)
22+
{
23+
const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b;
24+
GIT_UNUSED(payload);
25+
return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0;
26+
}
27+
28+
static int cmp_int(const void *_a, const void *_b, void *payload)
29+
{
30+
int a = *(const int *)_a, b = *(const int *)_b;
31+
GIT_UNUSED(payload);
32+
return (a < b) ? -1 : (a > b) ? +1 : 0;
33+
}
34+
35+
static int cmp_str(const void *_a, const void *_b, void *payload)
36+
{
37+
GIT_UNUSED(payload);
38+
return strcmp((const char *) _a, (const char *) _b);
39+
}
40+
41+
void test_core_qsort__array_with_single_entry(void)
42+
{
43+
int a[] = { 10 };
44+
assert_sorted(a, cmp_int);
45+
}
46+
47+
void test_core_qsort__array_with_equal_entries(void)
48+
{
49+
int a[] = { 4, 4, 4, 4 };
50+
assert_sorted(a, cmp_int);
51+
}
52+
53+
void test_core_qsort__sorted_array(void)
54+
{
55+
int a[] = { 1, 10 };
56+
assert_sorted(a, cmp_int);
57+
}
58+
59+
void test_core_qsort__unsorted_array(void)
60+
{
61+
int a[] = { 123, 9, 412938, 10, 234, 89 };
62+
assert_sorted(a, cmp_int);
63+
}
64+
65+
void test_core_qsort__sorting_strings(void)
66+
{
67+
char *a[] = { "foo", "bar", "baz" };
68+
assert_sorted(a, cmp_str);
69+
}
70+
71+
void test_core_qsort__sorting_big_entries(void)
72+
{
73+
struct big_entries a[5];
74+
75+
memset(&a, 0, sizeof(a));
76+
77+
memset(a[0].c, 'w', sizeof(a[0].c) - 1);
78+
memset(a[1].c, 'c', sizeof(a[1].c) - 1);
79+
memset(a[2].c, 'w', sizeof(a[2].c) - 1);
80+
memset(a[3].c, 'h', sizeof(a[3].c) - 1);
81+
memset(a[4].c, 'a', sizeof(a[4].c) - 1);
82+
83+
assert_sorted(a, cmp_big);
84+
85+
cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1);
86+
cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1);
87+
cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1);
88+
cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1);
89+
cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1);
90+
}

0 commit comments

Comments
 (0)