|
diff --git a/nonstd.h b/nonstd.h
|
| ... |
| 35 |
for (size_t _i_##var = 0, _n_##var = countof(array); \ |
35 |
for (size_t _i_##var = 0, _n_##var = countof(array); \ |
| 36 |
_i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var) |
36 |
_i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var) |
| 37 |
|
37 |
|
| 38 |
#define ALLOC(type, n) ((type *)malloc((n) * sizeof(type))) |
38 |
#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n))) |
| 39 |
#define REALLOC(ptr, type, n) ((type *)realloc((ptr), (n) * sizeof(type))) |
39 |
#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n))) |
| 40 |
#define FREE(ptr) \ |
40 |
#define FREE(ptr) \ |
| 41 |
do { \ |
41 |
do { \ |
| 42 |
free(ptr); \ |
42 |
free(ptr); \ |
| 43 |
ptr = NULL; \ |
43 |
ptr = NULL; \ |
| 44 |
} while (0) |
44 |
} while (0) |
|
|
45 |
|
|
|
46 |
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count); |
|
|
47 |
NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count); |
| 45 |
|
48 |
|
| 46 |
#define MIN(a, b) ((a) < (b) ? (a) : (b)) |
49 |
#define MIN(a, b) ((a) < (b) ? (a) : (b)) |
| 47 |
#define MAX(a, b) ((a) > (b) ? (a) : (b)) |
50 |
#define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| ... |
| 131 |
(arr).capacity = 0; \ |
134 |
(arr).capacity = 0; \ |
| 132 |
} while (0) |
135 |
} while (0) |
| 133 |
|
136 |
|
| 134 |
#define array_ensure(arr, additional) \ |
137 |
#define array_ensure(arr, additional) \ |
| 135 |
do { \ |
138 |
do { \ |
| 136 |
size_t _needed = (arr).length + (additional); \ |
139 |
size_t _needed = (arr).length + (additional); \ |
| 137 |
if (_needed > (arr).capacity) { \ |
140 |
if (_needed > (arr).capacity) { \ |
| 138 |
size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \ |
141 |
size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \ |
| 139 |
while (_new_cap < _needed) { \ |
142 |
while (_new_cap < _needed) { \ |
| 140 |
_new_cap *= 2; \ |
143 |
if (_new_cap > SIZE_MAX / 2) { \ |
| 141 |
} \ |
144 |
_new_cap = SIZE_MAX; \ |
| 142 |
(arr).data = \ |
145 |
break; \ |
| 143 |
REALLOC((arr).data, __typeof__(*(arr).data), _new_cap); \ |
146 |
} \ |
| 144 |
(arr).capacity = _new_cap; \ |
147 |
_new_cap *= 2; \ |
| 145 |
} \ |
148 |
} \ |
|
|
149 |
if (_new_cap < _needed) { /* Overflow or OOM */ \ |
|
|
150 |
break; \ |
|
|
151 |
} \ |
|
|
152 |
void *_new_data = \ |
|
|
153 |
safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \ |
|
|
154 |
if (_new_data) { \ |
|
|
155 |
(arr).data = _new_data; \ |
|
|
156 |
(arr).capacity = _new_cap; \ |
|
|
157 |
} \ |
|
|
158 |
} \ |
| 146 |
} while (0) |
159 |
} while (0) |
| 147 |
|
160 |
|
| 148 |
#define array_push(arr, value) \ |
161 |
#define array_push(arr, value) \ |
| 149 |
do { \ |
162 |
do { \ |
| 150 |
array_ensure((arr), 1); \ |
163 |
array_ensure((arr), 1); \ |
| 151 |
(arr).data[(arr).length++] = (value); \ |
164 |
if ((arr).length < (arr).capacity) { \ |
|
|
165 |
(arr).data[(arr).length++] = (value); \ |
|
|
166 |
} \ |
| 152 |
} while (0) |
167 |
} while (0) |
| 153 |
|
168 |
|
| 154 |
#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0) |
169 |
#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0) |
| ... |
| 162 |
} \ |
177 |
} \ |
| 163 |
} while (0) |
178 |
} while (0) |
| 164 |
|
179 |
|
| 165 |
#define array_insert(arr, index, value) \ |
180 |
#define array_insert(arr, index, value) \ |
| 166 |
do { \ |
181 |
do { \ |
| 167 |
if ((index) <= (arr).length) { \ |
182 |
if ((index) <= (arr).length) { \ |
| 168 |
array_ensure((arr), 1); \ |
183 |
array_ensure((arr), 1); \ |
| 169 |
for (size_t _i = (arr).length; _i > (index); --_i) { \ |
184 |
if ((arr).length < (arr).capacity) { \ |
| 170 |
(arr).data[_i] = (arr).data[_i - 1]; \ |
185 |
for (size_t _i = (arr).length; _i > (index); --_i) { \ |
| 171 |
} \ |
186 |
(arr).data[_i] = (arr).data[_i - 1]; \ |
| 172 |
(arr).data[index] = (value); \ |
187 |
} \ |
| 173 |
(arr).length++; \ |
188 |
(arr).data[index] = (value); \ |
| 174 |
} \ |
189 |
(arr).length++; \ |
|
|
190 |
} \ |
|
|
191 |
} \ |
| 175 |
} while (0) |
192 |
} while (0) |
| 176 |
|
193 |
|
| 177 |
#define array_remove(arr, index) \ |
194 |
#define array_remove(arr, index) \ |
| ... |
| 189 |
(arr).length = 0; \ |
206 |
(arr).length = 0; \ |
| 190 |
} while (0) |
207 |
} while (0) |
| 191 |
|
208 |
|
| 192 |
#define array_reserve(arr, new_capacity) \ |
209 |
#define array_reserve(arr, new_capacity) \ |
| 193 |
do { \ |
210 |
do { \ |
| 194 |
if ((new_capacity) > (arr).capacity) { \ |
211 |
if ((new_capacity) > (arr).capacity) { \ |
| 195 |
(arr).data = \ |
212 |
void *_new_data = \ |
| 196 |
REALLOC((arr).data, __typeof__(*(arr).data), (new_capacity)); \ |
213 |
safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \ |
| 197 |
(arr).capacity = (new_capacity); \ |
214 |
if (_new_data) { \ |
| 198 |
} \ |
215 |
(arr).data = _new_data; \ |
|
|
216 |
(arr).capacity = (new_capacity); \ |
|
|
217 |
} \ |
|
|
218 |
} \ |
| 199 |
} while (0) |
219 |
} while (0) |
| 200 |
|
220 |
|
| 201 |
#define array_foreach(arr, var) \ |
221 |
#define array_foreach(arr, var) \ |
| ... |
| 204 |
++_i_##var) |
224 |
++_i_##var) |
| 205 |
|
225 |
|
| 206 |
#define array_foreach_idx(arr, var, index) \ |
226 |
#define array_foreach_idx(arr, var, index) \ |
| 207 |
for (size_t index = 0; \ |
227 |
for (size_t index = 0; \ |
| 208 |
index < (arr).length && ((var) = (arr).data[index], 1); ++index) |
228 |
index < (arr).length && ((var) = (arr).data[index], 1); ++index) |
| 209 |
|
229 |
|
| 210 |
// Arena - block-based memory allocator |
230 |
// Arena - block-based memory allocator |
| ... |
| 226 |
// File I/O helpers |
246 |
// File I/O helpers |
| 227 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); |
247 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); |
| 228 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); |
248 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); |
| 229 |
NONSTD_DEF stringv read_entire_file_sv(const char *filepath); |
249 |
// read_entire_file_sv removed for security (ownership confusion) |
| 230 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath); |
250 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath); |
| 231 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); |
251 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); |
| 232 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); |
252 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); |
| ... |
| 234 |
#endif // NONSTD_H |
254 |
#endif // NONSTD_H |
| 235 |
|
255 |
|
| 236 |
#ifdef NONSTD_IMPLEMENTATION |
256 |
#ifdef NONSTD_IMPLEMENTATION |
|
|
257 |
|
|
|
258 |
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) { |
|
|
259 |
if (count != 0 && item_size > SIZE_MAX / count) { |
|
|
260 |
return NULL; |
|
|
261 |
} |
|
|
262 |
return malloc(item_size * count); |
|
|
263 |
} |
|
|
264 |
|
|
|
265 |
NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) { |
|
|
266 |
if (count != 0 && item_size > SIZE_MAX / count) { |
|
|
267 |
return NULL; |
|
|
268 |
} |
|
|
269 |
return realloc(ptr, item_size * count); |
|
|
270 |
} |
| 237 |
|
271 |
|
| 238 |
NONSTD_DEF stringv sv_from_cstr(const char *s) { |
272 |
NONSTD_DEF stringv sv_from_cstr(const char *s) { |
| 239 |
return (stringv){.data = s, .length = s ? strlen(s) : 0}; |
273 |
return (stringv){.data = s, .length = s ? strlen(s) : 0}; |
| ... |
| 285 |
|
319 |
|
| 286 |
NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) { |
320 |
NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) { |
| 287 |
size_t needed = sb->length + additional + 1; |
321 |
size_t needed = sb->length + additional + 1; |
| 288 |
if (needed > sb->capacity) { |
322 |
size_t new_cap = sb->capacity; |
| 289 |
while (sb->capacity < needed) { |
323 |
|
| 290 |
sb->capacity *= 2; |
324 |
if (needed > new_cap) { |
|
|
325 |
while (new_cap < needed) { |
|
|
326 |
if (new_cap > SIZE_MAX / 2) { |
|
|
327 |
new_cap = SIZE_MAX; |
|
|
328 |
break; |
|
|
329 |
} |
|
|
330 |
new_cap *= 2; |
|
|
331 |
} |
|
|
332 |
if (new_cap < needed) |
|
|
333 |
return; // Overflow |
|
|
334 |
|
|
|
335 |
char *new_data = safe_realloc(sb->data, sizeof(char), new_cap); |
|
|
336 |
if (new_data) { |
|
|
337 |
sb->data = new_data; |
|
|
338 |
sb->capacity = new_cap; |
| 291 |
} |
339 |
} |
| 292 |
sb->data = REALLOC(sb->data, char, sb->capacity); |
|
|
| 293 |
} |
340 |
} |
| 294 |
} |
341 |
} |
| 295 |
|
342 |
|
| ... |
| 299 |
} |
346 |
} |
| 300 |
size_t slength = strlen(s); |
347 |
size_t slength = strlen(s); |
| 301 |
sb_ensure(sb, slength); |
348 |
sb_ensure(sb, slength); |
| 302 |
memcpy(sb->data + sb->length, s, slength); |
349 |
if (sb->length + slength + 1 <= sb->capacity) { |
| 303 |
sb->length += slength; |
350 |
memcpy(sb->data + sb->length, s, slength); |
| 304 |
sb->data[sb->length] = '\0'; |
351 |
sb->length += slength; |
|
|
352 |
sb->data[sb->length] = '\0'; |
|
|
353 |
} |
| 305 |
} |
354 |
} |
| 306 |
|
355 |
|
| 307 |
NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) { |
356 |
NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) { |
| ... |
| 309 |
return; |
358 |
return; |
| 310 |
} |
359 |
} |
| 311 |
sb_ensure(sb, sv.length); |
360 |
sb_ensure(sb, sv.length); |
| 312 |
memcpy(sb->data + sb->length, sv.data, sv.length); |
361 |
if (sb->length + sv.length + 1 <= sb->capacity) { |
| 313 |
sb->length += sv.length; |
362 |
memcpy(sb->data + sb->length, sv.data, sv.length); |
| 314 |
sb->data[sb->length] = '\0'; |
363 |
sb->length += sv.length; |
|
|
364 |
sb->data[sb->length] = '\0'; |
|
|
365 |
} |
| 315 |
} |
366 |
} |
| 316 |
|
367 |
|
| 317 |
NONSTD_DEF void sb_append_char(stringb *sb, char c) { |
368 |
NONSTD_DEF void sb_append_char(stringb *sb, char c) { |
| 318 |
sb_ensure(sb, 1); |
369 |
sb_ensure(sb, 1); |
| 319 |
sb->data[sb->length++] = c; |
370 |
if (sb->length + 2 <= sb->capacity) { |
| 320 |
sb->data[sb->length] = '\0'; |
371 |
sb->data[sb->length++] = c; |
|
|
372 |
sb->data[sb->length] = '\0'; |
|
|
373 |
} |
| 321 |
} |
374 |
} |
| 322 |
|
375 |
|
| 323 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb) { |
376 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb) { |
| ... |
| 343 |
size_t align = sizeof(void *); |
396 |
size_t align = sizeof(void *); |
| 344 |
uintptr_t current = (uintptr_t)a->ptr; |
397 |
uintptr_t current = (uintptr_t)a->ptr; |
| 345 |
uintptr_t aligned = (current + align - 1) & ~(align - 1); |
398 |
uintptr_t aligned = (current + align - 1) & ~(align - 1); |
| 346 |
uintptr_t available = (uintptr_t)a->end - aligned; |
399 |
uintptr_t end = (uintptr_t)a->end; |
| 347 |
|
400 |
|
| 348 |
if (available < size) { |
401 |
// Check for overflow (aligned wrapped around) or out of bounds (aligned >= end) |
|
|
402 |
// or not enough space ((end - aligned) < size) |
|
|
403 |
if (aligned < current || aligned >= end || (end - aligned) < size) { |
| 349 |
arena_grow(a, size); |
404 |
arena_grow(a, size); |
| 350 |
current = (uintptr_t)a->ptr; |
405 |
current = (uintptr_t)a->ptr; |
| 351 |
aligned = (current + align - 1) & ~(align - 1); |
406 |
aligned = (current + align - 1) & ~(align - 1); |
|
|
407 |
end = (uintptr_t)a->end; |
|
|
408 |
} |
|
|
409 |
|
|
|
410 |
// Double check after grow (in case grow failed or size is just too huge) |
|
|
411 |
if (aligned < current || aligned >= end || (end - aligned) < size) { |
|
|
412 |
return NULL; |
| 352 |
} |
413 |
} |
| 353 |
|
414 |
|
| 354 |
a->ptr = (char *)(aligned + size); |
415 |
a->ptr = (char *)(aligned + size); |
| ... |
| 413 |
return written == size; |
474 |
return written == size; |
| 414 |
} |
475 |
} |
| 415 |
|
476 |
|
| 416 |
NONSTD_DEF stringv read_entire_file_sv(const char *filepath) { |
477 |
// read_entire_file_sv removed |
| 417 |
size_t size = 0; |
|
|
| 418 |
char *data = read_entire_file(filepath, &size); |
|
|
| 419 |
if (!data) { |
|
|
| 420 |
return (stringv){0}; |
|
|
| 421 |
} |
|
|
| 422 |
return (stringv){.data = data, .length = size}; |
|
|
| 423 |
} |
|
|
| 424 |
|
478 |
|
| 425 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { |
479 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { |
| 426 |
size_t size = 0; |
480 |
size_t size = 0; |
| ... |
|
diff --git a/tests.c b/tests.c
|
| ... |
| 784 |
arena_free(&a); |
784 |
arena_free(&a); |
| 785 |
} |
785 |
} |
| 786 |
|
786 |
|
|
|
787 |
MU_TEST(test_arena_safety) { |
|
|
788 |
Arena a = arena_make(); |
|
|
789 |
|
|
|
790 |
// Test 1: Integer overflow in allocation should be caught by safe_alloc |
|
|
791 |
// Requesting 2 items of size (SIZE_MAX/2 + 1) should fail |
|
|
792 |
// This tests ns_safe_malloc directly or via macro if we exposed it, |
|
|
793 |
// but arena_alloc calls ensure/grow which calls ALLOC(char, size). |
|
|
794 |
// To test ALLOC overflow: ALLOC(char, SIZE_MAX) is just 1*SIZE_MAX. |
|
|
795 |
// We need a way to trigger safe_alloc failure. |
|
|
796 |
|
|
|
797 |
// Let's test the underlying safe allocator directly since we exposed it in nonstd.h |
|
|
798 |
void *p = safe_malloc(SIZE_MAX / 2 + 100, 2); |
|
|
799 |
mu_check(p == NULL); |
|
|
800 |
|
|
|
801 |
// Test 2: Ensure normal allocations still work |
|
|
802 |
void *p2 = arena_alloc(&a, 100); |
|
|
803 |
mu_check(p2 != NULL); |
|
|
804 |
|
|
|
805 |
arena_free(&a); |
|
|
806 |
} |
|
|
807 |
|
| 787 |
// File I/O tests |
808 |
// File I/O tests |
| 788 |
MU_TEST(test_file_io_basic) { |
809 |
MU_TEST(test_file_io_basic) { |
| 789 |
const char *filename = "test_io_basic.txt"; |
810 |
const char *filename = "test_io_basic.txt"; |
| ... |
| 804 |
remove(filename); |
825 |
remove(filename); |
| 805 |
} |
826 |
} |
| 806 |
|
827 |
|
| 807 |
MU_TEST(test_file_io_sv) { |
|
|
| 808 |
const char *filename = "test_io_sv.txt"; |
|
|
| 809 |
stringv sv = sv_from_cstr("Hello from sv!"); |
|
|
| 810 |
|
|
|
| 811 |
// Write |
|
|
| 812 |
mu_check(write_file_sv(filename, sv)); |
|
|
| 813 |
|
|
|
| 814 |
// Read |
|
|
| 815 |
stringv read_sv = read_entire_file_sv(filename); |
|
|
| 816 |
mu_check(read_sv.data != NULL); |
|
|
| 817 |
mu_assert_int_eq(sv.length, read_sv.length); |
|
|
| 818 |
mu_check(sv_equals(sv, read_sv)); |
|
|
| 819 |
|
|
|
| 820 |
// The data returned by read_entire_file_sv is malloc'd and needs freeing |
|
|
| 821 |
// We cast away const to free it since we know it was allocated |
|
|
| 822 |
free((char *)read_sv.data); |
|
|
| 823 |
remove(filename); |
|
|
| 824 |
} |
|
|
| 825 |
|
|
|
| 826 |
MU_TEST(test_file_io_sb) { |
828 |
MU_TEST(test_file_io_sb) { |
| 827 |
const char *filename = "test_io_sb.txt"; |
829 |
const char *filename = "test_io_sb.txt"; |
| 828 |
stringb sb; |
830 |
stringb sb; |
| ... |
| 847 |
size_t len; |
849 |
size_t len; |
| 848 |
char *content = read_entire_file("non_existent_file.txt", &len); |
850 |
char *content = read_entire_file("non_existent_file.txt", &len); |
| 849 |
mu_check(content == NULL); |
851 |
mu_check(content == NULL); |
| 850 |
} |
|
|
| 851 |
|
|
|
| 852 |
MU_TEST(test_file_io_read_missing_sv) { |
|
|
| 853 |
stringv sv = read_entire_file_sv("non_existent_file.txt"); |
|
|
| 854 |
mu_check(sv.data == NULL); |
|
|
| 855 |
mu_assert_int_eq(0, sv.length); |
|
|
| 856 |
} |
852 |
} |
| 857 |
|
853 |
|
| 858 |
MU_TEST(test_file_io_read_missing_sb) { |
854 |
MU_TEST(test_file_io_read_missing_sb) { |
| ... |
| 953 |
RUN_TEST_WITH_NAME(test_arena_basic); |
949 |
RUN_TEST_WITH_NAME(test_arena_basic); |
| 954 |
RUN_TEST_WITH_NAME(test_arena_growth); |
950 |
RUN_TEST_WITH_NAME(test_arena_growth); |
| 955 |
RUN_TEST_WITH_NAME(test_arena_alignment); |
951 |
RUN_TEST_WITH_NAME(test_arena_alignment); |
|
|
952 |
RUN_TEST_WITH_NAME(test_arena_safety); |
| 956 |
} |
953 |
} |
| 957 |
|
954 |
|
| 958 |
MU_TEST_SUITE(test_suite_files) { |
955 |
MU_TEST_SUITE(test_suite_files) { |
| 959 |
printf("\n[File I/O Tests]\n"); |
956 |
printf("\n[File I/O Tests]\n"); |
| 960 |
RUN_TEST_WITH_NAME(test_file_io_basic); |
957 |
RUN_TEST_WITH_NAME(test_file_io_basic); |
| 961 |
RUN_TEST_WITH_NAME(test_file_io_sv); |
|
|
| 962 |
RUN_TEST_WITH_NAME(test_file_io_sb); |
958 |
RUN_TEST_WITH_NAME(test_file_io_sb); |
| 963 |
RUN_TEST_WITH_NAME(test_file_io_read_missing); |
959 |
RUN_TEST_WITH_NAME(test_file_io_read_missing); |
| 964 |
RUN_TEST_WITH_NAME(test_file_io_read_missing_sv); |
|
|
| 965 |
RUN_TEST_WITH_NAME(test_file_io_read_missing_sb); |
960 |
RUN_TEST_WITH_NAME(test_file_io_read_missing_sb); |
| 966 |
} |
961 |
} |
| 967 |
|
962 |
|
| ... |