Good point. For unsigned char I get differences starting at 156 elements, here just the first few:
unsigned char nr=156 0 (155 -> 0) vs 256 (155 -> 0) unsigned char nr=157 0 (155 -> 0) vs 256 (155 -> 0) unsigned char nr=157 2 (156 -> 2) vs 258 (156 -> 2)
So the current code cuts the allocation size to 0 if you have an array of 155 and ask for more entries. That would cause a buffer overrun. With the patch ALLOC_GROW actually grows the buffer.
For signed char I see a different failure mode:
signed char nr=71 18446744073709551489 (70 -> -127) vs 129 (70 -> -127) signed char nr=72 18446744073709551489 (70 -> -127) vs 129 (70 -> -127) signed char nr=72 18446744073709551490 (71 -> -126) vs 130 (71 -> -126)
The current code tries to allocate (something close to) infinity, which would terminate the program. With the patch ALLOC_GROW actually grows the buffer.
I don't see this for int and unsigned, though. Weird C integer promotion rules hit us here, I guess. Just this, as you mentioned:
ALLOC_GROW1 unsigned nr=1787844770 1787844770 (1787844769 -> 1787844770) step too small, abort ALLOC_GROW1 int nr=1787844770 1787844770 (1787844769 -> 1787844770) step too small, abort
The demo code is now big and hairy enough to need its own tests, though. *snicker*
René
#include <limits.h> #include <stdbool.h> #include <stdio.h>
#define alloc_nr(x) (((x)+16)*3/2)
#define ALLOC_GROW1(x, nr, alloc) \
do { \
if ((nr) > alloc) { \
if (alloc_nr(alloc) < (nr)) \
alloc = (nr); \
else \
alloc = alloc_nr(alloc); \
x = alloc; \
} \
} while (0)static inline bool st_alloc_nr(size_t nr, size_t alloc, size_t *outp)
{
if (nr > alloc) {
size_t out = alloc_nr(alloc);
*outp = out < nr ? nr : out;
return true;
}
return false;
}#define ALLOC_GROW2(x, nr, alloc) \
do { \
size_t alloc_grow_new_alloc_; \
if (st_alloc_nr((nr), (alloc), &alloc_grow_new_alloc_)) { \
alloc = alloc_grow_new_alloc_; \
x = alloc_grow_new_alloc_; \
} \
} while (0)#define COMPARE(T, P, nr, alloc1, alloc_sz1, alloc2, alloc_sz2) do { \
T orig_alloc1 = alloc1, orig_alloc2 = alloc2; \
ALLOC_GROW1(alloc_sz1, nr, alloc1); \
ALLOC_GROW2(alloc_sz2, nr, alloc2); \
if (alloc_sz1 != alloc_sz2) \
printf(#T" nr="P" %zu ("P" -> "P") vs %zu ("P" -> "P")\n", \
nr, \
alloc_sz1, orig_alloc1, alloc1, \
alloc_sz2, orig_alloc2, alloc2); \
} while (0)#define COMPARE_ALL(T, MIN, MAX, P) do { \
for (T nr = 0;; nr++) { \
for (T alloc = MIN;; alloc++) { \
T alloc1 = alloc, alloc2 = alloc; \
size_t alloc_sz1 = alloc1, alloc_sz2 = alloc2; \
COMPARE(T, P, nr, alloc1, alloc_sz1, alloc2, alloc_sz2); \
if (alloc == MAX) \
break; \
} \
if (nr == MAX) \
break; \
} \
} while (0)#define COMPARE_GROWTH(T, MAX, P) do { \
T alloc1 = 0, alloc2 = 0; \
size_t alloc_sz1 = 0, alloc_sz2 = 0; \
for (T nr = 0;; nr++) { \
COMPARE(T, P, nr, alloc1, alloc_sz1, alloc2, alloc_sz2); \
if (nr == MAX) \
break; \
} \
} while (0)#define CHECK_GROWTH_ONE(T, MAX, P, ALLOC_GROW) do { \
T alloc = 0; \
size_t alloc_sz = 0; \
for (T nr = 0;; nr++) { \
T orig_alloc = alloc; \
size_t orig_alloc_sz = alloc_sz; \
ALLOC_GROW(alloc_sz, nr, alloc); \
if (alloc_sz < (size_t)nr) \
printf(#ALLOC_GROW" "#T" nr="P" %zu ("P" -> "P")" \
" too small\n", \
nr, alloc_sz, orig_alloc, alloc); \
if (alloc_sz > alloc_nr((size_t)nr)) \
printf(#ALLOC_GROW" "#T" nr="P" %zu ("P" -> "P")" \
" too big\n", \
nr, alloc_sz, orig_alloc, alloc); \
if (alloc_sz > orig_alloc_sz && \
alloc_sz - alloc_sz / 3 < orig_alloc_sz) { \
printf(#ALLOC_GROW" "#T" nr="P" %zu ("P" -> "P")" \
" step too small, abort\n", \
nr, alloc_sz, orig_alloc, alloc); \
break; \
} \
if (nr == MAX) \
break; \
} \
} while (0)#define CHECK_GROWTH(T, MAX, P) do { \
CHECK_GROWTH_ONE(T, MAX, P, ALLOC_GROW1); \
CHECK_GROWTH_ONE(T, MAX, P, ALLOC_GROW2); \
} while (0)int main(int argc, char **argv)
{
COMPARE_ALL(unsigned char, 0, UCHAR_MAX, "%hhu");
COMPARE_ALL(signed char, 0, SCHAR_MAX, "%hhd");
COMPARE_GROWTH(short, SHRT_MAX, "%hd");
CHECK_GROWTH(short, SHRT_MAX, "%hd");
CHECK_GROWTH(unsigned short, USHRT_MAX, "%hu");
CHECK_GROWTH(unsigned, UINT_MAX, "%u");
CHECK_GROWTH(int, INT_MAX, "%d");
return 0;
}