Skip to content

Commit 45636bd

Browse files
authored
Harden C11 allocator and IO callbacks (tinyobjloader#432)
1 parent 477cbdf commit 45636bd

7 files changed

Lines changed: 366 additions & 49 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,11 @@ Properties:
413413
(no `malloc` / `stdio` / `strtod` / `math.h`): supply your own allocator and a
414414
byte buffer. Define `TOBJ_NO_LIBC` (a.k.a. `TOBJ_FREESTANDING`) for that mode,
415415
or enable libc-backed conveniences with `TOBJ_ENABLE_FILE_IO`.
416+
* **Pluggable allocation and I/O.** `tobj_allocator` carries bounded
417+
size/alignment requests (`max_alloc_size` is optional) and supports custom
418+
`alloc`/`calloc`/`free` backends; libc builds default to malloc/calloc/free.
419+
`tobj_load_obj_from_io_*` reads through caller-supplied byte callbacks for
420+
freestanding streams.
416421
* **Dual precision.** `float` (`_f`) and `double` (`_d`) data structures and
417422
entry points coexist in the same build (e.g. `tobj_scene_f` / `tobj_scene_d`).
418423
* **Robust tessellation.** Survives degenerate / concave / collinear /
@@ -456,10 +461,11 @@ tobj_diag_free(&diag, NULL);
456461
```
457462

458463
Use the `_d` variants for double precision, `tobj_load_obj_from_memory_f` for
459-
the freestanding / in-memory path, and `tobj_load_obj_with_callbacks_f` for
460-
streaming. Build with CMake (`-DTINYOBJLOADER_BUILD_C_LIBRARY=ON`, the default;
461-
options `TINYOBJLOADER_C_ENABLE_FILE_IO` / `MMAP` / `SIMD` / `MULTITHREADING`)
462-
or simply compile `tiny_obj_c.c` and `tobj_tess.c`. C tests live under `tests/`
464+
the freestanding / in-memory path, `tobj_load_obj_from_io_f` for custom byte
465+
input, and `tobj_load_obj_with_callbacks_f` for parse-event callbacks. Build
466+
with CMake (`-DTINYOBJLOADER_BUILD_C_LIBRARY=ON`, the default; options
467+
`TINYOBJLOADER_C_ENABLE_FILE_IO` / `MMAP` / `SIMD` / `MULTITHREADING`) or simply
468+
compile `tiny_obj_c.c` and `tobj_tess.c`. C tests live under `tests/`
463469
(`make check_c`).
464470

465471
## Python binding

‎tests/tester_c.c‎

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,48 @@ static const char *load_string_f(const char *obj, tobj_scene_f *sc,
4242
return tobj_result_string(r);
4343
}
4444

45+
typedef struct mem_io {
46+
const uint8_t *data;
47+
size_t len;
48+
size_t pos;
49+
size_t chunk;
50+
int closed;
51+
} mem_io;
52+
53+
static tobj_result mem_io_read(void *ud, uint8_t *dst, size_t dst_size,
54+
size_t *bytes_read) {
55+
mem_io *m = (mem_io *)ud;
56+
size_t left = m->len - m->pos;
57+
size_t n = left < dst_size ? left : dst_size;
58+
if (m->chunk && n > m->chunk) n = m->chunk;
59+
if (n) memcpy(dst, m->data + m->pos, n);
60+
m->pos += n;
61+
*bytes_read = n;
62+
return TOBJ_OK;
63+
}
64+
65+
static void mem_io_close(void *ud) { ((mem_io *)ud)->closed = 1; }
66+
67+
static tobj_result io_overreport_read(void *ud, uint8_t *dst, size_t dst_size,
68+
size_t *bytes_read) {
69+
(void)ud;
70+
if (dst_size) dst[0] = 'x';
71+
*bytes_read = dst_size + 1;
72+
return TOBJ_OK;
73+
}
74+
75+
static tobj_result io_error_read(void *ud, uint8_t *dst, size_t dst_size,
76+
size_t *bytes_read) {
77+
(void)ud;
78+
(void)dst;
79+
(void)dst_size;
80+
*bytes_read = 0;
81+
return TOBJ_ERR_IO;
82+
}
83+
4584
/* ---- corpus ------------------------------------------------------------ */
4685

47-
/* Only files committed to the repo (CI checks out a clean tree; sandbox/*.obj
86+
/* Only files committed to the repo (CI checks out a clean tree; sandbox files
4887
* are local scratch files and are intentionally not used here). The
4988
* pathological-geometry triangulation cases are covered directly by
5089
* tests/tess_tester.c. */
@@ -205,6 +244,143 @@ static void test_mtl_standalone(void) {
205244
tobj_material_list_free_f(&ml);
206245
}
207246

247+
static void test_io_callbacks(void) {
248+
const char *obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
249+
mem_io mio;
250+
mio.data = (const uint8_t *)obj;
251+
mio.len = strlen(obj);
252+
mio.pos = 0;
253+
mio.chunk = 5;
254+
mio.closed = 0;
255+
tobj_io_callbacks io;
256+
io.read = mem_io_read;
257+
io.close = mem_io_close;
258+
io.user_data = &mio;
259+
tobj_load_config cfg = tobj_default_config();
260+
tobj_scene_f sc;
261+
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
262+
TEST_CHECK(r == TOBJ_OK);
263+
TEST_CHECK(mio.closed == 1);
264+
TEST_CHECK(sc.num_shapes == 1);
265+
TEST_CHECK(sc.shapes[0].mesh.num_indices == 3);
266+
tobj_scene_free_f(&sc);
267+
}
268+
269+
static void test_allocator_limit_failure(void) {
270+
const char *obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
271+
tobj_load_config cfg = tobj_default_config();
272+
cfg.allocator = tobj_default_allocator();
273+
cfg.allocator.max_alloc_size = 16;
274+
tobj_scene_f sc;
275+
tobj_result r =
276+
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
277+
NULL);
278+
TEST_CHECK(r == TOBJ_ERR_ALLOC);
279+
TEST_CHECK(sc.num_shapes == 0);
280+
}
281+
282+
static void test_input_limit_failure(void) {
283+
const char *obj = "v 0 0 0\n";
284+
tobj_load_config cfg = tobj_default_config();
285+
cfg.max_input_bytes = 4;
286+
tobj_scene_f sc;
287+
tobj_result r =
288+
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
289+
NULL);
290+
TEST_CHECK(r == TOBJ_ERR_LIMIT_EXCEEDED);
291+
}
292+
293+
static void test_io_input_limit_exact_and_over(void) {
294+
const char *obj = "v 0 0 0\n";
295+
tobj_load_config cfg = tobj_default_config();
296+
cfg.max_input_bytes = strlen(obj);
297+
298+
mem_io exact;
299+
exact.data = (const uint8_t *)obj;
300+
exact.len = strlen(obj);
301+
exact.pos = 0;
302+
exact.chunk = 3;
303+
exact.closed = 0;
304+
tobj_io_callbacks io;
305+
io.read = mem_io_read;
306+
io.close = mem_io_close;
307+
io.user_data = &exact;
308+
tobj_scene_f sc;
309+
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
310+
TEST_CHECK(r == TOBJ_OK);
311+
TEST_CHECK(exact.closed == 1);
312+
tobj_scene_free_f(&sc);
313+
314+
const char *too_big = "v 0 0 0\n# extra\n";
315+
mem_io over;
316+
over.data = (const uint8_t *)too_big;
317+
over.len = strlen(too_big);
318+
over.pos = 0;
319+
over.chunk = 0;
320+
over.closed = 0;
321+
io.user_data = &over;
322+
r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
323+
TEST_CHECK(r == TOBJ_ERR_LIMIT_EXCEEDED);
324+
TEST_CHECK(over.closed == 1);
325+
TEST_CHECK(sc.num_shapes == 0);
326+
}
327+
328+
static void test_io_callback_errors(void) {
329+
tobj_io_callbacks io;
330+
io.close = mem_io_close;
331+
332+
mem_io state;
333+
state.data = NULL;
334+
state.len = 0;
335+
state.pos = 0;
336+
state.chunk = 0;
337+
state.closed = 0;
338+
io.user_data = &state;
339+
io.read = io_overreport_read;
340+
tobj_scene_f sc;
341+
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, NULL, NULL);
342+
TEST_CHECK(r == TOBJ_ERR_IO);
343+
TEST_CHECK(state.closed == 1);
344+
345+
state.closed = 0;
346+
io.read = io_error_read;
347+
r = tobj_load_obj_from_io_f(&sc, &io, NULL, NULL);
348+
TEST_CHECK(r == TOBJ_ERR_IO);
349+
TEST_CHECK(state.closed == 1);
350+
}
351+
352+
static void test_invalid_allocator_rejected(void) {
353+
const char *obj = "v 0 0 0\n";
354+
tobj_load_config cfg = tobj_default_config();
355+
cfg.allocator = tobj_default_allocator();
356+
cfg.allocator.free = NULL;
357+
tobj_scene_f sc;
358+
tobj_result r =
359+
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
360+
NULL);
361+
TEST_CHECK(r == TOBJ_ERR_INVALID_ARG);
362+
TEST_CHECK(sc.num_shapes == 0);
363+
}
364+
365+
static void test_allocator_without_calloc_or_realloc(void) {
366+
const char *obj =
367+
"v 0 0 0\nv 1 0 0\nv 2 0 0\nv 3 0 0\nv 4 0 0\n"
368+
"v 5 0 0\nv 6 0 0\nv 7 0 0\nv 8 0 0\n"
369+
"f 1 2 3\nf 4 5 6\nf 7 8 9\n";
370+
tobj_load_config cfg = tobj_default_config();
371+
cfg.allocator = tobj_default_allocator();
372+
cfg.allocator.calloc = NULL;
373+
cfg.allocator.realloc = NULL;
374+
tobj_scene_f sc;
375+
tobj_result r =
376+
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
377+
NULL);
378+
TEST_CHECK(r == TOBJ_OK);
379+
TEST_CHECK(sc.attrib.vertices.count == 27);
380+
TEST_CHECK(sc.shapes[0].mesh.num_indices == 9);
381+
tobj_scene_free_f(&sc);
382+
}
383+
208384
TEST_LIST = {
209385
{"corpus_loads", test_corpus_loads},
210386
{"both_precisions", test_both_precisions},
@@ -218,4 +394,12 @@ TEST_LIST = {
218394
{"degenerate_face_skipped", test_degenerate_face_skipped},
219395
{"empty_and_garbage", test_empty_and_garbage},
220396
{"mtl_standalone", test_mtl_standalone},
397+
{"io_callbacks", test_io_callbacks},
398+
{"allocator_limit_failure", test_allocator_limit_failure},
399+
{"input_limit_failure", test_input_limit_failure},
400+
{"io_input_limit_exact_and_over", test_io_input_limit_exact_and_over},
401+
{"io_callback_errors", test_io_callback_errors},
402+
{"invalid_allocator_rejected", test_invalid_allocator_rejected},
403+
{"allocator_without_calloc_or_realloc",
404+
test_allocator_without_calloc_or_realloc},
221405
{NULL, NULL}};

‎tests/tester_c_freestanding.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ static const char g_obj[] =
4141
int main(void) {
4242
tobj_allocator a;
4343
a.alloc = fs_alloc;
44+
a.calloc = 0;
4445
a.realloc = fs_realloc;
4546
a.free = fs_free;
47+
a.max_alloc_size = 0;
4648
a.user_data = (void *)0;
4749

4850
tobj_load_config cfg = tobj_default_config();

0 commit comments

Comments
 (0)