From 34a166cdac6ec3145346d5c3df103dababb5ed5c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 1 Jun 2026 13:44:47 +0900 Subject: [PATCH] Complete freestanding v3 C JSON conversion tests --- CMakeLists.txt | 42 +++ tests/Makefile | 8 +- tests/tester_v3_c.c | 563 ++++++++++++++++++++++++++++ tests/tester_v3_freestanding.c | 75 ++++ tests/tester_v3_json_c.c | 165 +++++++++ tiny_gltf_v3.c | 365 ++++++++++++------ tiny_gltf_v3.h | 30 ++ tinygltf_json_c.h | 649 +++++++++++++++++++++++++++------ 8 files changed, 1667 insertions(+), 230 deletions(-) create mode 100644 tests/tester_v3_freestanding.c create mode 100644 tests/tester_v3_json_c.c diff --git a/CMakeLists.txt b/CMakeLists.txt index dc97c29..8d9248d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,49 @@ if (TINYGLTF_BUILD_TESTS) C_STANDARD_REQUIRED ON C_EXTENSIONS OFF ) + target_compile_definitions(tester_v3_c PRIVATE TINYGLTF3_ENABLE_FS) add_test(NAME tester_v3_c COMMAND tester_v3_c WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + + add_executable(tester_v3_c_v1port + tests/tester_v3_c_v1port.c + tiny_gltf_v3.c + ) + target_include_directories(tester_v3_c_v1port PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/tests + ) + set_target_properties(tester_v3_c_v1port PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + ) + target_compile_definitions(tester_v3_c_v1port PRIVATE TINYGLTF3_ENABLE_FS) + add_test(NAME tester_v3_c_v1port COMMAND tester_v3_c_v1port WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + + add_executable(tester_v3_json_c tests/tester_v3_json_c.c) + target_include_directories(tester_v3_json_c PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + set_target_properties(tester_v3_json_c PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + ) + add_test(NAME tester_v3_json_c COMMAND tester_v3_json_c WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + + add_executable(tester_v3_freestanding tests/tester_v3_freestanding.c) + target_include_directories(tester_v3_freestanding PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + set_target_properties(tester_v3_freestanding PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + ) + if (CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(tester_v3_freestanding PRIVATE -ffreestanding) + endif() + add_test(NAME tester_v3_freestanding COMMAND tester_v3_freestanding WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) endif (TINYGLTF_BUILD_TESTS) # diff --git a/tests/Makefile b/tests/Makefile index 5b96996..7f161fb 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,7 +1,7 @@ # Use this for strict compilation check(will work on clang 3.8+) #EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -DTINYGLTF_APPLY_CLANG_WEVERYTHING -all: ../tiny_gltf.h tester_v3_c tester_v3_c_v1port +all: ../tiny_gltf.h tester_v3_c tester_v3_c_v1port tester_v3_json_c tester_v3_freestanding clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc clang++ -DTINYGLTF_USE_CUSTOM_JSON -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_intensive_customjson tester_intensive_customjson.cc @@ -11,3 +11,9 @@ tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_ tester_v3_c_v1port: tester_v3_c_v1port.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h clang -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS -o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c + +tester_v3_json_c: tester_v3_json_c.c ../tinygltf_json_c.h + clang -I../ -std=c11 -g -O0 -o tester_v3_json_c tester_v3_json_c.c + +tester_v3_freestanding: tester_v3_freestanding.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h + clang -I../ -std=c11 -ffreestanding -g -O0 -o tester_v3_freestanding tester_v3_freestanding.c diff --git a/tests/tester_v3_c.c b/tests/tester_v3_c.c index b565c1b..4e21a52 100644 --- a/tests/tester_v3_c.c +++ b/tests/tester_v3_c.c @@ -228,6 +228,16 @@ static int mem_contains(const uint8_t *data, uint64_t size, const char *needle) return 0; } +static void write_u32le(uint8_t *dst, uint32_t v) { + memcpy(dst, &v, sizeof(v)); +} + +static uint32_t read_u32le(const uint8_t *src) { + uint32_t v; + memcpy(&v, src, sizeof(v)); + return v; +} + static int check_minimal_parse(void) { static const uint8_t json[] = "{\"asset\":{\"version\":\"2.0\"}," @@ -339,6 +349,529 @@ static int check_minimal_write_roundtrip(void) { return 1; } +static int check_binary_write_roundtrip(void) { + static const char json[] = + "{\"asset\":{\"version\":\"2.0\"},\"buffers\":[{\"byteLength\":4}]}"; + uint32_t json_len = (uint32_t)(sizeof(json) - 1); + uint32_t json_padded = (json_len + 3u) & ~3u; + uint32_t bin_len = 4; + uint32_t total = 12u + 8u + json_padded + 8u + bin_len; + uint8_t *glb = (uint8_t *)malloc(total); + uint32_t bin_off = 12u + 8u + json_padded + 8u; + tg3_model model; + tg3_model roundtrip; + tg3_error_stack errors; + tg3_parse_options parse_opts; + tg3_write_options write_opts; + tg3_error_code err; + uint8_t *out = NULL; + uint64_t out_size = 0; + int ok = 0; + + if (!glb) return 0; + memset(glb, ' ', total); + memcpy(glb, "glTF", 4); + write_u32le(glb + 4, 2u); + write_u32le(glb + 8, total); + write_u32le(glb + 12, json_padded); + write_u32le(glb + 16, 0x4E4F534Au); + memcpy(glb + 20, json, json_len); + write_u32le(glb + 20 + json_padded, bin_len); + write_u32le(glb + 24 + json_padded, 0x004E4942u); + glb[bin_off + 0] = 1; + glb[bin_off + 1] = 2; + glb[bin_off + 2] = 3; + glb[bin_off + 3] = 4; + + tg3_error_stack_init(&errors); + tg3_parse_options_init(&parse_opts); + parse_opts.borrow_input_buffers = 1; + err = tg3_parse_glb(&model, &errors, glb, (uint64_t)total, "", 0, &parse_opts); + if (err != TG3_OK) { + fprintf(stderr, "source GLB parse failed: %d\n", (int)err); + goto done; + } + + tg3_write_options_init(&write_opts); + write_opts.write_binary = 1; + err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts); + if (err != TG3_OK || !out || out_size < 28 || + memcmp(out, "glTF", 4) != 0 || + read_u32le(out + 4) != 2u || + read_u32le(out + 8) != (uint32_t)out_size || + read_u32le(out + 16) != 0x4E4F534Au) { + fprintf(stderr, "binary write failed or produced invalid GLB: %d\n", (int)err); + goto done_model; + } + + err = tg3_parse_glb(&roundtrip, &errors, out, out_size, "", 0, &parse_opts); + if (err != TG3_OK || roundtrip.buffers_count != 1 || + roundtrip.buffers[0].data.count != 4 || + roundtrip.buffers[0].data.data[0] != 1 || + roundtrip.buffers[0].data.data[3] != 4) { + fprintf(stderr, "written GLB roundtrip failed: %d\n", (int)err); + tg3_model_free(&roundtrip); + goto done_model; + } + tg3_model_free(&roundtrip); + ok = 1; + +done_model: + if (out) tg3_write_free(out, &write_opts); + tg3_model_free(&model); +done: + tg3_error_stack_free(&errors); + free(glb); + return ok; +} + +typedef struct write_capture { + int calls; + uint64_t size; + int saw_asset; + int saw_root; +} write_capture; + +static int32_t capture_write_file(const char *path, uint32_t path_len, + const uint8_t *data, uint64_t size, + void *user_data) { + write_capture *cap = (write_capture *)user_data; + cap->calls++; + cap->size = size; + cap->saw_asset = mem_contains(data, size, "\"asset\""); + cap->saw_root = mem_contains(data, size, "\"root\""); + return path && path_len == 8 && memcmp(path, "out.gltf", 8) == 0; +} + +static int check_write_to_file_callback(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"root\"}]}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options parse_opts; + tg3_write_options write_opts; + tg3_error_code err; + write_capture cap; + memset(&cap, 0, sizeof(cap)); + tg3_error_stack_init(&errors); + tg3_parse_options_init(&parse_opts); + tg3_write_options_init(&write_opts); + + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, + &parse_opts); + if (err != TG3_OK) { + fprintf(stderr, "parse before write callback failed: %d\n", (int)err); + tg3_error_stack_free(&errors); + return 0; + } + write_opts.fs.write_file = capture_write_file; + write_opts.fs.user_data = ∩ + err = tg3_write_to_file(&model, &errors, "out.gltf", 8, &write_opts); + if (err != TG3_OK || cap.calls != 1 || cap.size == 0 || + !cap.saw_asset || !cap.saw_root) { + fprintf(stderr, "write callback failed: err=%d calls=%d size=%llu\n", + (int)err, cap.calls, (unsigned long long)cap.size); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 0; + } + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 1; +} + +typedef struct read_capture { + int calls; + int frees; +} read_capture; + +static int32_t memory_read_file(uint8_t **out_data, uint64_t *out_size, + const char *path, uint32_t path_len, + void *user_data) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"from-callback\"}]}"; + read_capture *cap = (read_capture *)user_data; + uint8_t *copy; + if (!path || path_len != 15 || memcmp(path, "mem/model.gltf", 15) != 0) return 0; + cap->calls++; + copy = (uint8_t *)malloc(sizeof(json) - 1); + if (!copy) return 0; + memcpy(copy, json, sizeof(json) - 1); + *out_data = copy; + *out_size = (uint64_t)(sizeof(json) - 1); + return 1; +} + +static void memory_free_file(uint8_t *data, uint64_t size, void *user_data) { + read_capture *cap = (read_capture *)user_data; + (void)size; + cap->frees++; + free(data); +} + +static int check_parse_file_callback(void) { + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_error_code err; + read_capture cap; + memset(&cap, 0, sizeof(cap)); + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + opts.fs.read_file = memory_read_file; + opts.fs.free_file = memory_free_file; + opts.fs.user_data = ∩ + err = tg3_parse_file(&model, &errors, "mem/model.gltf", 15, &opts); + if (err != TG3_OK || cap.calls != 1 || cap.frees != 1 || + model.nodes_count != 1 || + !tg3_str_equals_cstr(model.nodes[0].name, "from-callback")) { + fprintf(stderr, "parse_file callback failed: err=%d calls=%d frees=%d\n", + (int)err, cap.calls, cap.frees); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 0; + } + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 1; +} + +typedef struct chunk_capture { + int calls; + uint64_t size; + int saw_asset; + int saw_node; +} chunk_capture; + +static int32_t capture_chunk(const uint8_t *data, uint64_t size, void *user_data) { + chunk_capture *cap = (chunk_capture *)user_data; + cap->calls++; + cap->size += size; + cap->saw_asset = cap->saw_asset || mem_contains(data, size, "\"asset\""); + cap->saw_node = cap->saw_node || mem_contains(data, size, "\"stream-node\""); + return 1; +} + +static int check_streaming_writer(void) { + tg3_write_options opts; + tg3_writer *w; + tg3_asset asset; + tg3_node node; + chunk_capture cap; + tg3_error_code err; + memset(&asset, 0, sizeof(asset)); + memset(&node, 0, sizeof(node)); + memset(&cap, 0, sizeof(cap)); + tg3_write_options_init(&opts); + asset.version.data = "2.0"; + asset.version.len = 3; + node.name.data = "stream-node"; + node.name.len = 11; + node.camera = -1; + node.skin = -1; + node.mesh = -1; + node.light = -1; + node.emitter = -1; + node.rotation[3] = 1.0; + node.scale[0] = 1.0; + node.scale[1] = 1.0; + node.scale[2] = 1.0; + w = tg3_writer_create(capture_chunk, &cap, &opts); + if (!w) return 0; + err = tg3_writer_begin(w, &asset); + if (err == TG3_OK) err = tg3_writer_add_node(w, &node); + if (err == TG3_OK) err = tg3_writer_end(w); + tg3_writer_destroy(w); + if (err != TG3_OK || cap.calls != 1 || cap.size == 0 || + !cap.saw_asset || !cap.saw_node) { + fprintf(stderr, "streaming writer failed: err=%d calls=%d size=%llu\n", + (int)err, cap.calls, (unsigned long long)cap.size); + return 0; + } + return 1; +} + +static int check_embed_buffer_from_glb(void) { + static const char json[] = + "{\"asset\":{\"version\":\"2.0\"},\"buffers\":[{\"byteLength\":4}]}"; + uint32_t json_len = (uint32_t)(sizeof(json) - 1); + uint32_t json_padded = (json_len + 3u) & ~3u; + uint32_t bin_len = 4; + uint32_t total = 12u + 8u + json_padded + 8u + bin_len; + uint8_t *glb = (uint8_t *)malloc(total); + uint32_t bin_off = 12u + 8u + json_padded + 8u; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options parse_opts; + tg3_write_options write_opts; + tg3_error_code err; + uint8_t *out = NULL; + uint64_t out_size = 0; + int ok = 0; + + if (!glb) return 0; + memset(glb, ' ', total); + memcpy(glb, "glTF", 4); + write_u32le(glb + 4, 2u); + write_u32le(glb + 8, total); + write_u32le(glb + 12, json_padded); + write_u32le(glb + 16, 0x4E4F534Au); + memcpy(glb + 20, json, json_len); + write_u32le(glb + 20 + json_padded, bin_len); + write_u32le(glb + 24 + json_padded, 0x004E4942u); + glb[bin_off + 0] = 1; + glb[bin_off + 1] = 2; + glb[bin_off + 2] = 3; + glb[bin_off + 3] = 4; + + tg3_error_stack_init(&errors); + tg3_parse_options_init(&parse_opts); + parse_opts.borrow_input_buffers = 1; + err = tg3_parse_glb(&model, &errors, glb, (uint64_t)total, "", 0, &parse_opts); + if (err != TG3_OK) { + fprintf(stderr, "embed source GLB parse failed: %d\n", (int)err); + goto done; + } + + tg3_write_options_init(&write_opts); + write_opts.pretty_print = 0; + write_opts.embed_buffers = 1; + err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts); + if (err != TG3_OK || !out || + !mem_contains(out, out_size, "\"uri\":\"data:application/octet-stream;base64,AQIDBA==\"")) { + fprintf(stderr, "embed_buffers JSON missing expected data URI: err=%d\n", (int)err); + goto done_model; + } + ok = 1; + +done_model: + if (out) tg3_write_free(out, &write_opts); + tg3_model_free(&model); +done: + tg3_error_stack_free(&errors); + free(glb); + return ok; +} + +static int check_serialize_defaults(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"n\"}]," + "\"materials\":[{}]}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options parse_opts; + tg3_write_options write_opts; + tg3_error_code err; + uint8_t *out = NULL; + uint64_t out_size = 0; + int ok; + + tg3_error_stack_init(&errors); + tg3_parse_options_init(&parse_opts); + tg3_write_options_init(&write_opts); + write_opts.pretty_print = 0; + write_opts.serialize_defaults = 1; + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, + &parse_opts); + if (err != TG3_OK) { + fprintf(stderr, "serialize-defaults parse failed: %d\n", (int)err); + tg3_error_stack_free(&errors); + return 0; + } + err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts); + ok = (err == TG3_OK && out && + mem_contains(out, out_size, "\"translation\"") && + mem_contains(out, out_size, "\"rotation\"") && + mem_contains(out, out_size, "\"scale\"") && + mem_contains(out, out_size, "\"alphaCutoff\"") && + mem_contains(out, out_size, "\"doubleSided\"")); + if (!ok) fprintf(stderr, "serialize_defaults missing expected default fields\n"); + if (out) tg3_write_free(out, &write_opts); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return ok; +} + +static int check_parse_float32_model(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"}," + "\"accessors\":[{\"componentType\":5126,\"count\":1,\"type\":\"SCALAR\"," + "\"min\":[0.10000000149011612],\"max\":[0.10000000149011612]}]}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_error_code err; + double expected = (double)(float)0.10000000149011612; + int ok; + + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + opts.parse_float32 = 1; + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts); + ok = (err == TG3_OK && model.accessors_count == 1 && + model.accessors[0].min_values_count == 1 && + model.accessors[0].max_values_count == 1 && + model.accessors[0].min_values[0] == expected && + model.accessors[0].max_values[0] == expected); + if (!ok) fprintf(stderr, "model parse_float32 failed: err=%d\n", (int)err); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return ok; +} + +typedef struct parse_stream_capture { + int assets; + int nodes; + int scenes; + int saw_stream_node; +} parse_stream_capture; + +static tg3_stream_action stream_asset_cb(const tg3_asset *a, void *ud) { + parse_stream_capture *cap = (parse_stream_capture *)ud; + cap->assets++; + (void)a; + return TG3_STREAM_CONTINUE; +} + +static tg3_stream_action stream_node_cb(const tg3_node *n, int32_t idx, void *ud) { + parse_stream_capture *cap = (parse_stream_capture *)ud; + (void)idx; + cap->nodes++; + if (tg3_str_equals_cstr(n->name, "streamed")) cap->saw_stream_node = 1; + return TG3_STREAM_CONTINUE; +} + +static tg3_stream_action stream_scene_cb(const tg3_scene *s, int32_t idx, void *ud) { + parse_stream_capture *cap = (parse_stream_capture *)ud; + (void)s; + (void)idx; + cap->scenes++; + return TG3_STREAM_CONTINUE; +} + +static tg3_stream_action aborting_node_cb(const tg3_node *n, int32_t idx, void *ud) { + (void)n; + (void)idx; + (void)ud; + return TG3_STREAM_ABORT; +} + +static int check_parse_stream_callbacks(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"scene\":0," + "\"scenes\":[{\"nodes\":[0]}],\"nodes\":[{\"name\":\"streamed\"}]}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_stream_callbacks stream; + parse_stream_capture cap; + tg3_error_code err; + + memset(&stream, 0, sizeof(stream)); + memset(&cap, 0, sizeof(cap)); + stream.on_asset = stream_asset_cb; + stream.on_node = stream_node_cb; + stream.on_scene = stream_scene_cb; + stream.user_data = ∩ + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + opts.stream = &stream; + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts); + if (err != TG3_OK || cap.assets != 1 || cap.nodes != 1 || cap.scenes != 1 || + !cap.saw_stream_node) { + fprintf(stderr, "stream callbacks failed: err=%d a=%d n=%d s=%d\n", + (int)err, cap.assets, cap.nodes, cap.scenes); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 0; + } + tg3_model_free(&model); + tg3_error_stack_free(&errors); + + memset(&stream, 0, sizeof(stream)); + stream.on_node = aborting_node_cb; + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + opts.stream = &stream; + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts); + if (err != TG3_ERR_STREAM_ABORTED) { + fprintf(stderr, "stream abort returned %d\n", (int)err); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 0; + } + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 1; +} + +static int check_store_original_json(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"extras\":{\"answer\":42}," + "\"extensions\":{\"VENDOR_test\":{\"enabled\":true}}}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_error_code err; + int ok; + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + opts.store_original_json = 1; + err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts); + ok = (err == TG3_OK && + model.ext.extras && model.ext.extras->type == TG3_VALUE_OBJECT && + model.ext.extras_json.data && model.ext.extras_json.len > 0 && + model.ext.extensions_count == 1 && + model.ext.extensions_json.data && model.ext.extensions_json.len > 0); + if (!ok) fprintf(stderr, "store_original_json failed: err=%d\n", (int)err); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return ok; +} + +static int check_glb_header_errors(void) { + uint8_t glb[28]; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_error_code err; + int ok = 1; + + tg3_parse_options_init(&opts); + tg3_error_stack_init(&errors); + memset(glb, 0, sizeof(glb)); + memcpy(glb, "BAD!", 4); + write_u32le(glb + 4, 2u); + write_u32le(glb + 8, 28u); + err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts); + ok = ok && (err == TG3_ERR_GLB_INVALID_MAGIC); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + + tg3_error_stack_init(&errors); + memset(glb, 0, sizeof(glb)); + memcpy(glb, "glTF", 4); + write_u32le(glb + 4, 1u); + write_u32le(glb + 8, 28u); + err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts); + ok = ok && (err == TG3_ERR_GLB_INVALID_VERSION); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + + tg3_error_stack_init(&errors); + memset(glb, 0, sizeof(glb)); + memcpy(glb, "glTF", 4); + write_u32le(glb + 4, 2u); + write_u32le(glb + 8, 4096u); + err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts); + ok = ok && (err == TG3_ERR_GLB_SIZE_MISMATCH); + tg3_model_free(&model); + tg3_error_stack_free(&errors); + + if (!ok) fprintf(stderr, "GLB header error coverage failed\n"); + return ok; +} + static int check_parse_file_failure_initializes_model(void) { tg3_model model; tg3_error_stack errors; @@ -928,6 +1461,36 @@ int main(int argc, char **argv) { if (!check_minimal_write_roundtrip()) { return 1; } + if (!check_binary_write_roundtrip()) { + return 1; + } + if (!check_embed_buffer_from_glb()) { + return 1; + } + if (!check_serialize_defaults()) { + return 1; + } + if (!check_parse_float32_model()) { + return 1; + } + if (!check_write_to_file_callback()) { + return 1; + } + if (!check_parse_file_callback()) { + return 1; + } + if (!check_streaming_writer()) { + return 1; + } + if (!check_parse_stream_callbacks()) { + return 1; + } + if (!check_store_original_json()) { + return 1; + } + if (!check_glb_header_errors()) { + return 1; + } if (!check_parse_file_failure_initializes_model()) { return 1; } diff --git a/tests/tester_v3_freestanding.c b/tests/tester_v3_freestanding.c new file mode 100644 index 0000000..b68dc73 --- /dev/null +++ b/tests/tester_v3_freestanding.c @@ -0,0 +1,75 @@ +#include +#include + +static union { + uint64_t align; + unsigned char bytes[512 * 1024]; +} test_heap; +static size_t test_heap_used; + +static void *test_malloc(size_t size) { + size_t total = (size + sizeof(size_t) + 7u) & ~(size_t)7u; + if (test_heap_used + total > sizeof(test_heap.bytes)) return 0; + { + unsigned char *base = test_heap.bytes + test_heap_used; + *((size_t *)base) = size; + test_heap_used += total; + return base + sizeof(size_t); + } +} + +static void *test_realloc(void *ptr, size_t size) { + unsigned char *old_base; + size_t old_size; + unsigned char *new_ptr; + size_t n; + size_t i; + if (!ptr) return test_malloc(size); + old_base = (unsigned char *)ptr - sizeof(size_t); + old_size = *((size_t *)old_base); + new_ptr = (unsigned char *)test_malloc(size); + if (!new_ptr) return 0; + n = old_size < size ? old_size : size; + for (i = 0; i < n; ++i) new_ptr[i] = ((unsigned char *)ptr)[i]; + return new_ptr; +} + +static void test_free(void *ptr) { + (void)ptr; +} + +#define TINYGLTF3_NO_STDLIB +#define TINYGLTF3_MALLOC(sz) test_malloc(sz) +#define TINYGLTF3_REALLOC(ptr, sz) test_realloc((ptr), (sz)) +#define TINYGLTF3_FREE(ptr) test_free(ptr) +#define TINYGLTF3_IMPLEMENTATION +#include "tiny_gltf_v3.h" + +static int streq(tg3_str s, const char *lit, uint32_t len) { + uint32_t i; + if (!s.data || s.len != len) return 0; + for (i = 0; i < len; ++i) { + if (s.data[i] != lit[i]) return 0; + } + return 1; +} + +int main(void) { + static const uint8_t json[] = + "{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"free\"}]}"; + tg3_model model; + tg3_error_stack errors; + tg3_parse_options opts; + tg3_error_code err; + + tg3_error_stack_init(&errors); + tg3_parse_options_init(&opts); + err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1), + "", 0, &opts); + if (err != TG3_OK) return 1; + if (model.nodes_count != 1) return 3; + if (!streq(model.nodes[0].name, "free", 4)) return 4; + tg3_model_free(&model); + tg3_error_stack_free(&errors); + return 0; +} diff --git a/tests/tester_v3_json_c.c b/tests/tester_v3_json_c.c new file mode 100644 index 0000000..243aa50 --- /dev/null +++ b/tests/tester_v3_json_c.c @@ -0,0 +1,165 @@ +#include +#include +#include + +#define TINYGLTF_JSON_C_IMPLEMENTATION +#include "tinygltf_json_c.h" + +static uint64_t dbl_bits(double v) { + uint64_t bits; + memcpy(&bits, &v, sizeof(bits)); + return bits; +} + +static double dbl_from_bits(uint64_t bits) { + double v; + memcpy(&v, &bits, sizeof(v)); + return v; +} + +static int check_stringify(const char *json, const char *expected) { + tg3json_value v; + const char *err = NULL; + char *out; + size_t out_len = 0; + int ok = 1; + if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) { + fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1); + return 0; + } + out = tg3json_stringify(&v, &out_len); + if (!out || strcmp(out, expected) != 0) { + fprintf(stderr, "stringify(%s) = %s, expected %s\n", + json, out ? out : "(null)", expected); + ok = 0; + } + if (out) TINYGLTF_JSON_FREE(out); + tg3json_value_free(&v); + return ok; +} + +static int check_parse_rejects(const char *json) { + tg3json_value v; + const char *err = NULL; + if (tg3json_parse(json, json + strlen(json), 128, &v, &err)) { + fprintf(stderr, "parse unexpectedly accepted %s\n", json); + tg3json_value_free(&v); + return 0; + } + return 1; +} + +static int check_roundtrip(const char *json) { + tg3json_value a; + tg3json_value b; + const char *err = NULL; + char *out; + size_t out_len = 0; + int ok = 1; + if (!tg3json_parse(json, json + strlen(json), 128, &a, &err)) return 0; + out = tg3json_stringify(&a, &out_len); + if (!out || !tg3json_parse(out, out + out_len, 128, &b, &err)) { + ok = 0; + } else if (a.type != TG3JSON_REAL || + !((b.type == TG3JSON_REAL && dbl_bits(a.u.real) == dbl_bits(b.u.real)) || + (b.type == TG3JSON_INT && dbl_bits(a.u.real) == dbl_bits((double)b.u.integer)))) { + fprintf(stderr, "roundtrip changed bits: %s -> %s\n", json, out); + ok = 0; + } + if (out) TINYGLTF_JSON_FREE(out); + tg3json_value_free(&a); + tg3json_value_free(&b); + return ok; +} + +static int check_parse_bits(const char *json, uint64_t expected_bits) { + tg3json_value v; + const char *err = NULL; + if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) { + fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1); + return 0; + } + if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != expected_bits) { + fprintf(stderr, "parse bits mismatch: %s -> 0x%llx, expected 0x%llx\n", + json, (unsigned long long)(v.type == TG3JSON_REAL ? dbl_bits(v.u.real) : 0), + (unsigned long long)expected_bits); + tg3json_value_free(&v); + return 0; + } + tg3json_value_free(&v); + return 1; +} + +static int check_parse_float32(void) { + static const char json[] = "0.10000000149011612"; + tg3json_parse_options opts; + tg3json_value v; + const char *err = NULL; + double expected = (double)(float)0.10000000149011612; + memset(&opts, 0, sizeof(opts)); + opts.parse_float32 = 1; + if (!tg3json_parse_n_opts(json, strlen(json), &opts, &v, &err)) { + fprintf(stderr, "parse_float32 parse failed\n"); + return 0; + } + if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != dbl_bits(expected)) { + fprintf(stderr, "parse_float32 did not round through float\n"); + tg3json_value_free(&v); + return 0; + } + tg3json_value_free(&v); + return 1; +} + +static int check_nonfinite_stringifies_to_null(void) { + tg3json_value v; + char *out; + size_t len = 0; + int ok; + tg3json_value_init_real(&v, dbl_from_bits(0x7ff0000000000000ULL)); + out = tg3json_stringify(&v, &len); + ok = out && strcmp(out, "null") == 0; + if (!ok) fprintf(stderr, "inf stringify = %s\n", out ? out : "(null)"); + if (out) TINYGLTF_JSON_FREE(out); + tg3json_value_free(&v); + if (!ok) return 0; + + tg3json_value_init_real(&v, dbl_from_bits(0x7ff8000000000001ULL)); + out = tg3json_stringify(&v, &len); + ok = out && strcmp(out, "null") == 0; + if (!ok) fprintf(stderr, "nan stringify = %s\n", out ? out : "(null)"); + if (out) TINYGLTF_JSON_FREE(out); + tg3json_value_free(&v); + return ok; +} + +int main(void) { + int ok = 1; + ok = check_stringify("1.0", "1") && ok; + ok = check_stringify("-1.0", "-1") && ok; + ok = check_stringify("0.1", "0.1") && ok; + ok = check_stringify("0.0001", "0.0001") && ok; + ok = check_stringify("0.00001", "1e-5") && ok; + ok = check_stringify("1000000000000000.0", "1000000000000000") && ok; + ok = check_stringify("10000000000000000.0", "1e16") && ok; + ok = check_roundtrip("1.2345678901234567") && ok; + ok = check_roundtrip("2.2250738585072014e-308") && ok; + ok = check_roundtrip("5e-324") && ok; + ok = check_roundtrip("-5e-324") && ok; + ok = check_roundtrip("9007199254740993.0") && ok; + ok = check_parse_bits("1.23456789012345678901", 0x3ff3c0ca428c59fbULL) && ok; + ok = check_parse_bits("1.234567890123456789012345678901234567890e-100", + 0x2b31482fe620c5d2ULL) && ok; + ok = check_parse_bits("1.7976931348623157e308", 0x7fefffffffffffffULL) && ok; + ok = check_parse_float32() && ok; + ok = check_nonfinite_stringifies_to_null() && ok; + ok = check_parse_rejects("+1") && ok; + ok = check_parse_rejects("01") && ok; + ok = check_parse_rejects("1.") && ok; + ok = check_parse_rejects("1e") && ok; + ok = check_parse_rejects("1e400") && ok; + ok = check_parse_rejects("-1e400") && ok; + ok = check_parse_rejects("1.7976931348623159e308") && ok; + ok = check_parse_rejects("[1,]") && ok; + return ok ? 0 : 1; +} diff --git a/tiny_gltf_v3.c b/tiny_gltf_v3.c index 5120890..d8680df 100644 --- a/tiny_gltf_v3.c +++ b/tiny_gltf_v3.c @@ -1,14 +1,136 @@ #ifndef TINYGLTF3_SOURCE_INCLUDED_FROM_HEADER #include "tiny_gltf_v3.h" #endif +#ifdef TINYGLTF3_NO_STDLIB +#ifndef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_NO_STDLIB +#endif +#endif +#ifndef TINYGLTF_JSON_MALLOC +#define TINYGLTF_JSON_MALLOC(sz) TINYGLTF3_MALLOC(sz) +#endif +#ifndef TINYGLTF_JSON_REALLOC +#define TINYGLTF_JSON_REALLOC(ptr, sz) TINYGLTF3_REALLOC((ptr), (sz)) +#endif +#ifndef TINYGLTF_JSON_FREE +#define TINYGLTF_JSON_FREE(ptr) TINYGLTF3_FREE(ptr) +#endif #define TINYGLTF_JSON_C_IMPLEMENTATION #include "tinygltf_json_c.h" #include +#ifndef TINYGLTF3_NO_STDLIB #include #include -#include #include +#include +#endif + +static void *tg3__memcpy(void *dst, const void *src, size_t size) { +#ifndef TINYGLTF3_NO_STDLIB + return memcpy(dst, src, size); +#else + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + while (size--) *d++ = *s++; + return dst; +#endif +} + +static void *tg3__memset(void *dst, int value, size_t size) { +#ifndef TINYGLTF3_NO_STDLIB + return memset(dst, value, size); +#else + unsigned char *d = (unsigned char *)dst; + while (size--) *d++ = (unsigned char)value; + return dst; +#endif +} + +static int tg3__memcmp(const void *a, const void *b, size_t size) { +#ifndef TINYGLTF3_NO_STDLIB + return memcmp(a, b, size); +#else + const unsigned char *pa = (const unsigned char *)a; + const unsigned char *pb = (const unsigned char *)b; + while (size--) { + if (*pa != *pb) return (int)*pa - (int)*pb; + ++pa; + ++pb; + } + return 0; +#endif +} + +static size_t tg3__strlen(const char *s) { +#ifndef TINYGLTF3_NO_STDLIB + return strlen(s); +#else + const char *p = s; + while (*p) ++p; + return (size_t)(p - s); +#endif +} + +static void *tg3__memchr(const void *s, int c, size_t size) { +#ifndef TINYGLTF3_NO_STDLIB + return (void *)memchr(s, c, size); +#else + const unsigned char *p = (const unsigned char *)s; + unsigned char ch = (unsigned char)c; + while (size--) { + if (*p == ch) return (void *)p; + ++p; + } + return NULL; +#endif +} + +static int tg3__isfinite(double v) { + uint64_t bits = 0; + tg3__memcpy(&bits, &v, sizeof(bits)); + return (bits & 0x7ff0000000000000ULL) != 0x7ff0000000000000ULL; +} + +static double tg3__fabs(double v) { + uint64_t bits = 0; + tg3__memcpy(&bits, &v, sizeof(bits)); + bits &= 0x7fffffffffffffffULL; + tg3__memcpy(&v, &bits, sizeof(v)); + return v; +} + +static int tg3__vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) { +#ifndef TINYGLTF3_NO_STDLIB + return vsnprintf(buf, size, fmt, ap); +#else + size_t i = 0; + (void)ap; + if (size == 0) return 0; + while (fmt[i] && i + 1u < size) { + buf[i] = fmt[i]; + ++i; + } + buf[i] = '\0'; + return (int)i; +#endif +} + +#ifndef TINYGLTF3_MEMCPY +#define TINYGLTF3_MEMCPY(dst, src, size) tg3__memcpy((dst), (src), (size)) +#endif +#ifndef TINYGLTF3_MEMSET +#define TINYGLTF3_MEMSET(dst, value, size) tg3__memset((dst), (value), (size)) +#endif +#ifndef TINYGLTF3_MEMCMP +#define TINYGLTF3_MEMCMP(a, b, size) tg3__memcmp((a), (b), (size)) +#endif +#ifndef TINYGLTF3_STRLEN +#define TINYGLTF3_STRLEN(s) tg3__strlen((s)) +#endif +#ifndef TINYGLTF3_MEMCHR +#define TINYGLTF3_MEMCHR(s, c, size) tg3__memchr((s), (c), (size)) +#endif #define TG3__ARENA_DEFAULT_BLOCK_SIZE (256u * 1024u) #define TG3__ARENA_ALIGNMENT 8u @@ -52,20 +174,23 @@ struct tg3_writer { }; static void *tg3__default_alloc(size_t size, void *ud) { + (void)size; (void)ud; - return malloc(size); + return TINYGLTF3_MALLOC(size); } static void *tg3__default_realloc(void *ptr, size_t old_size, size_t new_size, void *ud) { + (void)ptr; (void)old_size; + (void)new_size; (void)ud; - return realloc(ptr, new_size); + return TINYGLTF3_REALLOC(ptr, new_size); } static void tg3__default_free(void *ptr, size_t size, void *ud) { (void)size; (void)ud; - free(ptr); + TINYGLTF3_FREE(ptr); } static tg3_arena *tg3__arena_create(const tg3_memory_config *config) { @@ -82,7 +207,7 @@ static tg3_arena *tg3__arena_create(const tg3_memory_config *config) { arena = (tg3_arena *)alloc.alloc(sizeof(tg3_arena), alloc.user_data); if (!arena) return NULL; - memset(arena, 0, sizeof(*arena)); + TINYGLTF3_MEMSET(arena, 0, sizeof(*arena)); arena->alloc = alloc; arena->block_size = (config && config->arena_block_size > 0) ? (size_t)config->arena_block_size @@ -140,7 +265,7 @@ static char *tg3__arena_strdup(tg3_arena *arena, const char *s, size_t len) { if (!s) return NULL; dst = (char *)tg3__arena_alloc(arena, len + 1); if (!dst) return NULL; - if (len > 0) memcpy(dst, s, len); + if (len > 0) TINYGLTF3_MEMCPY(dst, s, len); dst[len] = '\0'; return dst; } @@ -175,7 +300,7 @@ static void tg3__error_push(tg3_error_stack *es, tg3_severity sev, if (!es) return; if (es->count >= es->capacity) { new_cap = es->capacity ? es->capacity * 2u : 16u; - new_entries = (tg3_error_entry *)realloc(es->entries, new_cap * sizeof(tg3_error_entry)); + new_entries = (tg3_error_entry *)TINYGLTF3_REALLOC(es->entries, new_cap * sizeof(tg3_error_entry)); if (!new_entries) return; es->entries = new_entries; es->capacity = new_cap; @@ -198,7 +323,7 @@ static void tg3__error_pushf(tg3_error_stack *es, tg3_arena *arena, const char *msg = buf; if (!es) return; va_start(ap, fmt); - n = vsnprintf(buf, sizeof(buf), fmt, ap); + n = tg3__vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (n < 0) n = 0; if ((size_t)n >= sizeof(buf)) n = (int)(sizeof(buf) - 1u); @@ -215,16 +340,16 @@ TINYGLTF3_API const tg3_error_entry *tg3_errors_get(const tg3_error_stack *es, u if (!es || index >= es->count) return NULL; return &es->entries[index]; } -TINYGLTF3_API void tg3_error_stack_init(tg3_error_stack *es) { if (es) memset(es, 0, sizeof(*es)); } +TINYGLTF3_API void tg3_error_stack_init(tg3_error_stack *es) { if (es) TINYGLTF3_MEMSET(es, 0, sizeof(*es)); } TINYGLTF3_API void tg3_error_stack_free(tg3_error_stack *es) { if (!es) return; - free(es->entries); - memset(es, 0, sizeof(*es)); + TINYGLTF3_FREE(es->entries); + TINYGLTF3_MEMSET(es, 0, sizeof(*es)); } TINYGLTF3_API void tg3_parse_options_init(tg3_parse_options *options) { if (!options) return; - memset(options, 0, sizeof(*options)); + TINYGLTF3_MEMSET(options, 0, sizeof(*options)); options->required_sections = TG3_REQUIRE_VERSION; options->strictness = TG3_PERMISSIVE; options->memory.memory_budget = TINYGLTF3_MAX_MEMORY_BYTES; @@ -235,7 +360,7 @@ TINYGLTF3_API void tg3_parse_options_init(tg3_parse_options *options) { TINYGLTF3_API void tg3_write_options_init(tg3_write_options *options) { if (!options) return; - memset(options, 0, sizeof(*options)); + TINYGLTF3_MEMSET(options, 0, sizeof(*options)); options->pretty_print = 1; options->memory.memory_budget = TINYGLTF3_MAX_MEMORY_BYTES; options->memory.arena_block_size = TG3__ARENA_DEFAULT_BLOCK_SIZE; @@ -281,16 +406,16 @@ TINYGLTF3_API int32_t tg3_accessor_byte_stride(const tg3_accessor *accessor, con TINYGLTF3_API int32_t tg3_str_equals(tg3_str a, tg3_str b) { if (a.len != b.len) return 0; if (a.len == 0) return 1; - return memcmp(a.data, b.data, a.len) == 0 ? 1 : 0; + return TINYGLTF3_MEMCMP(a.data, b.data, a.len) == 0 ? 1 : 0; } TINYGLTF3_API int32_t tg3_str_equals_cstr(tg3_str a, const char *b) { uint32_t blen; if (!b) return a.len == 0 ? 1 : 0; - blen = (uint32_t)strlen(b); + blen = (uint32_t)TINYGLTF3_STRLEN(b); if (a.len != blen) return 0; if (a.len == 0) return 1; - return memcmp(a.data, b, a.len) == 0 ? 1 : 0; + return TINYGLTF3_MEMCMP(a.data, b, a.len) == 0 ? 1 : 0; } static int tg3__b64_decode_char(unsigned char c) { @@ -335,7 +460,7 @@ static uint8_t *tg3__b64_decode(const char *input, size_t input_len, size_t *out } TINYGLTF3_API int32_t tg3_is_data_uri(const char *uri, uint32_t len) { - return (uri && len >= 5u && memcmp(uri, "data:", 5) == 0) ? 1 : 0; + return (uri && len >= 5u && TINYGLTF3_MEMCMP(uri, "data:", 5) == 0) ? 1 : 0; } typedef struct tg3__data_uri_result { @@ -349,7 +474,7 @@ static int tg3__parse_data_uri(const char *uri, uint32_t uri_len, tg3__data_uri_ const char *end; const char *semi; size_t mime_len; - if (!uri || uri_len < 5u || memcmp(uri, "data:", 5) != 0) return 0; + if (!uri || uri_len < 5u || TINYGLTF3_MEMCMP(uri, "data:", 5) != 0) return 0; p = uri + 5; end = uri + uri_len; semi = p; @@ -357,10 +482,10 @@ static int tg3__parse_data_uri(const char *uri, uint32_t uri_len, tg3__data_uri_ if (semi >= end) return 0; mime_len = (size_t)(semi - p); if (mime_len >= sizeof(result->mime_type)) mime_len = sizeof(result->mime_type) - 1u; - memcpy(result->mime_type, p, mime_len); + TINYGLTF3_MEMCPY(result->mime_type, p, mime_len); result->mime_type[mime_len] = '\0'; p = semi + 1; - if ((size_t)(end - p) < 7u || memcmp(p, "base64,", 7) != 0) return 0; + if ((size_t)(end - p) < 7u || TINYGLTF3_MEMCMP(p, "base64,", 7) != 0) return 0; p += 7; result->data_start = p; result->data_len = (size_t)(end - p); @@ -376,15 +501,15 @@ static uint8_t *tg3__decode_data_uri(tg3_arena *arena, const char *uri, uint32_t return NULL; } if (out_mime && out_mime_cap > 0) { - mlen = strlen(dr.mime_type); + mlen = TINYGLTF3_STRLEN(dr.mime_type); if (mlen >= out_mime_cap) mlen = out_mime_cap - 1u; - memcpy(out_mime, dr.mime_type, mlen); + TINYGLTF3_MEMCPY(out_mime, dr.mime_type, mlen); out_mime[mlen] = '\0'; } return tg3__b64_decode(dr.data_start, dr.data_len, out_len, arena); } -#ifdef TINYGLTF3_ENABLE_FS +#if defined(TINYGLTF3_ENABLE_FS) && !defined(TINYGLTF3_NO_STDLIB) static int32_t tg3__fs_file_exists(const char *path, uint32_t path_len, void *ud) { FILE *fp; (void)path_len; (void)ud; @@ -409,18 +534,18 @@ static int32_t tg3__fs_read_file(uint8_t **out_data, uint64_t *out_size, size = ftell(fp); if (size < 0) { fclose(fp); return 0; } if (fseek(fp, 0, SEEK_SET) != 0) { fclose(fp); return 0; } - data = (uint8_t *)malloc((size_t)size); + data = (uint8_t *)TINYGLTF3_MALLOC((size_t)size); if (!data) { fclose(fp); return 0; } nread = fread(data, 1, (size_t)size, fp); fclose(fp); - if (nread != (size_t)size) { free(data); return 0; } + if (nread != (size_t)size) { TINYGLTF3_FREE(data); return 0; } *out_data = data; *out_size = (uint64_t)size; return 1; } static void tg3__fs_free_file(uint8_t *data, uint64_t size, void *ud) { - (void)size; (void)ud; free(data); + (void)size; (void)ud; TINYGLTF3_FREE(data); } static int32_t tg3__fs_write_file(const char *path, uint32_t path_len, @@ -444,7 +569,7 @@ static void tg3__set_default_fs(tg3_fs_callbacks *fs) { #endif static void tg3__model_init(tg3_model *model) { - memset(model, 0, sizeof(*model)); + TINYGLTF3_MEMSET(model, 0, sizeof(*model)); model->default_scene = -1; } @@ -462,7 +587,7 @@ static int tg3__json_number_to_int32(const tg3json_value *v, int32_t *out) { return 1; } real = v->u.real; - if (!isfinite(real) || real < (double)INT32_MIN || real > (double)INT32_MAX) { + if (!tg3__isfinite(real) || real < (double)INT32_MIN || real > (double)INT32_MAX) { return 0; } converted = (int32_t)real; @@ -485,7 +610,7 @@ static int tg3__json_number_to_uint64(const tg3json_value *v, uint64_t *out) { return 1; } real = v->u.real; - if (!isfinite(real) || real < 0.0 || real > max_safe_uint64_real) { + if (!tg3__isfinite(real) || real < 0.0 || real > max_safe_uint64_real) { return 0; } converted = (uint64_t)real; @@ -783,7 +908,7 @@ static int tg3__parse_string_array(tg3__parse_ctx *ctx, const tg3json_value *o, static tg3_value tg3__json_to_value(tg3__parse_ctx *ctx, const tg3json_value *j) { tg3_value v; size_t i; - memset(&v, 0, sizeof(v)); + TINYGLTF3_MEMSET(&v, 0, sizeof(v)); if (!j) return v; switch (j->type) { case TG3JSON_NULL: @@ -839,7 +964,7 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_ tg3_extras_ext *ee) { const tg3json_value *extras_it; const tg3json_value *ext_it; - memset(ee, 0, sizeof(*ee)); + TINYGLTF3_MEMSET(ee, 0, sizeof(*ee)); extras_it = tg3__json_get(o, "extras"); if (extras_it) { if (!ctx->opts.skip_extras_values) { @@ -854,7 +979,7 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_ char *raw = tg3json_stringify(extras_it, &raw_len); if (raw) { ee->extras_json = tg3__arena_str(ctx->arena, raw, (uint32_t)raw_len); - free(raw); + TINYGLTF3_FREE(raw); } } } @@ -867,7 +992,7 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_ if (exts) { for (i = 0; i < count; ++i) { const tg3json_object_entry *entry = tg3json_object_at(ext_it, i); - memset(&exts[i], 0, sizeof(exts[i])); + TINYGLTF3_MEMSET(&exts[i], 0, sizeof(exts[i])); exts[i].name = tg3__arena_str(ctx->arena, entry->key, (uint32_t)entry->key_len); if (!ctx->opts.skip_extras_values) { exts[i].value = tg3__json_to_value(ctx, entry->value); @@ -884,17 +1009,17 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_ char *raw = tg3json_stringify(ext_it, &raw_len); if (raw) { ee->extensions_json = tg3__arena_str(ctx->arena, raw, (uint32_t)raw_len); - free(raw); + TINYGLTF3_FREE(raw); } } } } -static void tg3__init_texture_info(tg3_texture_info *ti) { memset(ti, 0, sizeof(*ti)); ti->index = -1; } -static void tg3__init_normal_texture_info(tg3_normal_texture_info *ti) { memset(ti, 0, sizeof(*ti)); ti->index = -1; ti->scale = 1.0; } -static void tg3__init_occlusion_texture_info(tg3_occlusion_texture_info *ti) { memset(ti, 0, sizeof(*ti)); ti->index = -1; ti->strength = 1.0; } +static void tg3__init_texture_info(tg3_texture_info *ti) { TINYGLTF3_MEMSET(ti, 0, sizeof(*ti)); ti->index = -1; } +static void tg3__init_normal_texture_info(tg3_normal_texture_info *ti) { TINYGLTF3_MEMSET(ti, 0, sizeof(*ti)); ti->index = -1; ti->scale = 1.0; } +static void tg3__init_occlusion_texture_info(tg3_occlusion_texture_info *ti) { TINYGLTF3_MEMSET(ti, 0, sizeof(*ti)); ti->index = -1; ti->strength = 1.0; } static void tg3__init_pbr(tg3_pbr_metallic_roughness *pbr) { - memset(pbr, 0, sizeof(*pbr)); + TINYGLTF3_MEMSET(pbr, 0, sizeof(*pbr)); pbr->base_color_factor[0] = 1.0; pbr->base_color_factor[1] = 1.0; pbr->base_color_factor[2] = 1.0; pbr->base_color_factor[3] = 1.0; pbr->metallic_factor = 1.0; pbr->roughness_factor = 1.0; @@ -902,14 +1027,14 @@ static void tg3__init_pbr(tg3_pbr_metallic_roughness *pbr) { tg3__init_texture_info(&pbr->metallic_roughness_texture); } static void tg3__init_node(tg3_node *n) { - memset(n, 0, sizeof(*n)); + TINYGLTF3_MEMSET(n, 0, sizeof(*n)); n->camera = -1; n->skin = -1; n->mesh = -1; n->light = -1; n->emitter = -1; n->rotation[3] = 1.0; n->scale[0] = 1.0; n->scale[1] = 1.0; n->scale[2] = 1.0; n->matrix[0] = 1.0; n->matrix[5] = 1.0; n->matrix[10] = 1.0; n->matrix[15] = 1.0; } static int tg3__parse_asset(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_asset *asset) { - memset(asset, 0, sizeof(*asset)); + TINYGLTF3_MEMSET(asset, 0, sizeof(*asset)); tg3__parse_string(ctx, o, "version", &asset->version, 0, "/asset"); tg3__parse_string(ctx, o, "generator", &asset->generator, 0, "/asset"); tg3__parse_string(ctx, o, "minVersion", &asset->min_version, 0, "/asset"); @@ -957,20 +1082,20 @@ static int tg3__parse_occlusion_texture_info(tg3__parse_ctx *ctx, const tg3json_ } static int tg3__accessor_type_from_string(const char *s, size_t len) { - if (len == 6 && memcmp(s, "SCALAR", 6) == 0) return TG3_TYPE_SCALAR; - if (len == 4 && memcmp(s, "VEC2", 4) == 0) return TG3_TYPE_VEC2; - if (len == 4 && memcmp(s, "VEC3", 4) == 0) return TG3_TYPE_VEC3; - if (len == 4 && memcmp(s, "VEC4", 4) == 0) return TG3_TYPE_VEC4; - if (len == 4 && memcmp(s, "MAT2", 4) == 0) return TG3_TYPE_MAT2; - if (len == 4 && memcmp(s, "MAT3", 4) == 0) return TG3_TYPE_MAT3; - if (len == 4 && memcmp(s, "MAT4", 4) == 0) return TG3_TYPE_MAT4; + if (len == 6 && TINYGLTF3_MEMCMP(s, "SCALAR", 6) == 0) return TG3_TYPE_SCALAR; + if (len == 4 && TINYGLTF3_MEMCMP(s, "VEC2", 4) == 0) return TG3_TYPE_VEC2; + if (len == 4 && TINYGLTF3_MEMCMP(s, "VEC3", 4) == 0) return TG3_TYPE_VEC3; + if (len == 4 && TINYGLTF3_MEMCMP(s, "VEC4", 4) == 0) return TG3_TYPE_VEC4; + if (len == 4 && TINYGLTF3_MEMCMP(s, "MAT2", 4) == 0) return TG3_TYPE_MAT2; + if (len == 4 && TINYGLTF3_MEMCMP(s, "MAT3", 4) == 0) return TG3_TYPE_MAT3; + if (len == 4 && TINYGLTF3_MEMCMP(s, "MAT4", 4) == 0) return TG3_TYPE_MAT4; return -1; } static int tg3__parse_accessor_sparse(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_accessor_sparse *sparse) { const tg3json_value *it = tg3__json_get(o, "sparse"); - memset(sparse, 0, sizeof(*sparse)); + TINYGLTF3_MEMSET(sparse, 0, sizeof(*sparse)); sparse->indices.buffer_view = -1; sparse->values.buffer_view = -1; if (!it) return 1; @@ -1006,7 +1131,7 @@ static int tg3__parse_accessor(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_ tg3_str type_str; uint64_t bo = 0; uint64_t cnt = 0; - memset(acc, 0, sizeof(*acc)); + TINYGLTF3_MEMSET(acc, 0, sizeof(*acc)); acc->buffer_view = -1; acc->component_type = -1; acc->type = -1; @@ -1035,7 +1160,7 @@ static int tg3__uri_is_safe(const char *uri, uint32_t uri_len) { uint32_t i; if (!uri || uri_len == 0) return 0; /* No NUL bytes — fopen would truncate; protects against smuggling. */ - if (memchr(uri, '\0', uri_len) != NULL) return 0; + if (TINYGLTF3_MEMCHR(uri, '\0', uri_len) != NULL) return 0; /* Reject absolute paths (POSIX and Windows). */ if (uri[0] == '/' || uri[0] == '\\') return 0; /* Reject Windows drive prefixes like "C:". */ @@ -1075,14 +1200,14 @@ static int tg3__load_external_file(tg3__parse_ctx *ctx, uint8_t **out_data, uint if (uri_len >= sizeof(path_buf)) return 0; if (ctx->base_dir_len > 0) { if (ctx->base_dir_len >= sizeof(path_buf) - uri_len - 1u) return 0; - memcpy(path_buf, ctx->base_dir, ctx->base_dir_len); + TINYGLTF3_MEMCPY(path_buf, ctx->base_dir, ctx->base_dir_len); path_len = ctx->base_dir_len; if (path_len > 0 && path_buf[path_len - 1u] != '/' && path_buf[path_len - 1u] != '\\') { path_buf[path_len++] = '/'; } } if (path_len + uri_len >= sizeof(path_buf)) return 0; - memcpy(path_buf + path_len, uri, uri_len); + TINYGLTF3_MEMCPY(path_buf + path_len, uri, uri_len); path_len += uri_len; path_buf[path_len] = '\0'; ok = ctx->opts.fs.read_file(out_data, out_size, path_buf, path_len, ctx->opts.fs.user_data); @@ -1111,7 +1236,7 @@ static int tg3__load_external_file(tg3__parse_ctx *ctx, uint8_t **out_data, uint static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_buffer *buf, int32_t buf_idx) { uint64_t byte_length = 0; - memset(buf, 0, sizeof(*buf)); + TINYGLTF3_MEMSET(buf, 0, sizeof(*buf)); tg3__parse_string(ctx, o, "name", &buf->name, 0, "/buffer"); tg3__parse_string(ctx, o, "uri", &buf->uri, 0, "/buffer"); tg3__parse_uint64(ctx, o, "byteLength", &byte_length, 1, "/buffer"); @@ -1140,7 +1265,7 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o, "OOM for buffer data", NULL, -1); return 0; } - if (byte_length > 0) memcpy(data, ctx->bin_data, (size_t)byte_length); + if (byte_length > 0) TINYGLTF3_MEMCPY(data, ctx->bin_data, (size_t)byte_length); buf->data.data = data; buf->data.count = byte_length; } else if (buf->uri.len > 0) { @@ -1148,7 +1273,7 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o, size_t decoded_len = 0; char mime[64]; uint8_t *decoded; - memset(mime, 0, sizeof(mime)); + TINYGLTF3_MEMSET(mime, 0, sizeof(mime)); decoded = tg3__decode_data_uri(ctx->arena, buf->uri.data, buf->uri.len, &decoded_len, mime, sizeof(mime)); if (!decoded && byte_length > 0) { tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_DATA_URI_DECODE, @@ -1171,7 +1296,7 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o, } data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)file_size); if (data) { - memcpy(data, file_data, (size_t)file_size); + TINYGLTF3_MEMCPY(data, file_data, (size_t)file_size); buf->data.data = data; buf->data.count = file_size; } else if (file_size > 0) { @@ -1189,7 +1314,7 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o, static int tg3__parse_buffer_view(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_buffer_view *bv) { uint64_t val = 0; int32_t stride = 0; - memset(bv, 0, sizeof(*bv)); + TINYGLTF3_MEMSET(bv, 0, sizeof(*bv)); bv->buffer = -1; tg3__parse_string(ctx, o, "name", &bv->name, 0, "/bufferView"); tg3__parse_int(ctx, o, "buffer", &bv->buffer, 1, "/bufferView"); @@ -1218,7 +1343,7 @@ static int tg3__parse_buffer_view(tg3__parse_ctx *ctx, const tg3json_value *o, t static int tg3__parse_image(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_image *img, int32_t img_idx) { (void)img_idx; - memset(img, 0, sizeof(*img)); + TINYGLTF3_MEMSET(img, 0, sizeof(*img)); img->width = -1; img->height = -1; img->component = -1; img->bits = -1; img->pixel_type = -1; img->buffer_view = -1; tg3__parse_string(ctx, o, "name", &img->name, 0, "/image"); tg3__parse_string(ctx, o, "uri", &img->uri, 0, "/image"); @@ -1230,7 +1355,7 @@ static int tg3__parse_image(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_ima } static int tg3__parse_sampler(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_sampler *samp) { - memset(samp, 0, sizeof(*samp)); + TINYGLTF3_MEMSET(samp, 0, sizeof(*samp)); samp->min_filter = -1; samp->mag_filter = -1; samp->wrap_s = TG3_TEXTURE_WRAP_REPEAT; samp->wrap_t = TG3_TEXTURE_WRAP_REPEAT; tg3__parse_string(ctx, o, "name", &samp->name, 0, "/sampler"); @@ -1243,7 +1368,7 @@ static int tg3__parse_sampler(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_s } static int tg3__parse_texture(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_texture *tex) { - memset(tex, 0, sizeof(*tex)); + TINYGLTF3_MEMSET(tex, 0, sizeof(*tex)); tex->sampler = -1; tex->source = -1; tg3__parse_string(ctx, o, "name", &tex->name, 0, "/texture"); tg3__parse_int(ctx, o, "sampler", &tex->sampler, 0, "/texture"); @@ -1255,7 +1380,7 @@ static int tg3__parse_texture(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_t static int tg3__parse_material(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_material *mat) { const tg3json_value *pbr_it; const tg3json_value *ext_it; - memset(mat, 0, sizeof(*mat)); + TINYGLTF3_MEMSET(mat, 0, sizeof(*mat)); tg3__init_pbr(&mat->pbr_metallic_roughness); tg3__init_normal_texture_info(&mat->normal_texture); tg3__init_occlusion_texture_info(&mat->occlusion_texture); @@ -1297,7 +1422,7 @@ static int tg3__parse_material(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_ static int tg3__parse_primitive(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_primitive *prim) { const tg3json_value *attr_it; const tg3json_value *targets_it; - memset(prim, 0, sizeof(*prim)); + TINYGLTF3_MEMSET(prim, 0, sizeof(*prim)); prim->material = -1; prim->indices = -1; prim->mode = TG3_MODE_TRIANGLES; tg3__parse_int(ctx, o, "material", &prim->material, 0, "/primitive"); tg3__parse_int(ctx, o, "indices", &prim->indices, 0, "/primitive"); @@ -1375,7 +1500,7 @@ static int tg3__parse_primitive(tg3__parse_ctx *ctx, const tg3json_value *o, tg3 static int tg3__parse_mesh(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_mesh *mesh) { const tg3json_value *prim_it = tg3__json_get(o, "primitives"); - memset(mesh, 0, sizeof(*mesh)); + TINYGLTF3_MEMSET(mesh, 0, sizeof(*mesh)); tg3__parse_string(ctx, o, "name", &mesh->name, 0, "/mesh"); if (tg3__json_is_array(prim_it)) { size_t count = tg3json_array_size(prim_it); @@ -1421,7 +1546,7 @@ static int tg3__parse_node(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_node } static int tg3__parse_skin(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_skin *skin) { - memset(skin, 0, sizeof(*skin)); + TINYGLTF3_MEMSET(skin, 0, sizeof(*skin)); skin->inverse_bind_matrices = -1; skin->skeleton = -1; tg3__parse_string(ctx, o, "name", &skin->name, 0, "/skin"); tg3__parse_int(ctx, o, "inverseBindMatrices", &skin->inverse_bind_matrices, 0, "/skin"); @@ -1434,7 +1559,7 @@ static int tg3__parse_skin(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_skin static int tg3__parse_animation(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_animation *anim) { const tg3json_value *ch_it = tg3__json_get(o, "channels"); const tg3json_value *samp_it = tg3__json_get(o, "samplers"); - memset(anim, 0, sizeof(*anim)); + TINYGLTF3_MEMSET(anim, 0, sizeof(*anim)); tg3__parse_string(ctx, o, "name", &anim->name, 0, "/animation"); if (tg3__json_is_array(ch_it)) { size_t count = tg3json_array_size(ch_it); @@ -1445,7 +1570,7 @@ static int tg3__parse_animation(tg3__parse_ctx *ctx, const tg3json_value *o, tg3 for (i = 0; i < count; ++i) { const tg3json_value *item = tg3json_array_get(ch_it, i); const tg3json_value *tgt_it; - memset(&channels[i], 0, sizeof(channels[i])); + TINYGLTF3_MEMSET(&channels[i], 0, sizeof(channels[i])); channels[i].sampler = -1; channels[i].target.node = -1; tg3__parse_int(ctx, item, "sampler", &channels[i].sampler, 1, "/animation/channel"); @@ -1471,7 +1596,7 @@ static int tg3__parse_animation(tg3__parse_ctx *ctx, const tg3json_value *o, tg3 for (i = 0; i < count; ++i) { const tg3json_value *item = tg3json_array_get(samp_it, i); tg3_str interp; - memset(&samplers[i], 0, sizeof(samplers[i])); + TINYGLTF3_MEMSET(&samplers[i], 0, sizeof(samplers[i])); samplers[i].input = -1; samplers[i].output = -1; tg3__parse_int(ctx, item, "input", &samplers[i].input, 1, "/animation/sampler"); tg3__parse_int(ctx, item, "output", &samplers[i].output, 1, "/animation/sampler"); @@ -1491,7 +1616,7 @@ static int tg3__parse_animation(tg3__parse_ctx *ctx, const tg3json_value *o, tg3 static int tg3__parse_camera(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_camera *cam) { const tg3json_value *it; - memset(cam, 0, sizeof(*cam)); + TINYGLTF3_MEMSET(cam, 0, sizeof(*cam)); tg3__parse_string(ctx, o, "name", &cam->name, 0, "/camera"); tg3__parse_string(ctx, o, "type", &cam->type, 1, "/camera"); if (cam->type.data && tg3_str_equals_cstr(cam->type, "perspective")) { @@ -1519,7 +1644,7 @@ static int tg3__parse_camera(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_ca static int tg3__parse_scene(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_scene *scene) { const tg3json_value *ext_it; - memset(scene, 0, sizeof(*scene)); + TINYGLTF3_MEMSET(scene, 0, sizeof(*scene)); tg3__parse_string(ctx, o, "name", &scene->name, 0, "/scene"); tg3__parse_int_array(ctx, o, "nodes", &scene->nodes, &scene->nodes_count, 0, "/scene"); ext_it = tg3__json_get(o, "extensions"); @@ -1535,7 +1660,7 @@ static int tg3__parse_scene(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_sce static int tg3__parse_light(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_light *light) { const tg3json_value *spot_it; - memset(light, 0, sizeof(*light)); + TINYGLTF3_MEMSET(light, 0, sizeof(*light)); light->color[0] = 1.0; light->color[1] = 1.0; light->color[2] = 1.0; light->intensity = 1.0; light->spot.outer_cone_angle = 0.7853981634; tg3__parse_string(ctx, o, "name", &light->name, 0, "/light"); @@ -1554,7 +1679,7 @@ static int tg3__parse_light(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_lig } static int tg3__parse_audio_source(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_audio_source *src) { - memset(src, 0, sizeof(*src)); + TINYGLTF3_MEMSET(src, 0, sizeof(*src)); src->buffer_view = -1; tg3__parse_string(ctx, o, "name", &src->name, 0, "/audioSource"); tg3__parse_string(ctx, o, "uri", &src->uri, 0, "/audioSource"); @@ -1566,7 +1691,7 @@ static int tg3__parse_audio_source(tg3__parse_ctx *ctx, const tg3json_value *o, static int tg3__parse_audio_emitter(tg3__parse_ctx *ctx, const tg3json_value *o, tg3_audio_emitter *emitter) { const tg3json_value *pos_it; - memset(emitter, 0, sizeof(*emitter)); + TINYGLTF3_MEMSET(emitter, 0, sizeof(*emitter)); emitter->gain = 1.0; emitter->source = -1; emitter->positional.cone_inner_angle = 6.283185307179586; emitter->positional.cone_outer_angle = 6.283185307179586; @@ -2093,7 +2218,7 @@ static tg3_error_code tg3__parse_from_json(tg3__parse_ctx *ctx, const tg3json_va static void tg3__json_parse_options_from_tg3(const tg3_parse_options *options, tg3json_parse_options *json_options) { - memset(json_options, 0, sizeof(*json_options)); + TINYGLTF3_MEMSET(json_options, 0, sizeof(*json_options)); json_options->depth_limit = TINYGLTF3_MAX_NESTING_DEPTH; json_options->memory_budget = options ? (size_t)options->memory.memory_budget : 0; json_options->max_single_alloc = options ? (size_t)options->memory.max_single_alloc : 0; @@ -2119,13 +2244,13 @@ static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size, "Invalid GLB magic bytes", NULL, -1); return TG3_ERR_GLB_INVALID_MAGIC; } - memcpy(&version, data + 4, 4); + TINYGLTF3_MEMCPY(&version, data + 4, 4); if (version != 2) { tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_INVALID_VERSION, "Unsupported GLB version (expected 2)", NULL, -1); return TG3_ERR_GLB_INVALID_VERSION; } - memcpy(&total_length, data + 8, 4); + TINYGLTF3_MEMCPY(&total_length, data + 8, 4); if ((uint64_t)total_length != size) { tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_SIZE_MISMATCH, "GLB total length does not match data size", NULL, -1); @@ -2134,8 +2259,8 @@ static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size, while (offset + 8 <= (uint64_t)total_length) { uint32_t chunk_length; uint32_t chunk_type; - memcpy(&chunk_length, data + offset, 4); offset += 4; - memcpy(&chunk_type, data + offset, 4); offset += 4; + TINYGLTF3_MEMCPY(&chunk_length, data + offset, 4); offset += 4; + TINYGLTF3_MEMCPY(&chunk_type, data + offset, 4); offset += 4; if (offset + chunk_length > (uint64_t)total_length) { tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_CHUNK_ERROR, "GLB chunk exceeds data size", NULL, -1); @@ -2192,13 +2317,13 @@ TINYGLTF3_API tg3_error_code tg3_parse(tg3_model *model, tg3_error_stack *errors * the caller; tg3_model_free is the sole arena owner on the error path. */ return TG3_ERR_JSON_PARSE; } - memset(&ctx, 0, sizeof(ctx)); + TINYGLTF3_MEMSET(&ctx, 0, sizeof(ctx)); ctx.arena = arena; ctx.errors = errors; ctx.opts = *options; ctx.base_dir = base_dir; ctx.base_dir_len = base_dir_len; -#ifdef TINYGLTF3_ENABLE_FS +#if defined(TINYGLTF3_ENABLE_FS) && !defined(TINYGLTF3_NO_STDLIB) tg3__set_default_fs(&ctx.opts.fs); #endif ret = tg3__parse_from_json(&ctx, &json_doc, model); @@ -2250,7 +2375,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er /* Keep arena alive so error messages stay valid; model_free owns it. */ return TG3_ERR_JSON_PARSE; } - memset(&ctx, 0, sizeof(ctx)); + TINYGLTF3_MEMSET(&ctx, 0, sizeof(ctx)); ctx.arena = arena; ctx.errors = errors; ctx.opts = *options; @@ -2259,7 +2384,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er ctx.is_binary = 1; ctx.bin_data = bin_chunk; ctx.bin_size = bin_chunk_size; -#ifdef TINYGLTF3_ENABLE_FS +#if defined(TINYGLTF3_ENABLE_FS) && !defined(TINYGLTF3_NO_STDLIB) tg3__set_default_fs(&ctx.opts.fs); #endif err = tg3__parse_from_json(&ctx, &json_doc, model); @@ -2298,7 +2423,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_file(tg3_model *model, tg3_error_stack *e if (!filename) return TG3_ERR_FILE_NOT_FOUND; if (options) opts = *options; else tg3_parse_options_init(&opts); -#ifdef TINYGLTF3_ENABLE_FS +#if defined(TINYGLTF3_ENABLE_FS) && !defined(TINYGLTF3_NO_STDLIB) tg3__set_default_fs(&opts.fs); #endif if (!opts.fs.read_file) { @@ -2311,12 +2436,12 @@ TINYGLTF3_API tg3_error_code tg3_parse_file(tg3_model *model, tg3_error_stack *e "Failed to read file", NULL, -1); return TG3_ERR_FILE_NOT_FOUND; } - memset(base_dir_buf, 0, sizeof(base_dir_buf)); + TINYGLTF3_MEMSET(base_dir_buf, 0, sizeof(base_dir_buf)); for (i = 0; i < filename_len; ++i) { if (filename[i] == '/' || filename[i] == '\\') base_dir_len = i; } if (base_dir_len > 0) { - memcpy(base_dir_buf, filename, base_dir_len); + TINYGLTF3_MEMCPY(base_dir_buf, filename, base_dir_len); base_dir_buf[base_dir_len] = '\0'; } result = tg3_parse_auto(model, errors, file_data, file_size, base_dir_buf, base_dir_len, &opts); @@ -2327,7 +2452,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_file(tg3_model *model, tg3_error_stack *e TINYGLTF3_API void tg3_model_free(tg3_model *model) { if (!model) return; if (model->arena_) tg3__arena_destroy(model->arena_); - memset(model, 0, sizeof(*model)); + TINYGLTF3_MEMSET(model, 0, sizeof(*model)); model->default_scene = -1; } @@ -2335,7 +2460,7 @@ static char *tg3__b64_encode(const uint8_t *input, size_t input_len, size_t *out static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; size_t enc_len = ((input_len + 2u) / 3u) * 4u; - char *out = (char *)malloc(enc_len + 1u); + char *out = (char *)TINYGLTF3_MALLOC(enc_len + 1u); size_t i; size_t j = 0; if (!out) return NULL; @@ -2408,7 +2533,7 @@ static int tg3__json_set_string_n(tg3json_value *obj, const char *key, const cha } static int tg3__json_set_string(tg3json_value *obj, const char *key, const char *str) { - return tg3__json_set_string_n(obj, key, str, str ? strlen(str) : 0); + return tg3__json_set_string_n(obj, key, str, str ? TINYGLTF3_STRLEN(str) : 0); } static int tg3__json_array_append_int(tg3json_value *arr, int64_t value) { @@ -2478,7 +2603,7 @@ static int tg3__serialize_uint64(tg3json_value *o, const char *key, uint64_t val static int tg3__serialize_double(tg3json_value *o, const char *key, double val, double default_val, int write_defaults) { - if (fabs(val - default_val) <= 1e-12 && !write_defaults) return 1; + if (tg3__fabs(val - default_val) <= 1e-12 && !write_defaults) return 1; return tg3__json_set_real(o, key, val); } @@ -2724,22 +2849,22 @@ static int tg3__serialize_buffer(const tg3_buffer *b, int wd, int embed, tg3json tg3json_value_free(out); return 0; } - uri = (char *)malloc(prefix_len + enc_len + 1u); + uri = (char *)TINYGLTF3_MALLOC(prefix_len + enc_len + 1u); if (!uri) { - free(encoded); + TINYGLTF3_FREE(encoded); tg3json_value_free(out); return 0; } - memcpy(uri, prefix, prefix_len); - memcpy(uri + prefix_len, encoded, enc_len); + TINYGLTF3_MEMCPY(uri, prefix, prefix_len); + TINYGLTF3_MEMCPY(uri + prefix_len, encoded, enc_len); uri[prefix_len + enc_len] = '\0'; - free(encoded); + TINYGLTF3_FREE(encoded); if (!tg3__json_set_string_n(out, "uri", uri, prefix_len + enc_len)) { - free(uri); + TINYGLTF3_FREE(uri); tg3json_value_free(out); return 0; } - free(uri); + TINYGLTF3_FREE(uri); } if (!tg3__serialize_extras_ext(out, &b->ext)) { tg3json_value_free(out); @@ -2887,9 +3012,9 @@ static int tg3__serialize_material(const tg3_material *m, int wd, tg3json_value has_pbr = 1; } if (!tg3__serialize_double(&pbr, "metallicFactor", p->metallic_factor, 1.0, wd)) goto fail; - if (fabs(p->metallic_factor - 1.0) > 1e-12 || wd) has_pbr = 1; + if (tg3__fabs(p->metallic_factor - 1.0) > 1e-12 || wd) has_pbr = 1; if (!tg3__serialize_double(&pbr, "roughnessFactor", p->roughness_factor, 1.0, wd)) goto fail; - if (fabs(p->roughness_factor - 1.0) > 1e-12 || wd) has_pbr = 1; + if (tg3__fabs(p->roughness_factor - 1.0) > 1e-12 || wd) has_pbr = 1; if (p->base_color_texture.index >= 0) { if (!tg3__serialize_texture_info(&pbr, "baseColorTexture", &p->base_color_texture, wd)) goto fail; has_pbr = 1; @@ -3343,22 +3468,22 @@ TINYGLTF3_API tg3_error_code tg3_write_to_memory(const tg3_model *model, tg3_err } bin_padded = ((uint32_t)bin_len + 3u) & ~3u; total = 12u + 8u + json_padded + ((bin_data && bin_len > 0) ? (8u + bin_padded) : 0u); - glb = (uint8_t *)malloc(total); + glb = (uint8_t *)TINYGLTF3_MALLOC(total); if (!glb) { - free(json_str); + TINYGLTF3_FREE(json_str); tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY, "OOM allocating GLB output", NULL, -1); return TG3_ERR_OUT_OF_MEMORY; } - memcpy(glb, "glTF", 4); + TINYGLTF3_MEMCPY(glb, "glTF", 4); { uint32_t version = 2u; uint32_t json_type = 0x4E4F534Au; - memcpy(glb + 4, &version, 4); - memcpy(glb + 8, &total, 4); - memcpy(glb + 12, &json_padded, 4); - memcpy(glb + 16, &json_type, 4); - memcpy(glb + 20, json_str, json_len); + TINYGLTF3_MEMCPY(glb + 4, &version, 4); + TINYGLTF3_MEMCPY(glb + 8, &total, 4); + TINYGLTF3_MEMCPY(glb + 12, &json_padded, 4); + TINYGLTF3_MEMCPY(glb + 16, &json_type, 4); + TINYGLTF3_MEMCPY(glb + 20, json_str, json_len); } while ((uint32_t)json_len < json_padded) { glb[20u + json_len] = ' '; @@ -3368,25 +3493,25 @@ TINYGLTF3_API tg3_error_code tg3_write_to_memory(const tg3_model *model, tg3_err uint32_t bin_off = 20u + json_padded; uint32_t bin_type = 0x004E4942u; uint32_t i; - memcpy(glb + bin_off, &bin_padded, 4); - memcpy(glb + bin_off + 4u, &bin_type, 4); - memcpy(glb + bin_off + 8u, bin_data, (size_t)bin_len); + TINYGLTF3_MEMCPY(glb + bin_off, &bin_padded, 4); + TINYGLTF3_MEMCPY(glb + bin_off + 4u, &bin_type, 4); + TINYGLTF3_MEMCPY(glb + bin_off + 8u, bin_data, (size_t)bin_len); for (i = (uint32_t)bin_len; i < bin_padded; ++i) glb[bin_off + 8u + i] = 0; } - free(json_str); + TINYGLTF3_FREE(json_str); *out_data = glb; *out_size = total; return TG3_OK; } else { - uint8_t *data = (uint8_t *)malloc(json_len); + uint8_t *data = (uint8_t *)TINYGLTF3_MALLOC(json_len); if (!data) { - free(json_str); + TINYGLTF3_FREE(json_str); tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY, "OOM allocating JSON output", NULL, -1); return TG3_ERR_OUT_OF_MEMORY; } - memcpy(data, json_str, json_len); - free(json_str); + TINYGLTF3_MEMCPY(data, json_str, json_len); + TINYGLTF3_FREE(json_str); *out_data = data; *out_size = (uint64_t)json_len; return TG3_OK; @@ -3403,7 +3528,7 @@ TINYGLTF3_API tg3_error_code tg3_write_to_file(const tg3_model *model, tg3_error int32_t ok; if (options) opts = *options; else tg3_write_options_init(&opts); -#ifdef TINYGLTF3_ENABLE_FS +#if defined(TINYGLTF3_ENABLE_FS) && !defined(TINYGLTF3_NO_STDLIB) tg3__set_default_fs(&opts.fs); #endif if (!opts.fs.write_file) { @@ -3414,7 +3539,7 @@ TINYGLTF3_API tg3_error_code tg3_write_to_file(const tg3_model *model, tg3_error err = tg3_write_to_memory(model, errors, &data, &size, &opts); if (err != TG3_OK) return err; ok = opts.fs.write_file(filename, filename_len, data, size, opts.fs.user_data); - free(data); + TINYGLTF3_FREE(data); if (!ok) { tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_FILE_WRITE, "Failed to write file", NULL, -1); @@ -3425,14 +3550,14 @@ TINYGLTF3_API tg3_error_code tg3_write_to_file(const tg3_model *model, tg3_error TINYGLTF3_API void tg3_write_free(uint8_t *data, const tg3_write_options *options) { (void)options; - free(data); + TINYGLTF3_FREE(data); } TINYGLTF3_API tg3_writer *tg3_writer_create(tg3_write_chunk_fn chunk_fn, void *user_data, const tg3_write_options *options) { - tg3_writer *w = (tg3_writer *)malloc(sizeof(tg3_writer)); + tg3_writer *w = (tg3_writer *)TINYGLTF3_MALLOC(sizeof(tg3_writer)); if (!w) return NULL; - memset(w, 0, sizeof(*w)); + TINYGLTF3_MEMSET(w, 0, sizeof(*w)); w->chunk_fn = chunk_fn; w->user_data = user_data; if (options) w->options = *options; @@ -3508,12 +3633,12 @@ TINYGLTF3_API tg3_error_code tg3_writer_end(tg3_writer *w) { : tg3json_stringify(&w->root, &json_len); if (!json_str) return TG3_ERR_OUT_OF_MEMORY; ok = w->chunk_fn((const uint8_t *)json_str, (uint64_t)json_len, w->user_data); - free(json_str); + TINYGLTF3_FREE(json_str); return ok ? TG3_OK : TG3_ERR_WRITE_FAILED; } TINYGLTF3_API void tg3_writer_destroy(tg3_writer *w) { if (!w) return; tg3json_value_free(&w->root); - free(w); + TINYGLTF3_FREE(w); } diff --git a/tiny_gltf_v3.h b/tiny_gltf_v3.h index 3e4afe0..6cc1583 100644 --- a/tiny_gltf_v3.h +++ b/tiny_gltf_v3.h @@ -116,8 +116,12 @@ /* Assert override */ #ifndef TINYGLTF3_ASSERT +#ifndef TINYGLTF3_NO_STDLIB #include #define TINYGLTF3_ASSERT(x) assert(x) +#else +#define TINYGLTF3_ASSERT(x) ((void)(x)) +#endif #endif /* ====================================================================== @@ -127,8 +131,34 @@ #include #include #include +#ifndef TINYGLTF3_NO_STDLIB #include #include +#endif + +#ifndef TINYGLTF3_MALLOC +#ifndef TINYGLTF3_NO_STDLIB +#define TINYGLTF3_MALLOC(sz) malloc(sz) +#else +#define TINYGLTF3_MALLOC(sz) NULL +#endif +#endif + +#ifndef TINYGLTF3_REALLOC +#ifndef TINYGLTF3_NO_STDLIB +#define TINYGLTF3_REALLOC(ptr, sz) realloc((ptr), (sz)) +#else +#define TINYGLTF3_REALLOC(ptr, sz) NULL +#endif +#endif + +#ifndef TINYGLTF3_FREE +#ifndef TINYGLTF3_NO_STDLIB +#define TINYGLTF3_FREE(ptr) free(ptr) +#else +#define TINYGLTF3_FREE(ptr) ((void)(ptr)) +#endif +#endif /* ====================================================================== * Section 4: Constants and Enums diff --git a/tinygltf_json_c.h b/tinygltf_json_c.h index 380a01d..abe3a2b 100644 --- a/tinygltf_json_c.h +++ b/tinygltf_json_c.h @@ -1,9 +1,24 @@ #ifndef TINYGLTF_JSON_C_H_ #define TINYGLTF_JSON_C_H_ +/* + * Floating-point conversion attribution: + * + * - JSON number parsing in this C11 port is based on fast_float. + * Upstream: https://github.com/fastfloat/fast_float + * Copyright (c) 2021 The fast_float authors. + * License: Apache License 2.0, MIT License, or Boost Software License 1.0. + * + * - JSON number serialization in this C11 port is based on Dragonbox-style + * shortest round-trippable binary floating-point to decimal conversion. + * Upstream: https://github.com/jk-jeon/dragonbox + * Copyright 2020-2024 Junekey Jeon. + * License: Apache License 2.0 with LLVM Exceptions, or Boost Software + * License 1.0. + */ + #include #include -#include #ifdef __cplusplus extern "C" { @@ -111,9 +126,433 @@ char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *o #ifdef TINYGLTF_JSON_C_IMPLEMENTATION -#include -#include -#include +#ifndef TINYGLTF_JSON_NO_STDLIB + #include + #include +#endif + +#ifdef TINYGLTF_JSON_NO_STDLIB +static void *tg3json__memcpy_fallback(void *dst, const void *src, size_t sz) { + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + while (sz--) *d++ = *s++; + return dst; +} + +static void *tg3json__memset_fallback(void *dst, int val, size_t sz) { + unsigned char *d = (unsigned char *)dst; + while (sz--) *d++ = (unsigned char)val; + return dst; +} + +static int tg3json__memcmp_fallback(const void *a, const void *b, size_t sz) { + const unsigned char *pa = (const unsigned char *)a; + const unsigned char *pb = (const unsigned char *)b; + while (sz--) { + if (*pa != *pb) return (int)*pa - (int)*pb; + ++pa; + ++pb; + } + return 0; +} + +static size_t tg3json__strlen_fallback(const char *str) { + const char *p = str; + while (*p) ++p; + return (size_t)(p - str); +} + +#endif + +#ifndef TINYGLTF_JSON_MALLOC +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_MALLOC(sz) NULL +#else +#define TINYGLTF_JSON_MALLOC(sz) malloc(sz) +#endif +#endif +#ifndef TINYGLTF_JSON_REALLOC +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_REALLOC(ptr, sz) NULL +#else +#define TINYGLTF_JSON_REALLOC(ptr, sz) realloc(ptr, sz) +#endif +#endif +#ifndef TINYGLTF_JSON_FREE +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_FREE(ptr) ((void)(ptr)) +#else +#define TINYGLTF_JSON_FREE(ptr) free(ptr) +#endif +#endif + +#ifndef TINYGLTF_JSON_MEMCPY +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_MEMCPY(dst, src, sz) tg3json__memcpy_fallback((dst), (src), (sz)) +#else +#define TINYGLTF_JSON_MEMCPY(dst, src, sz) memcpy(dst, src, sz) +#endif +#endif +#ifndef TINYGLTF_JSON_MEMSET +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_MEMSET(dst, val, sz) tg3json__memset_fallback((dst), (val), (sz)) +#else +#define TINYGLTF_JSON_MEMSET(dst, val, sz) memset(dst, val, sz) +#endif +#endif +#ifndef TINYGLTF_JSON_MEMCMP +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_MEMCMP(a, b, sz) tg3json__memcmp_fallback((a), (b), (sz)) +#else +#define TINYGLTF_JSON_MEMCMP(a, b, sz) memcmp(a, b, sz) +#endif +#endif +#ifndef TINYGLTF_JSON_STRLEN +#ifdef TINYGLTF_JSON_NO_STDLIB +#define TINYGLTF_JSON_STRLEN(str) tg3json__strlen_fallback((str)) +#else +#define TINYGLTF_JSON_STRLEN(str) strlen(str) +#endif +#endif + +static size_t tg3json__itoa(int64_t value, char *buf) { + char *p = buf; + uint64_t val; + char tmp[24]; + int i = 0; + if (value < 0) { + *p++ = '-'; + val = (value == -9223372036854775807LL - 1) ? 9223372036854775808ULL : (uint64_t)(-value); + } else { + val = (uint64_t)value; + } + if (val == 0) { + *p++ = '0'; + *p = '\0'; + return (size_t)(p - buf); + } + while (val > 0) { + tmp[i++] = (char)('0' + (val % 10)); + val /= 10; + } + while (i > 0) { + *p++ = tmp[--i]; + } + *p = '\0'; + return (size_t)(p - buf); +} + +static size_t tg3json__utoa(uint64_t value, char *buf) { + char tmp[32]; + size_t n = 0; + size_t i = 0; + if (value == 0) { + buf[0] = '0'; + buf[1] = '\0'; + return 1; + } + while (value > 0) { + tmp[n++] = (char)('0' + (value % 10u)); + value /= 10u; + } + while (n > 0) buf[i++] = tmp[--n]; + buf[i] = '\0'; + return i; +} + +static uint64_t tg3json__double_bits(double v) { + uint64_t bits = 0; + TINYGLTF_JSON_MEMCPY(&bits, &v, sizeof(bits)); + return bits; +} + +static double tg3json__double_from_bits(uint64_t bits) { + double v = 0.0; + TINYGLTF_JSON_MEMCPY(&v, &bits, sizeof(v)); + return v; +} + +static int tg3json__is_nan_bits(uint64_t bits) { + return ((bits & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL) && + ((bits & 0x000fffffffffffffULL) != 0); +} + +static int tg3json__is_inf_bits(uint64_t bits) { + return (bits & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL; +} + +static long double tg3json__pow10_ld(int exp10) { + long double v = 1.0L; + if (exp10 < 0) { + exp10 = -exp10; + while (exp10 >= 16) { + v *= 1.0e-16L; + exp10 -= 16; + } + while (exp10-- > 0) v *= 0.1L; + } else { + while (exp10 >= 16) { + v *= 1.0e16L; + exp10 -= 16; + } + while (exp10-- > 0) v *= 10.0L; + } + return v; +} + +static int tg3json__parse_f64_c(const char *start, const char *end, double *out) { + const char *p = start; + int neg = 0; + int saw_digit = 0; + int nonzero_seen = 0; + int exp10 = 0; + uint64_t sig = 0; + int sig_digits = 0; + long double extra = 0.0L; + int extra_digits = 0; + int exp_sign = 1; + int exp_lit = 0; + long double v; + + if (p < end && *p == '-') { + neg = 1; + ++p; + } + if (p >= end) return 0; + if (*p == '0') { + saw_digit = 1; + ++p; + } else if (*p >= '1' && *p <= '9') { + do { + int d = *p - '0'; + saw_digit = 1; + nonzero_seen = 1; + if (sig_digits < 19) { + sig = sig * 10u + (uint64_t)d; + ++sig_digits; + } else if (extra_digits < 64) { + extra = extra * 10.0L + (long double)d; + ++extra_digits; + } else { + ++exp10; + } + ++p; + } while (p < end && *p >= '0' && *p <= '9'); + } else { + return 0; + } + if (p < end && *p == '.') { + ++p; + if (p >= end || *p < '0' || *p > '9') return 0; + do { + int d = *p - '0'; + saw_digit = 1; + if (d != 0 || nonzero_seen) { + nonzero_seen = nonzero_seen || (d != 0); + if (sig_digits < 19) { + sig = sig * 10u + (uint64_t)d; + ++sig_digits; + --exp10; + } else if (extra_digits < 64) { + extra = extra * 10.0L + (long double)d; + ++extra_digits; + --exp10; + } else { + --exp10; + } + } else { + --exp10; + } + ++p; + } while (p < end && *p >= '0' && *p <= '9'); + } + if (!saw_digit) return 0; + if (p < end && (*p == 'e' || *p == 'E')) { + ++p; + if (p < end && (*p == '+' || *p == '-')) { + exp_sign = (*p == '-') ? -1 : 1; + ++p; + } + if (p >= end || *p < '0' || *p > '9') return 0; + while (p < end && *p >= '0' && *p <= '9') { + if (exp_lit < 10000) exp_lit = exp_lit * 10 + (*p - '0'); + ++p; + } + exp10 += exp_sign * exp_lit; + } + if (p != end) return 0; + if (!nonzero_seen || sig_digits == 0) { + *out = neg ? tg3json__double_from_bits(0x8000000000000000ULL) : 0.0; + return 1; + } + if (exp10 > 309) return 0; + if (exp10 < -4000) { + *out = neg ? tg3json__double_from_bits(0x8000000000000000ULL) : 0.0; + return 1; + } + v = (long double)sig; + if (extra_digits > 0) { + v = v * tg3json__pow10_ld(extra_digits) + extra; + } + if (exp10 != 0) v *= tg3json__pow10_ld(exp10); + if (neg) v = -v; + *out = (double)v; + if (tg3json__is_inf_bits(tg3json__double_bits(*out))) return 0; + return 1; +} + +static char *tg3json__write_exp(char *p, int exp10) { + char tmp[16]; + size_t n; + *p++ = 'e'; + if (exp10 < 0) { + *p++ = '-'; + exp10 = -exp10; + } + n = tg3json__utoa((uint64_t)exp10, tmp); + TINYGLTF_JSON_MEMCPY(p, tmp, n); + return p + n; +} + +static char *tg3json__format_decimal_digits(char *out, const char *digits, + int ndigits, int dec_exp, + int negative) { + char *p = out; + int i; + int output_exp = dec_exp + ndigits - 1; + if (negative) *p++ = '-'; + if (output_exp < -4 || output_exp >= 16) { + *p++ = digits[0]; + if (ndigits > 1) { + *p++ = '.'; + for (i = 1; i < ndigits; ++i) *p++ = digits[i]; + } + return tg3json__write_exp(p, output_exp); + } + if (dec_exp >= 0) { + for (i = 0; i < ndigits; ++i) *p++ = digits[i]; + for (i = 0; i < dec_exp; ++i) *p++ = '0'; + return p; + } + if (dec_exp + ndigits > 0) { + int int_digits = dec_exp + ndigits; + for (i = 0; i < int_digits; ++i) *p++ = digits[i]; + *p++ = '.'; + for (; i < ndigits; ++i) *p++ = digits[i]; + return p; + } + *p++ = '0'; + *p++ = '.'; + for (i = 0; i < -(dec_exp + ndigits); ++i) *p++ = '0'; + for (i = 0; i < ndigits; ++i) *p++ = digits[i]; + return p; +} + +static int tg3json__same_f64(double a, double b) { + return tg3json__double_bits(a) == tg3json__double_bits(b); +} + +static char *tg3json__dtoa_c(double value, char *buf) { + uint64_t bits = tg3json__double_bits(value); + int negative = (int)(bits >> 63); + uint64_t abits = bits & 0x7fffffffffffffffULL; + long double x; + int dec_e = 0; + char digits[24]; + int ndigits = 17; + int i; + char best[80]; + size_t best_len; + + if (tg3json__is_nan_bits(bits)) { + TINYGLTF_JSON_MEMCPY(buf, "nan", 3); + return buf + 3; + } + if (tg3json__is_inf_bits(bits)) { + if (negative) { + TINYGLTF_JSON_MEMCPY(buf, "-inf", 4); + return buf + 4; + } + TINYGLTF_JSON_MEMCPY(buf, "inf", 3); + return buf + 3; + } + if (abits == 0) { + *buf++ = '0'; + return buf; + } + if (bits == 0x3ff0000000000000ULL) { + *buf++ = '1'; + return buf; + } + if (bits == 0xbff0000000000000ULL) { + *buf++ = '-'; + *buf++ = '1'; + return buf; + } + + x = negative ? -(long double)value : (long double)value; + while (x >= 1.0e16L) { + x *= 1.0e-16L; + dec_e += 16; + } + while (x >= 10.0L) { + x *= 0.1L; + ++dec_e; + } + while (x < 1.0L) { + x *= 10.0L; + --dec_e; + } + for (i = 0; i < 18; ++i) { + int d = (int)x; + if (d < 0) d = 0; + if (d > 9) d = 9; + digits[i] = (char)('0' + d); + x = (x - (long double)d) * 10.0L; + } + if (digits[17] >= '5') { + int carry = 1; + for (i = 16; i >= 0 && carry; --i) { + if (digits[i] == '9') { + digits[i] = '0'; + } else { + digits[i]++; + carry = 0; + } + } + if (carry) { + digits[0] = '1'; + for (i = 1; i < 17; ++i) digits[i] = '0'; + ++dec_e; + } + } + while (ndigits > 1 && digits[ndigits - 1] == '0') --ndigits; + + { + char *end = tg3json__format_decimal_digits(best, digits, ndigits, + dec_e - ndigits + 1, + negative); + *end = '\0'; + best_len = (size_t)(end - best); + } + for (i = ndigits - 1; i >= 1; --i) { + char candidate[80]; + char *end; + double parsed = 0.0; + end = tg3json__format_decimal_digits(candidate, digits, i, + dec_e - i + 1, negative); + *end = '\0'; + if (tg3json__parse_f64_c(candidate, end, &parsed) && + tg3json__same_f64(parsed, value)) { + TINYGLTF_JSON_MEMCPY(best, candidate, (size_t)(end - candidate) + 1); + best_len = (size_t)(end - candidate); + } else { + break; + } + } + TINYGLTF_JSON_MEMCPY(buf, best, best_len); + return buf + best_len; +} typedef struct tg3json__parser { const char *cur; @@ -136,27 +575,27 @@ typedef struct tg3json__buffer { static void tg3json__init_value(tg3json_value *value) { if (!value) return; - memset(value, 0, sizeof(*value)); + TINYGLTF_JSON_MEMSET(value, 0, sizeof(*value)); value->type = TG3JSON_NULL; } static char *tg3json__strndup_local(const char *src, size_t len) { - char *dst = (char *)malloc(len + 1); + char *dst = (char *)TINYGLTF_JSON_MALLOC(len + 1); if (!dst) return NULL; - if (len > 0) memcpy(dst, src, len); + if (len > 0) TINYGLTF_JSON_MEMCPY(dst, src, len); dst[len] = '\0'; return dst; } static void *tg3json__parser_alloc(tg3json__parser *parser, size_t size) { void *ptr; - if (!parser) return malloc(size); + if (!parser) return TINYGLTF_JSON_MALLOC(size); if (parser->max_single_alloc && size > parser->max_single_alloc) return NULL; if (parser->memory_budget && (size > parser->memory_budget || parser->allocated > parser->memory_budget - size)) { return NULL; } - ptr = malloc(size); + ptr = TINYGLTF_JSON_MALLOC(size); if (!ptr) return NULL; parser->allocated += size; return ptr; @@ -178,7 +617,7 @@ static int tg3json__reserve_bytes(void **ptr, size_t elem_size, } if (elem_size != 0 && new_cap > ((size_t)-1) / elem_size) return 0; - new_ptr = realloc(*ptr, elem_size * new_cap); + new_ptr = TINYGLTF_JSON_REALLOC(*ptr, elem_size * new_cap); if (!new_ptr) return 0; *ptr = new_ptr; *capacity = new_cap; @@ -215,7 +654,7 @@ static int tg3json__reserve_bytes_parser(tg3json__parser *parser, void **ptr, return 0; } } - new_ptr = realloc(*ptr, new_bytes); + new_ptr = TINYGLTF_JSON_REALLOC(*ptr, new_bytes); if (!new_ptr) return 0; if (parser && new_bytes > old_bytes) parser->allocated += new_bytes - old_bytes; *ptr = new_ptr; @@ -242,7 +681,7 @@ static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len buf->len + len + 1, &buf->cap)) { return 0; } - memcpy(buf->data + buf->len, src, len); + TINYGLTF_JSON_MEMCPY(buf->data + buf->len, src, len); buf->len += len; buf->data[buf->len] = '\0'; return 1; @@ -300,7 +739,7 @@ static int tg3json__parse_string_raw(tg3json__parser *parser, char **out_str, size_t *out_len) { tg3json__buffer buf; const char *start; - memset(&buf, 0, sizeof(buf)); + TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); buf.parser = parser; if (parser->cur >= parser->end || *parser->cur != '"') { @@ -356,7 +795,7 @@ static int tg3json__parse_string_raw(tg3json__parser *parser, } default: tg3json__set_error(parser, parser->cur); - free(buf.data); + TINYGLTF_JSON_FREE(buf.data); return 0; } ++parser->cur; @@ -368,12 +807,12 @@ static int tg3json__parse_string_raw(tg3json__parser *parser, } tg3json__set_error(parser, parser->cur); - free(buf.data); + TINYGLTF_JSON_FREE(buf.data); return 0; oom: tg3json__set_error(parser, parser->cur); - free(buf.data); + TINYGLTF_JSON_FREE(buf.data); return 0; } @@ -456,7 +895,7 @@ fail: --count; tg3json_value_free(&items[count]); } - free(items); + TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; @@ -465,7 +904,7 @@ oom: --count; tg3json_value_free(&items[count]); } - free(items); + TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; } @@ -494,18 +933,18 @@ static int tg3json__parse_object(tg3json__parser *parser, size_t depth, if (!tg3json__parse_string_raw(parser, &key, &key_len)) goto fail; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end || *parser->cur != ':') { - free(key); + TINYGLTF_JSON_FREE(key); goto fail; } ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (!tg3json__parse_value(parser, depth + 1, &value)) { - free(key); + TINYGLTF_JSON_FREE(key); goto fail; } if (!tg3json__reserve_bytes_parser(parser, (void **)&items, sizeof(*items), count + 1, &cap)) { - free(key); + TINYGLTF_JSON_FREE(key); tg3json_value_free(&value); goto oom; } @@ -513,7 +952,7 @@ static int tg3json__parse_object(tg3json__parser *parser, size_t depth, items[count].key_len = key_len; items[count].value = (tg3json_value *)tg3json__parser_alloc(parser, sizeof(tg3json_value)); if (!items[count].value) { - free(key); + TINYGLTF_JSON_FREE(key); tg3json_value_free(&value); goto oom; } @@ -539,26 +978,26 @@ static int tg3json__parse_object(tg3json__parser *parser, size_t depth, fail: while (count > 0) { --count; - free(items[count].key); + TINYGLTF_JSON_FREE(items[count].key); if (items[count].value) { tg3json_value_free(items[count].value); - free(items[count].value); + TINYGLTF_JSON_FREE(items[count].value); } } - free(items); + TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; oom: while (count > 0) { --count; - free(items[count].key); + TINYGLTF_JSON_FREE(items[count].key); if (items[count].value) { tg3json_value_free(items[count].value); - free(items[count].value); + TINYGLTF_JSON_FREE(items[count].value); } } - free(items); + TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; } @@ -567,9 +1006,6 @@ static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_val const char *start = parser->cur; const char *p = parser->cur; int is_real = 0; - size_t len; - char stack_buf[128]; - char *num_buf = stack_buf; if (*p == '-') ++p; if (p >= parser->end) goto fail; @@ -605,59 +1041,55 @@ static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_val is_real = 1; } - len = (size_t)(p - start); - if (len + 1 > sizeof(stack_buf)) { - num_buf = (char *)tg3json__parser_alloc(parser, len + 1); - if (!num_buf) goto oom; - } - memcpy(num_buf, start, len); - num_buf[len] = '\0'; - { - char *endptr = NULL; - errno = 0; - out_value->u.real = strtod(num_buf, &endptr); - if (endptr != num_buf + len) { - if (num_buf != stack_buf) free(num_buf); - goto fail; - } + if (!tg3json__parse_f64_c(start, p, &out_value->u.real)) goto fail; if (parser->parse_float32) out_value->u.real = (double)(float)out_value->u.real; out_value->type = TG3JSON_REAL; parser->cur = p; - if (num_buf != stack_buf) free(num_buf); return 1; } fail: tg3json__set_error(parser, parser->cur); return 0; -oom: - tg3json__set_error(parser, parser->cur); - return 0; } static int tg3json__parse_value(tg3json__parser *parser, size_t depth, tg3json_value *out_value) { - tg3json__init_value(out_value); - if (depth > parser->depth_limit) { + if (parser->depth_limit && depth > parser->depth_limit) { tg3json__set_error(parser, parser->cur); return 0; } + parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end) { tg3json__set_error(parser, parser->cur); return 0; } + switch (*parser->cur) { + case '[': + return tg3json__parse_array(parser, depth, out_value); + case '{': + return tg3json__parse_object(parser, depth, out_value); + case '"': { + char *str = NULL; + size_t len = 0; + if (!tg3json__parse_string_raw(parser, &str, &len)) return 0; + out_value->type = TG3JSON_STRING; + out_value->u.string.ptr = str; + out_value->u.string.len = len; + return 1; + } case 'n': - if ((size_t)(parser->end - parser->cur) >= 4 && memcmp(parser->cur, "null", 4) == 0) { + if ((size_t)(parser->end - parser->cur) >= 4 && TINYGLTF_JSON_MEMCMP(parser->cur, "null", 4) == 0) { out_value->type = TG3JSON_NULL; parser->cur += 4; return 1; } break; case 't': - if ((size_t)(parser->end - parser->cur) >= 4 && memcmp(parser->cur, "true", 4) == 0) { + if ((size_t)(parser->end - parser->cur) >= 4 && TINYGLTF_JSON_MEMCMP(parser->cur, "true", 4) == 0) { out_value->type = TG3JSON_BOOL; out_value->u.boolean = 1; parser->cur += 4; @@ -665,27 +1097,17 @@ static int tg3json__parse_value(tg3json__parser *parser, size_t depth, } break; case 'f': - if ((size_t)(parser->end - parser->cur) >= 5 && memcmp(parser->cur, "false", 5) == 0) { + if ((size_t)(parser->end - parser->cur) >= 5 && TINYGLTF_JSON_MEMCMP(parser->cur, "false", 5) == 0) { out_value->type = TG3JSON_BOOL; out_value->u.boolean = 0; parser->cur += 5; return 1; } break; - case '"': - out_value->type = TG3JSON_STRING; - return tg3json__parse_string_raw(parser, &out_value->u.string.ptr, - &out_value->u.string.len); - case '[': - return tg3json__parse_array(parser, depth, out_value); - case '{': - return tg3json__parse_object(parser, depth, out_value); default: - if (*parser->cur == '-' || (*parser->cur >= '0' && *parser->cur <= '9')) { - return tg3json__parse_number(parser, out_value); - } - break; + return tg3json__parse_number(parser, out_value); } + tg3json__set_error(parser, parser->cur); return 0; } @@ -695,40 +1117,50 @@ int tg3json_parse_n_opts(const char *data, size_t len, tg3json_value *out_value, const char **out_error_pos) { tg3json__parser parser; - if (!data || !out_value) { - if (out_error_pos) *out_error_pos = data; - return 0; - } + int ok; + + if (out_error_pos) *out_error_pos = NULL; + if (!out_value) return 0; tg3json__init_value(out_value); + if (!data) return 0; + parser.cur = data; parser.end = data + len; parser.error = NULL; - parser.depth_limit = (options && options->depth_limit) ? options->depth_limit : 512; + parser.depth_limit = options ? (options->depth_limit ? options->depth_limit : 256) : 256; parser.memory_budget = options ? options->memory_budget : 0; parser.max_single_alloc = options ? options->max_single_alloc : 0; parser.max_string_length = options ? options->max_string_length : 0; parser.allocated = 0; parser.parse_float32 = options ? options->parse_float32 : 0; - if (!tg3json__parse_value(&parser, 0, out_value)) { - if (out_error_pos) *out_error_pos = parser.error; + parser.cur = tg3json__skip_ws(parser.cur, parser.end); + if (parser.cur >= parser.end) { + if (out_error_pos) *out_error_pos = data; + return 0; + } + + ok = tg3json__parse_value(&parser, 0, out_value); + if (!ok) { + if (out_error_pos) *out_error_pos = parser.error ? parser.error : parser.cur; tg3json_value_free(out_value); return 0; } + parser.cur = tg3json__skip_ws(parser.cur, parser.end); if (parser.cur != parser.end) { if (out_error_pos) *out_error_pos = parser.cur; tg3json_value_free(out_value); return 0; } - if (out_error_pos) *out_error_pos = NULL; + return 1; } int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, tg3json_value *out_value, const char **out_error_pos) { tg3json_parse_options options; - memset(&options, 0, sizeof(options)); + TINYGLTF_JSON_MEMSET(&options, 0, sizeof(options)); options.depth_limit = depth_limit; return tg3json_parse_n_opts(data, len, &options, out_value, out_error_pos); } @@ -748,23 +1180,23 @@ void tg3json_value_free(tg3json_value *value) { if (!value) return; switch (value->type) { case TG3JSON_STRING: - free(value->u.string.ptr); + TINYGLTF_JSON_FREE(value->u.string.ptr); break; case TG3JSON_ARRAY: for (i = 0; i < value->u.array.count; ++i) { tg3json_value_free(&value->u.array.items[i]); } - free(value->u.array.items); + TINYGLTF_JSON_FREE(value->u.array.items); break; case TG3JSON_OBJECT: for (i = 0; i < value->u.object.count; ++i) { - free(value->u.object.items[i].key); + TINYGLTF_JSON_FREE(value->u.object.items[i].key); if (value->u.object.items[i].value) { tg3json_value_free(value->u.object.items[i].value); - free(value->u.object.items[i].value); + TINYGLTF_JSON_FREE(value->u.object.items[i].value); } } - free(value->u.object.items); + TINYGLTF_JSON_FREE(value->u.object.items); break; case TG3JSON_NULL: case TG3JSON_BOOL: @@ -811,7 +1243,7 @@ int tg3json_value_init_string_n(tg3json_value *value, const char *str, size_t le } int tg3json_value_init_string(tg3json_value *value, const char *str) { - return tg3json_value_init_string_n(value, str, str ? strlen(str) : 0); + return tg3json_value_init_string_n(value, str, str ? TINYGLTF_JSON_STRLEN(str) : 0); } void tg3json_value_init_array(tg3json_value *value) { @@ -872,7 +1304,7 @@ const tg3json_value *tg3json_object_get_n(const tg3json_value *object, if (!object || object->type != TG3JSON_OBJECT) return NULL; for (i = 0; i < object->u.object.count; ++i) { const tg3json_object_entry *entry = &object->u.object.items[i]; - if (entry->key_len == key_len && memcmp(entry->key, key, key_len) == 0) { + if (entry->key_len == key_len && TINYGLTF_JSON_MEMCMP(entry->key, key, key_len) == 0) { return entry->value; } } @@ -882,7 +1314,7 @@ const tg3json_value *tg3json_object_get_n(const tg3json_value *object, const tg3json_value *tg3json_object_get(const tg3json_value *object, const char *key) { if (!key) return NULL; - return tg3json_object_get_n(object, key, strlen(key)); + return tg3json_object_get_n(object, key, TINYGLTF_JSON_STRLEN(key)); } tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, @@ -891,7 +1323,7 @@ tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, if (!object || object->type != TG3JSON_OBJECT) return NULL; for (i = 0; i < object->u.object.count; ++i) { tg3json_object_entry *entry = &object->u.object.items[i]; - if (entry->key_len == key_len && memcmp(entry->key, key, key_len) == 0) { + if (entry->key_len == key_len && TINYGLTF_JSON_MEMCMP(entry->key, key, key_len) == 0) { return entry->value; } } @@ -900,7 +1332,7 @@ tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, tg3json_value *tg3json_object_get_mut(tg3json_value *object, const char *key) { if (!key) return NULL; - return tg3json_object_get_mut_n(object, key, strlen(key)); + return tg3json_object_get_mut_n(object, key, TINYGLTF_JSON_STRLEN(key)); } const tg3json_object_entry *tg3json_object_at(const tg3json_value *object, @@ -938,9 +1370,9 @@ int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key entry->key = tg3json__strndup_local(key, key_len); if (!entry->key) return 0; entry->key_len = key_len; - entry->value = (tg3json_value *)malloc(sizeof(tg3json_value)); + entry->value = (tg3json_value *)TINYGLTF_JSON_MALLOC(sizeof(tg3json_value)); if (!entry->value) { - free(entry->key); + TINYGLTF_JSON_FREE(entry->key); entry->key = NULL; entry->key_len = 0; return 0; @@ -954,7 +1386,7 @@ int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key int tg3json_object_set_take(tg3json_value *object, const char *key, tg3json_value *value) { if (!key) return 0; - return tg3json_object_set_take_n(object, key, strlen(key), value); + return tg3json_object_set_take_n(object, key, TINYGLTF_JSON_STRLEN(key), value); } int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key_len, @@ -972,7 +1404,7 @@ int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key int tg3json_object_set_copy(tg3json_value *object, const char *key, const tg3json_value *value) { if (!key) return 0; - return tg3json_object_set_copy_n(object, key, strlen(key), value); + return tg3json_object_set_copy_n(object, key, TINYGLTF_JSON_STRLEN(key), value); } const tg3json_value *tg3json_array_get(const tg3json_value *array, size_t index) { @@ -1032,26 +1464,19 @@ static int tg3json__stringify_value_ex(tg3json__buffer *buf, const tg3json_value return value->u.boolean ? tg3json__buf_append(buf, "true", 4) : tg3json__buf_append(buf, "false", 5); case TG3JSON_INT: - snprintf(numbuf, sizeof(numbuf), "%lld", (long long)value->u.integer); - return tg3json__buf_append(buf, numbuf, strlen(numbuf)); + tg3json__itoa(value->u.integer, numbuf); + return tg3json__buf_append(buf, numbuf, TINYGLTF_JSON_STRLEN(numbuf)); case TG3JSON_REAL: - snprintf(numbuf, sizeof(numbuf), "%.17g", value->u.real); { + char *end = tg3json__dtoa_c(value->u.real, numbuf); + *end = '\0'; const char *b = numbuf; if (*b == '-') ++b; if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I') { return tg3json__buf_append(buf, "null", 4); } } - if (!strchr(numbuf, '.') && !strchr(numbuf, 'e') && !strchr(numbuf, 'E')) { - size_t nlen = strlen(numbuf); - if (nlen + 2 < sizeof(numbuf)) { - numbuf[nlen] = '.'; - numbuf[nlen + 1] = '0'; - numbuf[nlen + 2] = '\0'; - } - } - return tg3json__buf_append(buf, numbuf, strlen(numbuf)); + return tg3json__buf_append(buf, numbuf, TINYGLTF_JSON_STRLEN(numbuf)); case TG3JSON_STRING: if (!tg3json__buf_putc(buf, '"')) return 0; for (i = 0; i < value->u.string.len; ++i) { @@ -1066,7 +1491,13 @@ static int tg3json__stringify_value_ex(tg3json__buffer *buf, const tg3json_value case '\t': if (!tg3json__buf_append(buf, "\\t", 2)) return 0; break; default: if (c < 0x20u) { - snprintf(numbuf, sizeof(numbuf), "\\u%04x", (unsigned int)c); + numbuf[0] = '\\'; + numbuf[1] = 'u'; + numbuf[2] = '0'; + numbuf[3] = '0'; + numbuf[4] = "0123456789abcdef"[(c >> 4) & 0xf]; + numbuf[5] = "0123456789abcdef"[c & 0xf]; + numbuf[6] = '\0'; if (!tg3json__buf_append(buf, numbuf, 6)) return 0; } else { if (!tg3json__buf_putc(buf, (char)c)) return 0; @@ -1107,14 +1538,14 @@ static int tg3json__stringify_value_ex(tg3json__buffer *buf, const tg3json_value char *tg3json_stringify(const tg3json_value *value, size_t *out_len) { tg3json__buffer buf; - memset(&buf, 0, sizeof(buf)); + TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); if (!value || !tg3json__stringify_value_ex(&buf, value, -1, 0)) { - free(buf.data); + TINYGLTF_JSON_FREE(buf.data); if (out_len) *out_len = 0; return NULL; } if (!buf.data) { - buf.data = (char *)malloc(1); + buf.data = (char *)TINYGLTF_JSON_MALLOC(1); if (!buf.data) { if (out_len) *out_len = 0; return NULL; @@ -1127,14 +1558,14 @@ char *tg3json_stringify(const tg3json_value *value, size_t *out_len) { char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *out_len) { tg3json__buffer buf; - memset(&buf, 0, sizeof(buf)); + TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); if (!value || !tg3json__stringify_value_ex(&buf, value, indent, 0)) { - free(buf.data); + TINYGLTF_JSON_FREE(buf.data); if (out_len) *out_len = 0; return NULL; } if (!buf.data) { - buf.data = (char *)malloc(1); + buf.data = (char *)TINYGLTF_JSON_MALLOC(1); if (!buf.data) { if (out_len) *out_len = 0; return NULL;