Cleanup and fixes

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-21 19:02:42 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-21 19:02:42 +0100
Commit 5d37541d63b5c1454dd424eb6f0bc4465629dade (patch)
-rw-r--r-- README.md 9
-rw-r--r-- examples/files.c 6
-rw-r--r-- nonstd.h 172
-rw-r--r-- tests.c 49
4 files changed, 140 insertions, 96 deletions
diff --git a/README.md b/README.md
...
158
    FREE(content); // Standard free (unless using arena)
158
    FREE(content); // Standard free (unless using arena)
159
}
159
}
160
  
160
  
161
// Or read directly into a string view or builder
161
// Or read directly into a string builder
162
stringv file_sv = read_entire_file_sv("config.ini");
162
stringb file_sb = read_entire_file_sb("config.ini");
163
// ... use file_sv ...
163
// ... use file_sb ...
164
FREE((void*)file_sv.data); // Note: read_entire_file_sv allocates the data
164
sb_free(&file_sb);
  
165
  
165
```
166
```
166
  
167
  
167
## Testing
168
## Testing
...
diff --git a/examples/files.c b/examples/files.c
...
23
		printf("Written test_sv.txt\n");
23
		printf("Written test_sv.txt\n");
24
	}
24
	}
25
  
25
  
26
	stringv sv_read = read_entire_file_sv("test_sv.txt");
  
27
	if (sv_read.data) {
  
28
		printf("Read sv: %.*s", (int)sv_read.length, sv_read.data);
  
29
		free((char *)sv_read.data);
  
30
	}
  
31
  
  
32
	// 3. usage with string builder
26
	// 3. usage with string builder
33
	stringb sb;
27
	stringb sb;
34
	sb_init(&sb, 0);
28
	sb_init(&sb, 0);
...
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
  
...