Compare commits

..

33 Commits

Author SHA1 Message Date
Syoyo Fujita
135695e918 Clarify C++ version requirement in README
Updated README to reflect C++11 requirement and removed outdated information.
2026-03-24 04:52:36 +09:00
Syoyo Fujita
b163ff225a Merge pull request #537 from syoyo/v3
V3
2026-03-24 04:50:29 +09:00
Syoyo Fujita
1a04c114c6 Merge pull request #544 from syoyo/copilot/sub-pr-537-8bf00c20-87df-4d41-b746-2db2da281b7c
docs: Add v3 API section to README with deprecation notice for v2
2026-03-24 01:49:04 +09:00
copilot-swe-agent[bot]
b5a962f1f4 Add v3 documentation to README.md with summary, quick start, and v2 deprecation notice
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/ffbaa2c3-7ad0-4210-b802-3253f1443ec2
2026-03-23 16:38:16 +00:00
copilot-swe-agent[bot]
f143766625 Initial plan 2026-03-23 16:36:22 +00:00
Syoyo Fujita
1215adc13a Merge pull request #543 from syoyo/copilot/sub-pr-537-please-work
Fix misleading comment on cj_dbl_to_i64 clamping behavior
2026-03-24 01:32:48 +09:00
copilot-swe-agent[bot]
826b71cc24 Remove accidentally committed tmp.glb
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/74f01d98-ca42-4950-984e-458d4e3eeccd
2026-03-21 20:39:39 +00:00
copilot-swe-agent[bot]
dfd94f03fb Fix cj_dbl_to_i64 comment to accurately describe clamping behavior
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/74f01d98-ca42-4950-984e-458d4e3eeccd
2026-03-21 20:39:31 +00:00
copilot-swe-agent[bot]
131c4489fa Initial plan 2026-03-21 20:37:03 +00:00
Syoyo Fujita
9248070755 Merge pull request #536 from syoyo/copilot/implement-gltf-parser-unit-tester
Add intensive parser unit tester and LLVM fuzzer for tinygltf_json.h backend
2026-03-22 05:33:43 +09:00
Syoyo Fujita
d8fb6cad78 Update tests/fuzzer/fuzz_gltf_customjson.cc
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-22 02:50:24 +09:00
Syoyo Fujita
e2e40f58ae Update tests/fuzzer/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-22 02:50:11 +09:00
Syoyo Fujita
306c72fce9 Update tests/tester_intensive_customjson.cc
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-22 02:50:02 +09:00
Syoyo Fujita
594c3a057b Merge pull request #540 from syoyo/copilot/sub-pr-537-another-one
Fix float32_mode mis-classifying long integer tokens as floats
2026-03-21 07:04:36 +09:00
Syoyo Fujita
ad316367b9 Merge pull request #541 from syoyo/copilot/sub-pr-537-yet-again
Fix `tg3__arena_strdup` conflating empty strings with absent strings
2026-03-21 07:04:10 +09:00
Syoyo Fujita
1f15c2d140 Merge pull request #538 from syoyo/copilot/sub-pr-537
Fix tg3_writer allocation: replace calloc/free with new/delete
2026-03-21 06:38:06 +09:00
Syoyo Fujita
1d5e721a24 Merge pull request #542 from syoyo/copilot/sub-pr-537-one-more-time
Guard TINYGLTF3_IMPLEMENTATION against C translation units
2026-03-21 06:37:42 +09:00
copilot-swe-agent[bot]
c9a9b1175a Fix float32_mode integer parsing: preserve int64 precision for integer-only tokens
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/a77fd614-00f3-49c1-bb4a-0498771cc63b
2026-03-20 21:24:37 +00:00
copilot-swe-agent[bot]
5e0c5b9ada Fix tg3__arena_strdup to distinguish empty strings from absent strings
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/445ab61b-4294-45e6-8faf-4f2fc8dfe369
2026-03-20 21:21:35 +00:00
copilot-swe-agent[bot]
03b9db782e Add C++ compilation guard for TINYGLTF3_IMPLEMENTATION
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/9d34bfe8-6b91-44f8-aedc-adb3bfeadf84
2026-03-20 21:21:26 +00:00
Syoyo Fujita
c99e713fab Merge pull request #539 from syoyo/copilot/sub-pr-537-again
Use `__VA_OPT__` for variadic comma elision in C++20, `##__VA_ARGS__` fallback for C++17
2026-03-21 06:19:35 +09:00
copilot-swe-agent[bot]
8c8cbfa0ba Initial plan 2026-03-20 21:15:51 +00:00
copilot-swe-agent[bot]
0949983acc Initial plan 2026-03-20 21:15:45 +00:00
copilot-swe-agent[bot]
c870bd5fd6 Initial plan 2026-03-20 21:15:36 +00:00
copilot-swe-agent[bot]
b76cf7aa21 Replace ##__VA_ARGS__ with portable TG3__COMMA_VA_ARGS helper (C++17/C++20)
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/a7105342-8673-4241-b727-29026461cc67
2026-03-20 20:42:17 +00:00
Syoyo Fujita
f7bd377a69 Update tiny_gltf_v3.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:36:13 +09:00
Syoyo Fujita
5d6984b9fd Update tiny_gltf_v3.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:35:53 +09:00
Syoyo Fujita
3331c6cee2 Update tinygltf_json.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:35:34 +09:00
copilot-swe-agent[bot]
97316e140c Initial plan 2026-03-20 20:35:31 +00:00
copilot-swe-agent[bot]
12affdcc64 Address code review feedback: .gitignore path, fuzzer docs, trailing whitespace test
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-19 00:44:04 +00:00
copilot-swe-agent[bot]
2c1a8be82d Add intensive parser unit tester and LLVM fuzzer for tinygltf_json.h backend
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-19 00:42:53 +00:00
copilot-swe-agent[bot]
df3efc6453 Initial plan for intensive parser unit tester and LLVM fuzzer
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-19 00:36:24 +00:00
copilot-swe-agent[bot]
99720ea0cc Initial plan 2026-03-19 00:32:52 +00:00
11 changed files with 1358 additions and 35 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,5 @@
# CMake
/build/
CMakeCache.txt
CMakeFiles
CMakeScripts
@@ -71,6 +72,7 @@ imgui.ini
loader_example
tests/tester
tests/tester_noexcept
tests/tester_intensive_customjson
tests/issue-97.gltf
tests/issue-261.gltf
tests/issue-495-external.gltf

View File

@@ -56,6 +56,15 @@ if (TINYGLTF_BUILD_TESTS)
)
target_compile_definitions(tester_customjson PRIVATE TINYGLTF_USE_CUSTOM_JSON)
add_test(NAME tester_customjson COMMAND tester_customjson WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
# Intensive parser tests for the custom JSON backend
add_executable(tester_intensive_customjson tests/tester_intensive_customjson.cc)
target_include_directories(tester_intensive_customjson PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)
target_compile_definitions(tester_intensive_customjson PRIVATE TINYGLTF_USE_CUSTOM_JSON)
add_test(NAME tester_intensive_customjson COMMAND tester_intensive_customjson WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
endif (TINYGLTF_BUILD_TESTS)
#

View File

@@ -1,14 +1,57 @@
# Header only C++ tiny glTF library(loader/saver).
`TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
`TinyGLTF` is a header only C++ glTF 2.0 https://github.com/KhronosGroup/glTF library.
`TinyGLTF` uses Niels Lohmann's json library (https://github.com/nlohmann/json), so now it requires C++11 compiler.
(Also, you can use RadpidJSON as an JSON backend)
If you are looking for old, C++03 version, please use `devel-picojson` branch (but not maintained anymore).
## TinyGLTF v3 (new major release)
**`tiny_gltf_v3.h`** is the new major version of TinyGLTF and the recommended API for new projects.
### What's new in v3
v3 is a ground-up rewrite with a C-centric, low-overhead design:
- **Pure C POD structs** — no STL containers in the public API; easy to bind to other languages.
- **Arena-based memory management** — all parse-time allocations come from a single arena; a single `tg3_model_free()` frees everything.
- **Structured error reporting** — `tg3_error_stack` provides machine-readable errors with severity levels and source locations.
- **Custom JSON backend** — backed by `tinygltf_json.h`, a high-performance, locale-independent JSON parser with optional SIMD acceleration (SSE2 / AVX2 / NEON) and a float32 fast-path.
- **Streaming callbacks** — opt-in streaming parse/write via user-supplied callbacks.
- **No RTTI, no exceptions required** — suitable for embedded and game-engine use.
- **Opt-in filesystem and image I/O** — `TINYGLTF3_ENABLE_FS` / `TINYGLTF3_ENABLE_STB_IMAGE` are off by default; you control when and how assets are loaded.
- **C++20 coroutine facade** (optional, auto-detected). C17/C++17 default.
### Quick start (v3)
Copy `tiny_gltf_v3.h` and `tinygltf_json.h` to your project. In **one** `.cpp` file:
```cpp
#define TINYGLTF3_IMPLEMENTATION
#define TINYGLTF3_ENABLE_FS // enable file I/O
#define TINYGLTF3_ENABLE_STB_IMAGE // enable image decoding
#include "tiny_gltf_v3.h"
```
Loading a glTF file:
```c
tg3_load_options_t opts = tg3_load_options_default();
tg3_error_stack_t errors = {0};
tg3_model_t *model = tg3_load_from_file("scene.gltf", &opts, &errors);
if (!model) {
for (int i = 0; i < errors.count; i++)
fprintf(stderr, "[%s] %s\n", tg3_severity_str(errors.items[i].severity),
errors.items[i].message);
}
// ... use model ...
tg3_model_free(model);
```
## Status
Currently TinyGLTF is stable and maintenance mode. No drastic changes and feature additions planned.
> ⚠️ **v2 deprecation notice:** `tiny_gltf.h` (v2) remains fully functional and is still supported,
> but it is now in **maintenance mode only** — no new features will be added.
> v2 will be **sunset after mid-2026**. New projects should use `tiny_gltf_v3.h`.
Currently TinyGLTF v2 is stable and in maintenance mode. No drastic changes and feature additions planned.
- v2.9.0 Various fixes and improvements. Filesystem callback API change.
- v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397
- v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393

View File

@@ -4,3 +4,4 @@
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

View File

@@ -4,9 +4,14 @@ Do fuzzing test for TinyGLTF API.
## Supported API
* [x] LoadASCIIFromMemory
* [x] LoadASCIIFromString
* [ ] LoadBinaryFromMemory
### Custom JSON backend (`tinygltf_json.h`)
* [x] LoadASCIIFromString
* [x] LoadBinaryFromMemory
## Requirements
* meson
@@ -36,11 +41,17 @@ $ cd build
$ ninja
```
This builds two fuzzers:
* `fuzz_gltf` default nlohmann/json backend
* `fuzz_gltf_customjson` custom `tinygltf_json.h` backend (tests both ASCII and binary parsing paths)
## How to run
Increase memory limit. e.g. `-rss_limit_mb=50000`
```
$ ./fuzz_gltf -rss_limit_mb=20000 -jobs 4
$ ./fuzz_gltf_customjson -rss_limit_mb=20000 -jobs 4
```

View File

@@ -0,0 +1,76 @@
/*
* LLVM libFuzzer harness for tinygltf with the custom JSON backend
* (tinygltf_json.h).
*
* Exercises:
* 1. LoadASCIIFromString glTF JSON parsing
* 2. LoadBinaryFromMemory GLB binary parsing
*
* Build (clang with libFuzzer):
* clang++ -std=c++11 -fsanitize=address,fuzzer \
* -DTINYGLTF_USE_CUSTOM_JSON \
* -I../../ fuzz_gltf_customjson.cc \
* -o fuzz_gltf_customjson
*
* Run:
* ./fuzz_gltf_customjson -rss_limit_mb=20000 -jobs 4
*/
#include <cstdint>
#include <cstring>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_IMPLEMENTATION
#ifndef TINYGLTF_USE_CUSTOM_JSON
#define TINYGLTF_USE_CUSTOM_JSON
#endif
#include "tiny_gltf.h"
/* Fuzz the ASCII (JSON) parser path */
static void fuzz_ascii(const uint8_t *data, size_t size) {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
const char *str = reinterpret_cast<const char *>(data);
bool ret =
ctx.LoadASCIIFromString(&model, &err, &warn, str,
static_cast<unsigned int>(size), /* base_dir */ "");
(void)ret;
}
/* Fuzz the binary (GLB) parser path */
static void fuzz_binary(const uint8_t *data, size_t size) {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
bool ret = ctx.LoadBinaryFromMemory(&model, &err, &warn, data,
static_cast<unsigned int>(size),
/* base_dir */ "");
(void)ret;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
/* Use the lowest bit of the first byte to select the parse path.
* The remaining bits are left for the fuzzer engine to explore;
* additional paths (e.g. LoadASCIIFromFile, check_sections flags)
* can be added here in the future using more selector bits. */
uint8_t selector = data[0];
const uint8_t *payload = data + 1;
size_t payload_size = size - 1;
if (selector & 1) {
fuzz_binary(payload, payload_size);
} else {
fuzz_ascii(payload, payload_size);
}
return 0;
}

View File

@@ -7,3 +7,9 @@ executable('fuzz_gltf',
cpp_args : '-fsanitize=address,fuzzer',
link_args : '-fsanitize=address,fuzzer' )
executable('fuzz_gltf_customjson',
'fuzz_gltf_customjson.cc',
include_directories : incdirs,
cpp_args : ['-fsanitize=address,fuzzer', '-DTINYGLTF_USE_CUSTOM_JSON'],
link_args : '-fsanitize=address,fuzzer' )

View File

@@ -1249,3 +1249,37 @@ TEST_CASE("empty-images-not-written", "[issue-495]") {
// WriteImageData should be invoked for both images
CHECK(counter == 2);
}
#ifdef TINYGLTF_USE_CUSTOM_JSON
/* Regression test: in float32_mode, integer-only tokens with more than 9
* digits must still be parsed as integers (is_int == 1), not floats.
* Previously, max_sig=9 was applied to the integer part too, causing excess
* digits to bump exp10, which broke the exp10==0 guard in the integer
* fast-path and mis-classified the value as a float. */
TEST_CASE("cj-float32-long-integer", "[customjson]") {
// Values chosen to cover exactly-at, just-over, and near int64 boundaries.
struct {
const char *text;
int64_t expected;
} cases[] = {
{ "1234567890", 1234567890LL }, /* 10 digits */
{ "12345678901", 12345678901LL }, /* 11 digits */
{ "1000000000000", 1000000000000LL }, /* 13 digits */
{ "9223372036854775807", INT64_MAX }, /* max int64 (19 digits) */
{ "-1234567890", -1234567890LL }, /* negative 10 digits */
{ "-9223372036854775808", INT64_MIN }, /* min int64 */
};
for (auto &tc : cases) {
int is_int = 0;
int64_t ival = 0;
double dval = 0.0;
const char *end = tc.text + strlen(tc.text);
const char *ret = cj_parse_number(tc.text, end, &is_int, &ival, &dval, /*float32_mode=*/1);
CAPTURE(tc.text);
REQUIRE(ret != nullptr);
CHECK(is_int == 1);
CHECK(ival == tc.expected);
}
}
#endif /* TINYGLTF_USE_CUSTOM_JSON */

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@
* Section 2: Configuration Macros
* ====================================================================== */
/* Build mode: define in ONE .c/.cpp translation unit */
/* Build mode: define in ONE C++ translation unit (.cpp) */
/* #define TINYGLTF3_IMPLEMENTATION */
/* Opt-in features (OFF by default) */
@@ -1221,17 +1221,19 @@ ParseGenerator tg3_parse_coro(
#ifdef TINYGLTF3_IMPLEMENTATION
#if !defined(__cplusplus)
#error "TINYGLTF3_IMPLEMENTATION requires a C++ translation unit (compile as .cpp)"
#endif
/* Include JSON parser */
#include "tinygltf_json.h"
#include <stdio.h>
#include <math.h>
#ifdef __cplusplus
/* Implementation uses C++ features from tinygltf_json.h */
#include <string>
#include <algorithm>
#endif
/* Forward SIMD macros to tinygltf_json.h */
#ifdef TINYGLTF3_JSON_SIMD_SSE2
@@ -1358,10 +1360,13 @@ static void *tg3__arena_alloc(tg3_arena *arena, size_t size) {
}
static char *tg3__arena_strdup(tg3_arena *arena, const char *s, size_t len) {
if (!s || len == 0) return NULL;
if (!s) return NULL;
/* Allocate len+1 bytes; when len==0 this produces a 1-byte "\0" buffer so
* that empty strings (data!=NULL, len==0) remain distinguishable from
* absent strings (data==NULL, len==0). */
char *dst = (char *)tg3__arena_alloc(arena, len + 1);
if (!dst) return NULL;
memcpy(dst, s, len);
if (len > 0) memcpy(dst, s, len);
dst[len] = '\0';
return dst;
}
@@ -1750,7 +1755,25 @@ static int tg3__parse_int(tg3__parse_ctx *ctx, const tg3__json &o,
"Field '%s' must be a number", key);
return 0;
}
*out = it->get<int>();
if (it->is_number_integer()) {
int64_t v = it->get<int64_t>();
if (v < (int64_t)INT32_MIN || v > (int64_t)INT32_MAX) {
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);
return 0;
}
*out = (int32_t)v;
} else {
double d = it->get<double>();
if (d < (double)INT32_MIN || d > (double)INT32_MAX) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_JSON_TYPE_MISMATCH, parent,
"Field '%s' value %f is out of range for int32", key, d);
return 0;
}
*out = (int32_t)d;
}
return 1;
}
@@ -2315,17 +2338,22 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3__json &o,
/* Load buffer data */
if (ctx->is_binary && buf_idx == 0 && buf->uri.len == 0) {
/* GLB: first buffer uses binary chunk */
if (ctx->bin_data && ctx->bin_size >= byte_length) {
uint8_t *data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)byte_length);
if (!data) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
TG3_ERR_OUT_OF_MEMORY, "OOM for buffer data", NULL, -1);
return 0;
}
memcpy(data, ctx->bin_data, (size_t)byte_length);
buf->data.data = data;
buf->data.count = byte_length;
if (!ctx->bin_data || ctx->bin_size < byte_length) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
TG3_ERR_BUFFER_SIZE_MISMATCH,
"GLB BIN chunk missing or smaller than buffer.byteLength",
NULL, -1);
return 0;
}
uint8_t *data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)byte_length);
if (!data) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
TG3_ERR_OUT_OF_MEMORY, "OOM for buffer data", NULL, -1);
return 0;
}
memcpy(data, ctx->bin_data, (size_t)byte_length);
buf->data.data = data;
buf->data.count = byte_length;
} else if (buf->uri.len > 0) {
if (tg3_is_data_uri(buf->uri.data, buf->uri.len)) {
/* Data URI */
@@ -2908,6 +2936,22 @@ static int tg3__parse_audio_emitter(tg3__parse_ctx *ctx, const tg3__json &o,
return 1;
}
/* ======================================================================
* Internal: Portable variadic-comma helper
*
* TG3__COMMA_VA_ARGS(__VA_ARGS__) expands to , __VA_ARGS__ when the
* argument list is non-empty, and to nothing when it is empty.
*
* - C++20 and later: uses the standard __VA_OPT__(,) token.
* - C++17 and earlier: falls back to the widely-supported GNU/MSVC
* ##__VA_ARGS__ extension.
* ====================================================================== */
#if __cplusplus >= 202002L
# define TG3__COMMA_VA_ARGS(...) __VA_OPT__(,) __VA_ARGS__
#else
# define TG3__COMMA_VA_ARGS(...) , ##__VA_ARGS__
#endif
/* ======================================================================
* Internal: Array Parse Macro
* ====================================================================== */
@@ -2923,7 +2967,7 @@ static int tg3__parse_audio_emitter(tg3__parse_ctx *ctx, const tg3__json &o,
if (_items) { \
uint32_t _i = 0; \
for (auto _it = _arr_it->begin(); _it != _arr_it->end(); ++_it, ++_i) { \
parse_fn((ctx), *_it, &_items[_i], ##__VA_ARGS__); \
parse_fn((ctx), *_it, &_items[_i] TG3__COMMA_VA_ARGS(__VA_ARGS__)); \
} \
(model_field) = _items; \
(count_field) = _count; \
@@ -4094,7 +4138,7 @@ static tg3__json tg3__serialize_model(const tg3_model *model, int wd,
if ((arr) && (cnt) > 0) { \
tg3__json jarr; jarr.set_array(); \
for (uint32_t _i = 0; _i < (cnt); ++_i) { \
jarr.push_back(fn(&(arr)[_i], ##__VA_ARGS__)); \
jarr.push_back(fn(&(arr)[_i] TG3__COMMA_VA_ARGS(__VA_ARGS__))); \
} \
root[key] = static_cast<tg3__json&&>(jarr); \
}
@@ -4335,7 +4379,7 @@ TINYGLTF3_API tg3_error_code tg3_writer_begin(tg3_writer *w, const tg3_asset *as
w->root[json_key] = static_cast<tg3__json&&>(arr); \
} \
w->root[json_key].push_back( \
serialize_fn(item, w->options.serialize_defaults, ##__VA_ARGS__)); \
serialize_fn(item, w->options.serialize_defaults TG3__COMMA_VA_ARGS(__VA_ARGS__))); \
return TG3_OK; \
}

View File

@@ -297,8 +297,9 @@ static const char *cj_scan_str(const char *p, const char *end) {
* FAST NUMBER PARSING (C-style)
*
* Uses Clinger's fast path for float conversion, avoiding strtod() for the
* vast majority of JSON numbers. This is locale-independent and typically
* 4-10x faster than strtod.
* vast majority of JSON numbers. The fast path itself is locale-independent
* and typically 4-10x faster than strtod; however, rare fallback paths may
* still invoke the C library's strtod(), which can be locale-dependent.
*
* Optional float32 mode (CJ_FLOAT32_MODE flag in cj_parse_number):
* Parses floating-point values to float (single) precision and stores
@@ -307,7 +308,8 @@ static const char *cj_scan_str(const char *p, const char *end) {
* Breaks strict JSON/IEEE-754-double conformance.
* ====================================================================== */
/* Safe double-to-int64 cast: clamp inf/NaN/out-of-range to 0. */
/* Safe double-to-int64 cast: returns 0 for NaN; clamps +inf/out-of-range-high
* to INT64_MAX and -inf/out-of-range-low to INT64_MIN. */
static int64_t cj_dbl_to_i64(double d) {
if (d != d) return 0; /* NaN */
if (d >= (double)INT64_MAX) return INT64_MAX;
@@ -421,9 +423,11 @@ static int cj_fast_flt_convert(uint64_t mantissa, int exp10, int neg, float *out
* Returns pointer past the last character consumed, or NULL on error.
*
* float32_mode: when non-zero, floating-point values are parsed at float
* (single) precision — fewer digits are significant, and the result is
* stored as (double)(float)value. This is faster but not JSON-conformant
* for high-precision doubles.
* (single) precision — only 9 significant digits are tracked for the
* fraction part, and the result is stored as (double)(float)value. This
* is faster but not JSON-conformant for high-precision doubles. Integer-
* only tokens (no '.'/'e') are always parsed at full int64 precision
* regardless of this flag.
*
* Uses Clinger's fast path (no strtod) for ~99% of JSON float values.
* Falls back to strtod only for extreme exponents or >19 significant digits. */
@@ -443,8 +447,12 @@ static const char *cj_parse_number(const char *p, const char *end,
int mantissa_overflow = 0; /* set if >19 significant digits */
int has_frac = 0, has_exp = 0;
/* Max significant digits we track: 19 for double, 9 for float32 */
int max_sig = float32_mode ? 9 : 19;
/* Max significant digits we track:
* Integer part: always 19, so integer-only tokens (no '.'/'e') are always
* accumulated fully and can be typed as int64 regardless of float32_mode.
* Fraction part: 9 in float32_mode (single precision), 19 otherwise. */
int max_sig_int = 19;
int max_sig_frac = float32_mode ? 9 : 19;
/* Integer part */
if (*p == '0') {
@@ -452,7 +460,7 @@ static const char *cj_parse_number(const char *p, const char *end,
} else if ((unsigned)(*p - '1') <= 8u) {
while (p < end && (unsigned)(*p - '0') <= 9u) {
unsigned d = (unsigned)(*p - '0');
if (ndigits < max_sig) {
if (ndigits < max_sig_int) {
mantissa = mantissa * 10 + d;
} else {
exp10++; /* excess digit: bump exponent instead */
@@ -473,7 +481,7 @@ static const char *cj_parse_number(const char *p, const char *end,
if (p >= end || (unsigned)(*p - '0') > 9u) return NULL;
while (p < end && (unsigned)(*p - '0') <= 9u) {
unsigned d = (unsigned)(*p - '0');
if (ndigits < max_sig) {
if (ndigits < max_sig_frac) {
mantissa = mantissa * 10 + d;
exp10--;
}