mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-08 03:03:50 +00:00
Harden v3 numeric parsing and add C fuzz harness
Reject non-finite/out-of-range JSON numbers in int32/uint64 fields and array/attribute elements instead of silently truncating, initialize the model on parse-file failure, and free the partial JSON document when the root is not an object. Adds a pure-C libFuzzer harness (fuzz_gltf_v3_c) alongside the existing C++ one and tests covering the new failure modes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,3 +5,7 @@ all: ../tiny_gltf.h
|
||||
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
|
||||
clang -I../ -std=c11 -g -O0 -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
|
||||
|
||||
tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
|
||||
clang -I../ -std=c11 -g -O0 -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
|
||||
|
||||
@@ -129,6 +129,89 @@ static int check_minimal_write_roundtrip(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int check_parse_file_failure_initializes_model(void) {
|
||||
tg3_model model;
|
||||
tg3_error_stack errors;
|
||||
tg3_parse_options opts;
|
||||
tg3_error_code err;
|
||||
|
||||
memset(&model, 0xA5, sizeof(model));
|
||||
tg3_error_stack_init(&errors);
|
||||
tg3_parse_options_init(&opts);
|
||||
|
||||
err = tg3_parse_file(&model, &errors, "scene.gltf", 10, &opts);
|
||||
if (err != TG3_ERR_FS_NOT_AVAILABLE) {
|
||||
fprintf(stderr, "tg3_parse_file unexpected error: %d\n", (int)err);
|
||||
tg3_error_stack_free(&errors);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (model.default_scene != -1) {
|
||||
fprintf(stderr, "tg3_parse_file did not initialize model on failure\n");
|
||||
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_non_object_root_rejected(void) {
|
||||
static const uint8_t json[] = "\"not an object\"";
|
||||
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
|
||||
if (err != TG3_ERR_JSON_PARSE) {
|
||||
fprintf(stderr, "non-object root returned unexpected error: %d\n", (int)err);
|
||||
tg3_model_free(&model);
|
||||
tg3_error_stack_free(&errors);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (model.default_scene != -1) {
|
||||
fprintf(stderr, "non-object root left model in unexpected state\n");
|
||||
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_huge_integer_field_rejected(void) {
|
||||
static const uint8_t json[] =
|
||||
"{\"asset\":{\"version\":\"2.0\"},\"scene\":6.66667e+70}";
|
||||
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
|
||||
if (err != TG3_ERR_JSON_PARSE) {
|
||||
fprintf(stderr, "huge integer-like field returned unexpected error: %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;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (!check_minimal_parse()) {
|
||||
return 1;
|
||||
@@ -136,5 +219,14 @@ int main(void) {
|
||||
if (!check_minimal_write_roundtrip()) {
|
||||
return 1;
|
||||
}
|
||||
if (!check_parse_file_failure_initializes_model()) {
|
||||
return 1;
|
||||
}
|
||||
if (!check_non_object_root_rejected()) {
|
||||
return 1;
|
||||
}
|
||||
if (!check_huge_integer_field_rejected()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
# tests/v3/fuzzer/Makefile — Build libFuzzer harness for tinygltf v3
|
||||
# tests/v3/fuzzer/Makefile — Build libFuzzer harnesses for tinygltf v3
|
||||
#
|
||||
# Requires: clang++ with libFuzzer support
|
||||
# Requires: clang/clang++ with libFuzzer support
|
||||
#
|
||||
# Targets:
|
||||
# make — build fuzzer with ASan + UBSan
|
||||
# make run — run fuzzer with default settings
|
||||
# make — build both harnesses with ASan + UBSan
|
||||
# make run — run the dedicated pure-C v3 harness
|
||||
# make run-cpp — run the legacy header-implementation harness
|
||||
# make seed — generate seed corpus from test models
|
||||
# make clean — remove binaries and corpus
|
||||
|
||||
CC = clang
|
||||
CXX = clang++
|
||||
CCFLAGS = -g -O1 -std=c11
|
||||
CXXFLAGS = -g -O1 -std=c++17 -fno-rtti -fno-exceptions
|
||||
SANITIZE = -fsanitize=fuzzer,address,undefined
|
||||
INCLUDES = -I../../..
|
||||
|
||||
FUZZER = fuzz_gltf_v3
|
||||
FUZZER_C = fuzz_gltf_v3_c
|
||||
CORPUS = corpus
|
||||
ARTIFACTS = artifacts
|
||||
|
||||
@@ -21,16 +25,28 @@ ARTIFACTS = artifacts
|
||||
MAX_LEN ?= 65536
|
||||
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
||||
MAX_TIME ?= 0
|
||||
FUZZ_ENV ?= LSAN_OPTIONS=detect_leaks=0
|
||||
|
||||
.PHONY: all run seed clean
|
||||
.PHONY: all run run-cpp seed clean
|
||||
|
||||
all: $(FUZZER)
|
||||
all: $(FUZZER) $(FUZZER_C)
|
||||
|
||||
$(FUZZER): fuzz_gltf_v3.cc ../../../tiny_gltf_v3.h ../../../tiny_gltf_v3.c ../../../tinygltf_json_c.h
|
||||
$(CXX) $(CXXFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $<
|
||||
|
||||
run: $(FUZZER) | $(CORPUS) $(ARTIFACTS)
|
||||
./$(FUZZER) $(CORPUS) \
|
||||
$(FUZZER_C): fuzz_gltf_v3_c.c ../../../tiny_gltf_v3.h ../../../tiny_gltf_v3.c ../../../tinygltf_json_c.h
|
||||
$(CC) $(CCFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $< ../../../tiny_gltf_v3.c
|
||||
|
||||
run: $(FUZZER_C) | $(CORPUS) $(ARTIFACTS)
|
||||
$(FUZZ_ENV) ./$(FUZZER_C) $(CORPUS) \
|
||||
-artifact_prefix=$(ARTIFACTS)/ \
|
||||
-max_len=$(MAX_LEN) \
|
||||
-jobs=$(JOBS) \
|
||||
-workers=$(JOBS) \
|
||||
$(if $(filter-out 0,$(MAX_TIME)),-max_total_time=$(MAX_TIME))
|
||||
|
||||
run-cpp: $(FUZZER) | $(CORPUS) $(ARTIFACTS)
|
||||
$(FUZZ_ENV) ./$(FUZZER) $(CORPUS) \
|
||||
-artifact_prefix=$(ARTIFACTS)/ \
|
||||
-max_len=$(MAX_LEN) \
|
||||
-jobs=$(JOBS) \
|
||||
@@ -63,5 +79,5 @@ $(ARTIFACTS):
|
||||
mkdir -p $(ARTIFACTS)
|
||||
|
||||
clean:
|
||||
rm -f $(FUZZER)
|
||||
rm -f $(FUZZER) $(FUZZER_C)
|
||||
rm -rf $(CORPUS) $(ARTIFACTS)
|
||||
|
||||
96
tests/v3/fuzzer/fuzz_gltf_v3_c.c
Normal file
96
tests/v3/fuzzer/fuzz_gltf_v3_c.c
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "tiny_gltf_v3.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint64_t FUZZ_MEMORY_BUDGET = 64ULL * 1024 * 1024;
|
||||
|
||||
static void tg3_fuzz_parse_auto(const uint8_t *data, size_t size) {
|
||||
tg3_model model;
|
||||
tg3_error_stack errors;
|
||||
tg3_parse_options opts;
|
||||
|
||||
tg3_error_stack_init(&errors);
|
||||
tg3_parse_options_init(&opts);
|
||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
||||
|
||||
tg3_parse_auto(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
||||
|
||||
tg3_model_free(&model);
|
||||
tg3_error_stack_free(&errors);
|
||||
}
|
||||
|
||||
static void tg3_fuzz_parse_json(const uint8_t *data, size_t size) {
|
||||
tg3_model model;
|
||||
tg3_error_stack errors;
|
||||
tg3_parse_options opts;
|
||||
|
||||
tg3_error_stack_init(&errors);
|
||||
tg3_parse_options_init(&opts);
|
||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
||||
|
||||
tg3_parse(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
||||
|
||||
tg3_model_free(&model);
|
||||
tg3_error_stack_free(&errors);
|
||||
}
|
||||
|
||||
static void tg3_fuzz_parse_glb(const uint8_t *data, size_t size) {
|
||||
tg3_model model;
|
||||
tg3_error_stack errors;
|
||||
tg3_parse_options opts;
|
||||
|
||||
tg3_error_stack_init(&errors);
|
||||
tg3_parse_options_init(&opts);
|
||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
||||
|
||||
tg3_parse_glb(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
||||
|
||||
tg3_model_free(&model);
|
||||
tg3_error_stack_free(&errors);
|
||||
}
|
||||
|
||||
static void tg3_fuzz_parse_float32(const uint8_t *data, size_t size) {
|
||||
tg3_model model;
|
||||
tg3_error_stack errors;
|
||||
tg3_parse_options opts;
|
||||
|
||||
tg3_error_stack_init(&errors);
|
||||
tg3_parse_options_init(&opts);
|
||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
||||
opts.parse_float32 = 1;
|
||||
|
||||
tg3_parse_auto(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
||||
|
||||
tg3_model_free(&model);
|
||||
tg3_error_stack_free(&errors);
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
uint8_t selector;
|
||||
const uint8_t *payload;
|
||||
size_t payload_size;
|
||||
|
||||
if (size == 0) return 0;
|
||||
|
||||
selector = data[0] % 4;
|
||||
payload = data + 1;
|
||||
payload_size = size - 1;
|
||||
|
||||
switch (selector) {
|
||||
case 0:
|
||||
tg3_fuzz_parse_auto(payload, payload_size);
|
||||
break;
|
||||
case 1:
|
||||
tg3_fuzz_parse_json(payload, payload_size);
|
||||
break;
|
||||
case 2:
|
||||
tg3_fuzz_parse_glb(payload, payload_size);
|
||||
break;
|
||||
case 3:
|
||||
tg3_fuzz_parse_float32(payload, payload_size);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -451,6 +451,46 @@ static int tg3__json_is_number(const tg3json_value *v) {
|
||||
return v && (v->type == TG3JSON_INT || v->type == TG3JSON_REAL);
|
||||
}
|
||||
|
||||
static int tg3__json_number_to_int32(const tg3json_value *v, int32_t *out) {
|
||||
double real;
|
||||
int32_t converted;
|
||||
if (!tg3__json_is_number(v) || !out) return 0;
|
||||
if (v->type == TG3JSON_INT) {
|
||||
if (v->u.integer < INT32_MIN || v->u.integer > INT32_MAX) return 0;
|
||||
*out = (int32_t)v->u.integer;
|
||||
return 1;
|
||||
}
|
||||
real = v->u.real;
|
||||
if (!isfinite(real) || real < (double)INT32_MIN || real > (double)INT32_MAX) {
|
||||
return 0;
|
||||
}
|
||||
converted = (int32_t)real;
|
||||
if ((double)converted != real) return 0;
|
||||
*out = converted;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tg3__json_number_to_uint64(const tg3json_value *v, uint64_t *out) {
|
||||
double real;
|
||||
uint64_t converted;
|
||||
/* Largest safely castable integer expressible as double below 2^64. */
|
||||
const double max_safe_uint64_real = 18446744073709547520.0;
|
||||
if (!tg3__json_is_number(v) || !out) return 0;
|
||||
if (v->type == TG3JSON_INT) {
|
||||
if (v->u.integer < 0) return 0;
|
||||
*out = (uint64_t)v->u.integer;
|
||||
return 1;
|
||||
}
|
||||
real = v->u.real;
|
||||
if (!isfinite(real) || real < 0.0 || real > max_safe_uint64_real) {
|
||||
return 0;
|
||||
}
|
||||
converted = (uint64_t)real;
|
||||
if ((double)converted != real) return 0;
|
||||
*out = converted;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tg3__json_is_object(const tg3json_value *v) { return v && v->type == TG3JSON_OBJECT; }
|
||||
static int tg3__json_is_array(const tg3json_value *v) { return v && v->type == TG3JSON_ARRAY; }
|
||||
|
||||
@@ -487,7 +527,6 @@ static int tg3__parse_string(tg3__parse_ctx *ctx, const tg3json_value *o, const
|
||||
static int tg3__parse_int(tg3__parse_ctx *ctx, const tg3json_value *o, const char *key,
|
||||
int32_t *out, int required, const char *parent) {
|
||||
const tg3json_value *it = tg3__json_get(o, key);
|
||||
int64_t v;
|
||||
if (!it) {
|
||||
if (required) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR, TG3_ERR_JSON_MISSING_FIELD,
|
||||
@@ -501,13 +540,11 @@ static int tg3__parse_int(tg3__parse_ctx *ctx, const tg3json_value *o, const cha
|
||||
parent, "Field '%s' must be a number", key);
|
||||
return 0;
|
||||
}
|
||||
v = (it->type == TG3JSON_INT) ? it->u.integer : (int64_t)it->u.real;
|
||||
if (v < INT32_MIN || v > INT32_MAX) {
|
||||
if (!tg3__json_number_to_int32(it, out)) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR, TG3_ERR_JSON_TYPE_MISMATCH,
|
||||
parent, "Field '%s' value %" PRId64 " is out of range for int32", key, v);
|
||||
parent, "Field '%s' must be a finite int32-valued number", key);
|
||||
return 0;
|
||||
}
|
||||
*out = (int32_t)v;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -527,8 +564,11 @@ static int tg3__parse_uint64(tg3__parse_ctx *ctx, const tg3json_value *o, const
|
||||
parent, "Field '%s' must be a number", key);
|
||||
return 0;
|
||||
}
|
||||
if (it->type == TG3JSON_INT) *out = it->u.integer >= 0 ? (uint64_t)it->u.integer : 0u;
|
||||
else *out = it->u.real >= 0.0 ? (uint64_t)it->u.real : 0u;
|
||||
if (!tg3__json_number_to_uint64(it, out)) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR, TG3_ERR_JSON_TYPE_MISMATCH,
|
||||
parent, "Field '%s' must be a finite uint64-valued number", key);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -651,7 +691,13 @@ static int tg3__parse_int_array(tg3__parse_ctx *ctx, const tg3json_value *o, con
|
||||
}
|
||||
for (i = 0; i < count; ++i) {
|
||||
const tg3json_value *item = tg3json_array_get(it, i);
|
||||
arr[i] = item ? (int32_t)((item->type == TG3JSON_REAL) ? item->u.real : item->u.integer) : 0;
|
||||
if (!item || !tg3__json_number_to_int32(item, &arr[i])) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
|
||||
TG3_ERR_JSON_TYPE_MISMATCH, parent,
|
||||
"Field '%s' element %u must be a finite int32-valued number",
|
||||
key, (unsigned)i);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*out = arr;
|
||||
*out_count = (uint32_t)count;
|
||||
@@ -1155,7 +1201,13 @@ static int tg3__parse_primitive(tg3__parse_ctx *ctx, const tg3json_value *o, tg3
|
||||
for (i = 0; i < count; ++i) {
|
||||
const tg3json_object_entry *entry = tg3json_object_at(attr_it, i);
|
||||
attrs[i].key = tg3__arena_str(ctx->arena, entry->key, (uint32_t)entry->key_len);
|
||||
attrs[i].value = (int32_t)((entry->value->type == TG3JSON_REAL) ? entry->value->u.real : entry->value->u.integer);
|
||||
if (!tg3__json_number_to_int32(entry->value, &attrs[i].value)) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
|
||||
TG3_ERR_JSON_TYPE_MISMATCH, "/primitive/attributes",
|
||||
"Attribute '%s' must be a finite int32-valued number",
|
||||
entry->key ? entry->key : "");
|
||||
attrs[i].value = 0;
|
||||
}
|
||||
}
|
||||
prim->attributes = attrs;
|
||||
prim->attributes_count = (uint32_t)count;
|
||||
@@ -1186,7 +1238,13 @@ static int tg3__parse_primitive(tg3__parse_ctx *ctx, const tg3json_value *o, tg3
|
||||
for (ai = 0; ai < acount; ++ai) {
|
||||
const tg3json_object_entry *entry = tg3json_object_at(target_obj, ai);
|
||||
tattrs[ai].key = tg3__arena_str(ctx->arena, entry->key, (uint32_t)entry->key_len);
|
||||
tattrs[ai].value = (int32_t)((entry->value->type == TG3JSON_REAL) ? entry->value->u.real : entry->value->u.integer);
|
||||
if (!tg3__json_number_to_int32(entry->value, &tattrs[ai].value)) {
|
||||
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
|
||||
TG3_ERR_JSON_TYPE_MISMATCH, "/primitive/targets",
|
||||
"Target attribute '%s' must be a finite int32-valued number",
|
||||
entry->key ? entry->key : "");
|
||||
tattrs[ai].value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
target_arrays[ti] = tattrs;
|
||||
@@ -1611,6 +1669,7 @@ TINYGLTF3_API tg3_error_code tg3_parse(tg3_model *model, tg3_error_stack *errors
|
||||
tg3json_value json_doc;
|
||||
const char *error_pos = NULL;
|
||||
tg3_error_code ret;
|
||||
int parsed_ok;
|
||||
if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; }
|
||||
tg3__model_init(model);
|
||||
arena = tg3__arena_create(&options->memory);
|
||||
@@ -1619,9 +1678,12 @@ TINYGLTF3_API tg3_error_code tg3_parse(tg3_model *model, tg3_error_stack *errors
|
||||
return TG3_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
model->arena_ = arena;
|
||||
if (!tg3json_parse_n((const char *)json_data, (size_t)json_size, TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos) || json_doc.type != TG3JSON_OBJECT) {
|
||||
parsed_ok = tg3json_parse_n((const char *)json_data, (size_t)json_size,
|
||||
TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos);
|
||||
if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) {
|
||||
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse JSON", NULL,
|
||||
error_pos ? (int64_t)(error_pos - (const char *)json_data) : -1);
|
||||
if (parsed_ok) tg3json_value_free(&json_doc);
|
||||
if (model->arena_) { tg3__arena_destroy(model->arena_); model->arena_ = NULL; }
|
||||
return TG3_ERR_JSON_PARSE;
|
||||
}
|
||||
@@ -1657,6 +1719,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er
|
||||
tg3json_value json_doc;
|
||||
const char *error_pos = NULL;
|
||||
tg3_error_code err;
|
||||
int parsed_ok;
|
||||
err = tg3__parse_glb_header(glb_data, glb_size, &json_chunk, &json_chunk_size, &bin_chunk, &bin_chunk_size, errors);
|
||||
if (err != TG3_OK) return err;
|
||||
if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; }
|
||||
@@ -1667,9 +1730,12 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er
|
||||
return TG3_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
model->arena_ = arena;
|
||||
if (!tg3json_parse_n((const char *)json_chunk, (size_t)json_chunk_size, TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos) || json_doc.type != TG3JSON_OBJECT) {
|
||||
parsed_ok = tg3json_parse_n((const char *)json_chunk, (size_t)json_chunk_size,
|
||||
TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos);
|
||||
if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) {
|
||||
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse GLB JSON chunk", NULL,
|
||||
error_pos ? (int64_t)(error_pos - (const char *)json_chunk) : -1);
|
||||
if (parsed_ok) tg3json_value_free(&json_doc);
|
||||
tg3__arena_destroy(model->arena_);
|
||||
model->arena_ = NULL;
|
||||
return TG3_ERR_JSON_PARSE;
|
||||
@@ -1717,6 +1783,7 @@ TINYGLTF3_API tg3_error_code tg3_parse_file(tg3_model *model, tg3_error_stack *e
|
||||
uint32_t i;
|
||||
if (options) opts = *options;
|
||||
else tg3_parse_options_init(&opts);
|
||||
tg3__model_init(model);
|
||||
#ifdef TINYGLTF3_ENABLE_FS
|
||||
tg3__set_default_fs(&opts.fs);
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user