Coverage Report

Created: 2026-03-28 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tinygltf/tinygltf_json.h
Line
Count
Source
1
/*
2
 * tinygltf_json.h - Fast JSON parser for tinygltf
3
 *
4
 * The MIT License (MIT)
5
 * Copyright (c) 2015 - Present Syoyo Fujita, Aurelien Chatelain and many
6
 * contributors.
7
 *
8
 * A custom JSON parser optimized for glTF processing.
9
 *
10
 * Design goals:
11
 *   - C-style implementation core (structs, raw pointers, malloc/free)
12
 *   - Minimal C++ wrappers for tinygltf interface compatibility
13
 *   - SIMD-accelerated whitespace skipping and string scanning
14
 *   - Flat storage arrays for cache-friendly memory layout
15
 *
16
 * SIMD activation (default: SIMD disabled):
17
 *   Define TINYGLTF_JSON_USE_SIMD to auto-detect CPU SIMD support, OR
18
 *   define one or more of the following explicitly:
19
 *     TINYGLTF_JSON_SIMD_SSE2   - Enable SSE2 (x86/x86-64)
20
 *     TINYGLTF_JSON_SIMD_AVX2   - Enable AVX2 (x86-64, implies SSE2)
21
 *     TINYGLTF_JSON_SIMD_NEON   - Enable ARM NEON
22
 *
23
 * Exception handling (default: exceptions disabled):
24
 *   By default, parse errors silently return a null value.
25
 *   Define TINYGLTF_JSON_USE_EXCEPTIONS before including this header to
26
 *   allow tinygltf_json::parse() to throw std::invalid_argument on error
27
 *   when its allow_exceptions parameter is true.
28
 */
29
30
#ifndef TINYGLTF_JSON_H_
31
#define TINYGLTF_JSON_H_
32
33
/* C standard headers (keep these first for C compatibility) */
34
#include <stddef.h>
35
#include <stdint.h>
36
#include <inttypes.h>
37
#include <string.h>
38
#include <stdlib.h>
39
#include <stdio.h>
40
#include <assert.h>
41
42
/* C++ headers (minimal) */
43
#include <string>
44
#include <cstddef>  /* for std::nullptr_t */
45
#include <new>      /* for placement-new */
46
/* Exception opt-in: define TINYGLTF_JSON_USE_EXCEPTIONS to enable throws.
47
 * TINYGLTF_JSON_NO_EXCEPTIONS is the internal guard derived from the absence
48
 * of TINYGLTF_JSON_USE_EXCEPTIONS; users should not define it directly. */
49
#ifndef TINYGLTF_JSON_USE_EXCEPTIONS
50
#  define TINYGLTF_JSON_NO_EXCEPTIONS
51
#endif
52
#ifndef TINYGLTF_JSON_NO_EXCEPTIONS
53
#  include <stdexcept>
54
#endif
55
56
/* ======================================================================
57
 * SIMD detection
58
 * ====================================================================== */
59
60
#ifdef TINYGLTF_JSON_USE_SIMD
61
#  if defined(__AVX2__)
62
#    define TINYGLTF_JSON_SIMD_AVX2
63
#  endif
64
#  if defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || \
65
      (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
66
#    define TINYGLTF_JSON_SIMD_SSE2
67
#  endif
68
#  if defined(__ARM_NEON) || defined(__ARM_NEON__)
69
#    define TINYGLTF_JSON_SIMD_NEON
70
#  endif
71
#endif
72
73
#ifdef TINYGLTF_JSON_SIMD_AVX2
74
#  include <immintrin.h>
75
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
76
#  include <emmintrin.h>
77
#endif
78
79
#ifdef TINYGLTF_JSON_SIMD_NEON
80
#  include <arm_neon.h>
81
#endif
82
83
/* ======================================================================
84
 * JSON VALUE TYPE CONSTANTS (C-style integer constants)
85
 * ====================================================================== */
86
87
0
#define CJ_NULL    0
88
0
#define CJ_BOOL    1
89
0
#define CJ_INT     2
90
0
#define CJ_REAL    3
91
0
#define CJ_STRING  4
92
0
#define CJ_ARRAY   5
93
0
#define CJ_OBJECT  6
94
95
/* ======================================================================
96
 * SIMD WHITESPACE SKIPPING
97
 *
98
 * Whitespace characters in JSON: space(0x20), tab(0x09), CR(0x0D), LF(0x0A)
99
 * ====================================================================== */
100
101
0
static const char *cj_skip_ws_scalar(const char *p, const char *end) {
102
0
    while (p < end) {
103
0
        unsigned char c = (unsigned char)*p;
104
0
        if (c != 0x20u && c != 0x09u && c != 0x0Du && c != 0x0Au) break;
105
0
        ++p;
106
0
    }
107
0
    return p;
108
0
}
109
110
#if defined(TINYGLTF_JSON_SIMD_AVX2)
111
112
static const char *cj_skip_ws(const char *p, const char *end) {
113
    while (p + 32 <= end) {
114
        __m256i chunk = _mm256_loadu_si256((const __m256i *)(const void *)p);
115
        __m256i sp  = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8(' '));
116
        __m256i tab = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\t'));
117
        __m256i cr  = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\r'));
118
        __m256i lf  = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\n'));
119
        __m256i ws  = _mm256_or_si256(_mm256_or_si256(sp, tab),
120
                                      _mm256_or_si256(cr, lf));
121
        unsigned int mask = (unsigned int)_mm256_movemask_epi8(ws);
122
        if (mask != 0xFFFFFFFFu) {
123
#if defined(__GNUC__) || defined(__clang__)
124
            return p + (int)__builtin_ctz(~mask);
125
#else
126
            unsigned int inv = ~mask, idx = 0;
127
            while (!(inv & (1u << idx))) ++idx;
128
            return p + idx;
129
#endif
130
        }
131
        p += 32;
132
    }
133
    return cj_skip_ws_scalar(p, end);
134
}
135
136
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
137
138
static const char *cj_skip_ws(const char *p, const char *end) {
139
    while (p + 16 <= end) {
140
        __m128i chunk = _mm_loadu_si128((const __m128i *)(const void *)p);
141
        __m128i sp  = _mm_cmpeq_epi8(chunk, _mm_set1_epi8(' '));
142
        __m128i tab = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\t'));
143
        __m128i cr  = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\r'));
144
        __m128i lf  = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\n'));
145
        __m128i ws  = _mm_or_si128(_mm_or_si128(sp, tab),
146
                                   _mm_or_si128(cr, lf));
147
        unsigned int mask = (unsigned int)_mm_movemask_epi8(ws);
148
        if (mask != 0xFFFFu) {
149
            unsigned int inv = (~mask) & 0xFFFFu;
150
#if defined(__GNUC__) || defined(__clang__)
151
            return p + (int)__builtin_ctz(inv);
152
#else
153
            unsigned int idx = 0;
154
            while (!(inv & (1u << idx))) ++idx;
155
            return p + idx;
156
#endif
157
        }
158
        p += 16;
159
    }
160
    return cj_skip_ws_scalar(p, end);
161
}
162
163
#elif defined(TINYGLTF_JSON_SIMD_NEON)
164
165
static const char *cj_skip_ws(const char *p, const char *end) {
166
    while (p + 16 <= end) {
167
        uint8x16_t chunk = vld1q_u8((const uint8_t *)p);
168
        uint8x16_t sp    = vceqq_u8(chunk, vdupq_n_u8(' '));
169
        uint8x16_t tab   = vceqq_u8(chunk, vdupq_n_u8('\t'));
170
        uint8x16_t cr    = vceqq_u8(chunk, vdupq_n_u8('\r'));
171
        uint8x16_t lf    = vceqq_u8(chunk, vdupq_n_u8('\n'));
172
        uint8x16_t ws    = vorrq_u8(vorrq_u8(sp, tab), vorrq_u8(cr, lf));
173
        uint64x2_t ws64  = vreinterpretq_u64_u8(ws);
174
        uint64_t   lo    = vgetq_lane_u64(ws64, 0);
175
        uint64_t   hi    = vgetq_lane_u64(ws64, 1);
176
        if (lo != UINT64_C(0xFFFFFFFFFFFFFFFF) ||
177
            hi != UINT64_C(0xFFFFFFFFFFFFFFFF)) {
178
            uint8_t tmp[16];
179
            vst1q_u8(tmp, ws);
180
            for (int i = 0; i < 16; ++i) {
181
                if (!tmp[i]) return p + i;
182
            }
183
        }
184
        p += 16;
185
    }
186
    return cj_skip_ws_scalar(p, end);
187
}
188
189
#else
190
191
0
static const char *cj_skip_ws(const char *p, const char *end) {
192
0
    return cj_skip_ws_scalar(p, end);
193
0
}
194
195
#endif /* SIMD whitespace */
196
197
/* ======================================================================
198
 * SIMD STRING SCANNING (find '"', '\', or control char)
199
 * ====================================================================== */
200
201
0
static const char *cj_scan_str_scalar(const char *p, const char *end) {
202
0
    while (p < end) {
203
0
        unsigned char c = (unsigned char)*p;
204
0
        if (c == '"' || c == '\\' || c < 0x20u) break;
205
0
        ++p;
206
0
    }
207
0
    return p;
208
0
}
209
210
#if defined(TINYGLTF_JSON_SIMD_AVX2)
211
212
static const char *cj_scan_str(const char *p, const char *end) {
213
    while (p + 32 <= end) {
214
        __m256i chunk  = _mm256_loadu_si256((const __m256i *)(const void *)p);
215
        __m256i eq_q   = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('"'));
216
        __m256i eq_bs  = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\\'));
217
        /* Control chars: byte <= 0x1F  <=>  min(byte, 0x1F) == byte */
218
        __m256i ctrl   = _mm256_cmpeq_epi8(
219
                             _mm256_min_epu8(chunk, _mm256_set1_epi8(0x1F)),
220
                             chunk);
221
        __m256i special = _mm256_or_si256(_mm256_or_si256(eq_q, eq_bs), ctrl);
222
        unsigned int mask = (unsigned int)_mm256_movemask_epi8(special);
223
        if (mask) {
224
#if defined(__GNUC__) || defined(__clang__)
225
            return p + (int)__builtin_ctz(mask);
226
#else
227
            unsigned int idx = 0;
228
            while (!(mask & (1u << idx))) ++idx;
229
            return p + idx;
230
#endif
231
        }
232
        p += 32;
233
    }
234
    return cj_scan_str_scalar(p, end);
235
}
236
237
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
238
239
static const char *cj_scan_str(const char *p, const char *end) {
240
    while (p + 16 <= end) {
241
        __m128i chunk   = _mm_loadu_si128((const __m128i *)(const void *)p);
242
        __m128i eq_q    = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('"'));
243
        __m128i eq_bs   = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
244
        __m128i ctrl    = _mm_cmpeq_epi8(
245
                              _mm_min_epu8(chunk, _mm_set1_epi8(0x1F)),
246
                              chunk);
247
        __m128i special = _mm_or_si128(_mm_or_si128(eq_q, eq_bs), ctrl);
248
        unsigned int mask = (unsigned int)_mm_movemask_epi8(special);
249
        if (mask) {
250
#if defined(__GNUC__) || defined(__clang__)
251
            return p + (int)__builtin_ctz(mask);
252
#else
253
            unsigned int idx = 0;
254
            while (!(mask & (1u << idx))) ++idx;
255
            return p + idx;
256
#endif
257
        }
258
        p += 16;
259
    }
260
    return cj_scan_str_scalar(p, end);
261
}
262
263
#elif defined(TINYGLTF_JSON_SIMD_NEON)
264
265
static const char *cj_scan_str(const char *p, const char *end) {
266
    uint8x16_t vquote  = vdupq_n_u8('"');
267
    uint8x16_t vbslash = vdupq_n_u8('\\');
268
    uint8x16_t v20     = vdupq_n_u8(0x20u);
269
    while (p + 16 <= end) {
270
        uint8x16_t chunk   = vld1q_u8((const uint8_t *)p);
271
        uint8x16_t eq_q    = vceqq_u8(chunk, vquote);
272
        uint8x16_t eq_bs   = vceqq_u8(chunk, vbslash);
273
        uint8x16_t ctrl    = vcltq_u8(chunk, v20);
274
        uint8x16_t special = vorrq_u8(vorrq_u8(eq_q, eq_bs), ctrl);
275
        uint64x2_t s64     = vreinterpretq_u64_u8(special);
276
        if (vgetq_lane_u64(s64, 0) || vgetq_lane_u64(s64, 1)) {
277
            uint8_t tmp[16];
278
            vst1q_u8(tmp, special);
279
            for (int i = 0; i < 16; ++i) {
280
                if (tmp[i]) return p + i;
281
            }
282
        }
283
        p += 16;
284
    }
285
    return cj_scan_str_scalar(p, end);
286
}
287
288
#else
289
290
0
static const char *cj_scan_str(const char *p, const char *end) {
291
0
    return cj_scan_str_scalar(p, end);
292
0
}
293
294
#endif /* SIMD string scan */
295
296
/* ======================================================================
297
 * FAST NUMBER PARSING (C-style)
298
 *
299
 * Uses Clinger's fast path for float conversion, avoiding strtod() for the
300
 * vast majority of JSON numbers.  The fast path itself is locale-independent
301
 * and typically 4-10x faster than strtod; however, rare fallback paths may
302
 * still invoke the C library's strtod(), which can be locale-dependent.
303
 *
304
 * Optional float32 mode (CJ_FLOAT32_MODE flag in cj_parse_number):
305
 *   Parses floating-point values to float (single) precision and stores
306
 *   the result as double.  Faster because fewer significant digits are
307
 *   needed and the fast path covers a wider exponent range.
308
 *   Breaks strict JSON/IEEE-754-double conformance.
309
 * ====================================================================== */
310
311
/* Safe double-to-int64 cast: returns 0 for NaN; clamps +inf/out-of-range-high
312
 * to INT64_MAX and -inf/out-of-range-low to INT64_MIN. */
313
0
static int64_t cj_dbl_to_i64(double d) {
314
0
    if (d != d) return 0;                             /* NaN */
315
0
    if (d >= (double)INT64_MAX)  return INT64_MAX;
316
0
    if (d <= (double)INT64_MIN)  return INT64_MIN;
317
0
    return (int64_t)d;
318
0
}
319
320
/* Exact powers of 10 that are representable as IEEE 754 double.
321
 * 10^0 through 10^22 are all exactly representable. */
322
static const double cj_exact_pow10[23] = {
323
    1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
324
    1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
325
    1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
326
};
327
328
/* Clinger's fast path: mantissa * 10^exp10 → double.
329
 * Requires mantissa <= 2^53 (exactly representable as double).
330
 * Returns 1 on success, 0 if fallback needed. */
331
0
static int cj_fast_dbl_convert(uint64_t mantissa, int exp10, int neg, double *out) {
332
0
    if (mantissa == 0) {
333
0
        *out = neg ? -0.0 : 0.0;
334
0
        return 1;
335
0
    }
336
337
    /* Primary: |exp10| <= 22, mantissa fits in double mantissa bits */
338
0
    if (mantissa <= (1ULL << 53)) {
339
0
        double d;
340
0
        if (exp10 >= 0 && exp10 <= 22) {
341
0
            d = (double)mantissa * cj_exact_pow10[exp10];
342
0
            *out = neg ? -d : d;
343
0
            return 1;
344
0
        }
345
0
        if (exp10 < 0 && exp10 >= -22) {
346
0
            d = (double)mantissa / cj_exact_pow10[-exp10];
347
0
            *out = neg ? -d : d;
348
0
            return 1;
349
0
        }
350
        /* Extended: split exponent into two steps, each <= 22.
351
         * Positive: exp10 = 22 + remainder, both halves exact.
352
         * Negative: exp10 = -22 + remainder. */
353
0
        if (exp10 > 22 && exp10 <= 22 + 22) {
354
0
            d = (double)mantissa * cj_exact_pow10[exp10 - 22];
355
0
            d *= cj_exact_pow10[22];
356
0
            *out = neg ? -d : d;
357
0
            return 1;
358
0
        }
359
0
        if (exp10 < -22 && exp10 >= -(22 + 22)) {
360
0
            d = (double)mantissa / cj_exact_pow10[-exp10 - 22];
361
0
            d /= cj_exact_pow10[22];
362
0
            *out = neg ? -d : d;
363
0
            return 1;
364
0
        }
365
0
    }
366
367
0
    return 0;
368
0
}
369
370
/* Fast path for float32: wider range because float mantissa is only 24 bits. */
371
0
static int cj_fast_flt_convert(uint64_t mantissa, int exp10, int neg, float *out) {
372
0
    if (mantissa == 0) {
373
0
        *out = neg ? -0.0f : 0.0f;
374
0
        return 1;
375
0
    }
376
377
    /* Direct float path: mantissa fits in 24 bits, pow10 exact in float */
378
0
    if (mantissa <= (1ULL << 24)) {
379
0
        if (exp10 >= 0 && exp10 <= 10) {
380
0
            float f = (float)mantissa * (float)cj_exact_pow10[exp10];
381
0
            *out = neg ? -f : f;
382
0
            return 1;
383
0
        }
384
0
        if (exp10 < 0 && exp10 >= -10) {
385
0
            float f = (float)mantissa / (float)cj_exact_pow10[-exp10];
386
0
            *out = neg ? -f : f;
387
0
            return 1;
388
0
        }
389
0
    }
390
391
    /* Wider path via double arithmetic (still float-precision result) */
392
0
    if (mantissa <= (1ULL << 53)) {
393
0
        double d;
394
0
        if (exp10 >= 0 && exp10 <= 22) {
395
0
            d = (double)mantissa * cj_exact_pow10[exp10];
396
0
            *out = neg ? -(float)d : (float)d;
397
0
            return 1;
398
0
        }
399
0
        if (exp10 < 0 && exp10 >= -22) {
400
0
            d = (double)mantissa / cj_exact_pow10[-exp10];
401
0
            *out = neg ? -(float)d : (float)d;
402
0
            return 1;
403
0
        }
404
0
        if (exp10 > 22 && exp10 <= 44) {
405
0
            d = (double)mantissa * cj_exact_pow10[exp10 - 22];
406
0
            d *= cj_exact_pow10[22];
407
0
            *out = neg ? -(float)d : (float)d;
408
0
            return 1;
409
0
        }
410
0
        if (exp10 < -22 && exp10 >= -44) {
411
0
            d = (double)mantissa / cj_exact_pow10[-exp10 - 22];
412
0
            d /= cj_exact_pow10[22];
413
0
            *out = neg ? -(float)d : (float)d;
414
0
            return 1;
415
0
        }
416
0
    }
417
418
0
    return 0;
419
0
}
420
421
/* Parse a JSON number starting at [p, end).
422
 * Sets *is_int, *ival (integer result), *dval (floating-point result).
423
 * Returns pointer past the last character consumed, or NULL on error.
424
 *
425
 * float32_mode: when non-zero, floating-point values are parsed at float
426
 * (single) precision — only 9 significant digits are tracked for the
427
 * fraction part, and the result is stored as (double)(float)value.  This
428
 * is faster but not JSON-conformant for high-precision doubles.  Integer-
429
 * only tokens (no '.'/'e') are always parsed at full int64 precision
430
 * regardless of this flag.
431
 *
432
 * Uses Clinger's fast path (no strtod) for ~99% of JSON float values.
433
 * Falls back to strtod only for extreme exponents or >19 significant digits. */
434
static const char *cj_parse_number(const char *p, const char *end,
435
                                   int *is_int, int64_t *ival, double *dval,
436
0
                                   int float32_mode) {
437
0
    const char *start = p;
438
0
    int neg = 0;
439
0
    if (p < end && *p == '-') { neg = 1; ++p; }
440
0
    if (p >= end) return NULL;
441
442
    /* Accumulate ALL digits (integer + fraction) into a single mantissa.
443
     * Track the decimal exponent adjustment from the '.' position. */
444
0
    uint64_t mantissa = 0;
445
0
    int ndigits = 0;           /* total significant digits consumed */
446
0
    int exp10 = 0;             /* decimal exponent adjustment */
447
0
    int mantissa_overflow = 0; /* set if >19 significant digits */
448
0
    int has_frac = 0, has_exp = 0;
449
450
    /* Max significant digits we track:
451
     *   Integer part: always 19, so integer-only tokens (no '.'/'e') are always
452
     *     accumulated fully and can be typed as int64 regardless of float32_mode.
453
     *   Fraction part: 9 in float32_mode (single precision), 19 otherwise. */
454
0
    int max_sig_int  = 19;
455
0
    int max_sig_frac = float32_mode ? 9 : 19;
456
457
    /* Integer part */
458
0
    if (*p == '0') {
459
0
        ++p;
460
0
    } else if ((unsigned)(*p - '1') <= 8u) {
461
0
        while (p < end && (unsigned)(*p - '0') <= 9u) {
462
0
            unsigned d = (unsigned)(*p - '0');
463
0
            if (ndigits < max_sig_int) {
464
0
                mantissa = mantissa * 10 + d;
465
0
            } else {
466
0
                exp10++; /* excess digit: bump exponent instead */
467
0
                if (ndigits >= 19) mantissa_overflow = 1;
468
0
            }
469
0
            ndigits++;
470
0
            ++p;
471
0
        }
472
0
    } else {
473
0
        return NULL;
474
0
    }
475
476
    /* Fraction part */
477
0
    if (p < end && *p == '.') {
478
0
        has_frac = 1;
479
0
        ++p;
480
        /* JSON requires at least one digit after '.' */
481
0
        if (p >= end || (unsigned)(*p - '0') > 9u) return NULL;
482
0
        while (p < end && (unsigned)(*p - '0') <= 9u) {
483
0
            unsigned d = (unsigned)(*p - '0');
484
0
            if (ndigits < max_sig_frac) {
485
0
                mantissa = mantissa * 10 + d;
486
0
                exp10--;
487
0
            }
488
            /* else: ignore trailing fraction digits beyond precision */
489
0
            ndigits++;
490
0
            ++p;
491
0
        }
492
0
    }
493
494
    /* Exponent part */
495
0
    if (p < end && (*p == 'e' || *p == 'E')) {
496
0
        has_exp = 1;
497
0
        ++p;
498
0
        int exp_neg = 0;
499
0
        if (p < end && *p == '+') ++p;
500
0
        else if (p < end && *p == '-') { exp_neg = 1; ++p; }
501
        /* JSON requires at least one digit in exponent */
502
0
        if (p >= end || (unsigned)(*p - '0') > 9u) return NULL;
503
0
        int exp_val = 0;
504
0
        while (p < end && (unsigned)(*p - '0') <= 9u) {
505
0
            exp_val = exp_val * 10 + (*p - '0');
506
0
            if (exp_val > 9999) {
507
                /* Prevent overflow; will fall through to strtod */
508
0
                while (p < end && (unsigned)(*p - '0') <= 9u) ++p;
509
0
                break;
510
0
            }
511
0
            ++p;
512
0
        }
513
0
        exp10 += exp_neg ? -exp_val : exp_val;
514
0
    }
515
516
    /* ---- Integer fast path (no fraction, no exponent, fits int64) ---- */
517
    /* exp10 == 0 ensures all digits were accumulated (none truncated by max_sig) */
518
0
    if (!has_frac && !has_exp && !mantissa_overflow && exp10 == 0) {
519
0
        uint64_t mag = mantissa;
520
0
        int fits;
521
0
        if (!neg)
522
0
            fits = (mag <= (uint64_t)INT64_MAX);
523
0
        else
524
0
            fits = (mag <= (uint64_t)INT64_MAX + 1u);
525
0
        if (fits) {
526
0
            int64_t sv;
527
0
            if (neg && mag == (uint64_t)INT64_MAX + 1u)
528
0
                sv = INT64_MIN;
529
0
            else
530
0
                sv = neg ? -(int64_t)mag : (int64_t)mag;
531
0
            *is_int = 1;
532
0
            *ival   = sv;
533
0
            *dval   = (double)sv;
534
0
            return p;
535
0
        }
536
0
    }
537
538
    /* ---- Float fast path (Clinger's algorithm) ---- */
539
0
    if (!mantissa_overflow) {
540
0
        if (float32_mode) {
541
0
            float f;
542
0
            if (cj_fast_flt_convert(mantissa, exp10, neg, &f)) {
543
0
                *is_int = 0;
544
0
                *dval   = (double)f;
545
0
                *ival   = cj_dbl_to_i64((double)f);
546
0
                return p;
547
0
            }
548
0
        } else {
549
0
            double d;
550
0
            if (cj_fast_dbl_convert(mantissa, exp10, neg, &d)) {
551
0
                *is_int = 0;
552
0
                *dval   = d;
553
0
                *ival   = cj_dbl_to_i64(d);
554
0
                return p;
555
0
            }
556
0
        }
557
0
    }
558
559
    /* ---- Fallback: strtod (handles extreme exponents, >19 digits) ---- */
560
0
    char *eptr = NULL;
561
0
    double d = strtod(start, &eptr);
562
0
    if (eptr == start) return NULL;
563
0
    if (float32_mode) d = (double)(float)d;
564
0
    *is_int = 0;
565
0
    *dval   = d;
566
0
    *ival   = cj_dbl_to_i64(d);
567
0
    return eptr;
568
0
}
569
570
/* ======================================================================
571
 * STRING UNESCAPING (C-style)
572
 * ====================================================================== */
573
574
0
static int cj_hex4(const char *p) {
575
0
    int v = 0;
576
0
    for (int i = 0; i < 4; ++i) {
577
0
        char c = p[i];
578
0
        int d;
579
0
        if      (c >= '0' && c <= '9') d = c - '0';
580
0
        else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
581
0
        else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
582
0
        else return -1;
583
0
        v = (v << 4) | d;
584
0
    }
585
0
    return v;
586
0
}
587
588
0
static int cj_encode_utf8(unsigned int cp, char *buf) {
589
0
    if (cp <= 0x7Fu) {
590
0
        buf[0] = (char)cp;
591
0
        return 1;
592
0
    } else if (cp <= 0x7FFu) {
593
0
        buf[0] = (char)(0xC0u | (cp >> 6));
594
0
        buf[1] = (char)(0x80u | (cp & 0x3Fu));
595
0
        return 2;
596
0
    } else if (cp <= 0xFFFFu) {
597
0
        buf[0] = (char)(0xE0u | (cp >> 12));
598
0
        buf[1] = (char)(0x80u | ((cp >> 6) & 0x3Fu));
599
0
        buf[2] = (char)(0x80u | (cp & 0x3Fu));
600
0
        return 3;
601
0
    } else if (cp <= 0x10FFFFu) {
602
0
        buf[0] = (char)(0xF0u | (cp >> 18));
603
0
        buf[1] = (char)(0x80u | ((cp >> 12) & 0x3Fu));
604
0
        buf[2] = (char)(0x80u | ((cp >> 6) & 0x3Fu));
605
0
        buf[3] = (char)(0x80u | (cp & 0x3Fu));
606
0
        return 4;
607
0
    }
608
0
    return 0;
609
0
}
610
611
/*
612
 * Parse and unescape a JSON string from [p, end) where p is AFTER the
613
 * opening '"' and end is the INCLUSIVE closing '"'.
614
 * Caller must free() the returned pointer.
615
 * Returns NULL on allocation failure.
616
 */
617
static char *cj_unescape_string(const char *p, const char *str_end,
618
0
                                 size_t *out_len) {
619
0
    size_t alloc = (size_t)(str_end - p) + 1;
620
0
    char *out    = (char *)malloc(alloc);
621
0
    if (!out) return NULL;
622
0
    char *dst = out;
623
624
0
    while (p < str_end) {
625
        /* Track start of literal run so we can copy it before processing
626
         * the special character that ends it. */
627
0
        const char *run = p;
628
0
        p = cj_scan_str(p, str_end);
629
        /* Copy non-special (literal) bytes from run to p */
630
0
        if (p > run) {
631
0
            size_t n = (size_t)(p - run);
632
0
            memcpy(dst, run, n);
633
0
            dst += n;
634
0
        }
635
0
        if (p >= str_end) break;
636
637
0
        unsigned char c = (unsigned char)*p;
638
0
        if (c == '"') {
639
0
            break; /* should not happen - caller passes str_end=position of '"' */
640
0
        } else if (c == '\\') {
641
0
            ++p;
642
0
            if (p >= str_end) { free(out); return NULL; }
643
0
            unsigned char esc = (unsigned char)*p++;
644
0
            switch (esc) {
645
0
                case '"':  *dst++ = '"';  break;
646
0
                case '\\': *dst++ = '\\'; break;
647
0
                case '/':  *dst++ = '/';  break;
648
0
                case 'b':  *dst++ = '\b'; break;
649
0
                case 'f':  *dst++ = '\f'; break;
650
0
                case 'n':  *dst++ = '\n'; break;
651
0
                case 'r':  *dst++ = '\r'; break;
652
0
                case 't':  *dst++ = '\t'; break;
653
0
                case 'u': {
654
0
                    if (p + 4 > str_end) { free(out); return NULL; }
655
0
                    int cp = cj_hex4(p);
656
0
                    if (cp < 0) { free(out); return NULL; }
657
0
                    p += 4;
658
0
                    if (cp >= 0xD800 && cp <= 0xDBFF &&
659
0
                        p + 6 <= str_end && p[0] == '\\' && p[1] == 'u') {
660
0
                        int cp2 = cj_hex4(p + 2);
661
0
                        if (cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
662
0
                            unsigned int full = 0x10000u +
663
0
                                (((unsigned int)cp - 0xD800u) << 10) +
664
0
                                ((unsigned int)cp2 - 0xDC00u);
665
0
                            dst += cj_encode_utf8(full, dst);
666
0
                            p += 6;
667
0
                            break;
668
0
                        }
669
0
                    }
670
0
                    dst += cj_encode_utf8((unsigned int)cp, dst);
671
0
                    break;
672
0
                }
673
0
                default:
674
                    /* Unknown escape sequence is invalid in JSON */
675
0
                    free(out);
676
0
                    return NULL;
677
0
            }
678
0
        } else if (c < 0x20u) {
679
            /* Invalid unescaped control character in JSON string: treat as error */
680
0
            free(out);
681
0
            return NULL;
682
0
        } else {
683
            /* Should not be reached since scan_str stops here only for
684
               special chars - but guard just in case */
685
0
            *dst++ = (char)c;
686
0
            ++p;
687
0
        }
688
0
    }
689
690
0
    *dst = '\0';
691
0
    *out_len = (size_t)(dst - out);
692
0
    return out;
693
0
}
694
695
/* ======================================================================
696
 * FORWARD DECLARATIONS
697
 * ====================================================================== */
698
699
/*
700
 * tinygltf_json is the main JSON value class.
701
 * Its data layout is C-style (all members public, named with trailing _).
702
 */
703
class tinygltf_json;
704
705
/*
706
 * tinygltf_json_member stores one key-value pair in a JSON object.
707
 * Must be defined AFTER tinygltf_json is complete (contains json by value).
708
 */
709
struct tinygltf_json_member;
710
711
/* ======================================================================
712
 * tinygltf_json CLASS DECLARATION
713
 *
714
 * All data members are public (C-style struct convention).
715
 * Methods are declared here and implemented after tinygltf_json_member
716
 * is fully defined.
717
 * ====================================================================== */
718
719
class tinygltf_json {
720
public:
721
    /* ------------------------------------------------------------------
722
     * nlohmann-compatible value type enum
723
     * ------------------------------------------------------------------ */
724
    enum class value_t : uint8_t {
725
        null             = 0,
726
        boolean          = 1,
727
        number_integer   = 2,
728
        number_unsigned  = 3,
729
        number_float     = 4,
730
        string           = 5,
731
        array            = 6,
732
        object           = 7,
733
        /* Aliases from nlohmann/json that tinygltf.h references */
734
        discarded        = 8,
735
        binary           = 9
736
    };
737
738
    /* ------------------------------------------------------------------
739
     * C-style data storage (all public for direct access from C functions)
740
     * ------------------------------------------------------------------ */
741
742
    /* Type tag: one of CJ_NULL, CJ_BOOL, CJ_INT, CJ_REAL, CJ_STRING,
743
       CJ_ARRAY, CJ_OBJECT */
744
    int type_;
745
746
    /* Primitive values (union for space efficiency) */
747
    union {
748
        int64_t i_;   /* CJ_INT  */
749
        double  d_;   /* CJ_REAL */
750
        int     b_;   /* CJ_BOOL: 0 or 1 */
751
    };
752
753
    /* String storage */
754
    char   *str_;      /* CJ_STRING: owned, null-terminated */
755
    size_t  str_len_;
756
757
    /* Array storage: flat array of tinygltf_json objects (owned) */
758
    tinygltf_json *arr_data_;
759
    size_t         arr_size_;
760
    size_t         arr_cap_;
761
762
    /* Object storage: flat array of tinygltf_json_member (owned) */
763
    tinygltf_json_member *obj_data_;
764
    size_t                obj_size_;
765
    size_t                obj_cap_;
766
767
    /* ------------------------------------------------------------------
768
     * Iterator type (forward-declared here, defined later)
769
     * ------------------------------------------------------------------ */
770
    class iterator;
771
    using const_iterator = iterator;
772
773
    /* ------------------------------------------------------------------
774
     * Low-level helpers (implementations deferred until member is complete)
775
     * ------------------------------------------------------------------ */
776
    void init_null_();
777
    void destroy_();
778
    void copy_from_(const tinygltf_json &o);
779
    tinygltf_json_member *find_member_(const char *key) const;
780
    int  obj_reserve_();
781
    int  arr_reserve_();
782
    void make_object_();
783
    void make_array_();
784
785
    /* ------------------------------------------------------------------
786
     * Constructors and destructor
787
     * ------------------------------------------------------------------ */
788
    tinygltf_json();
789
    tinygltf_json(std::nullptr_t);
790
    tinygltf_json(bool b);
791
    tinygltf_json(int i);
792
    tinygltf_json(int64_t i);
793
    tinygltf_json(uint64_t u);
794
    tinygltf_json(double d);
795
    tinygltf_json(float f);
796
    tinygltf_json(const char *s);
797
    tinygltf_json(const std::string &s);
798
    tinygltf_json(const tinygltf_json &o);
799
    tinygltf_json(tinygltf_json &&o) noexcept;
800
    ~tinygltf_json();
801
802
    tinygltf_json &operator=(const tinygltf_json &o);
803
    tinygltf_json &operator=(tinygltf_json &&o) noexcept;
804
805
    /* ------------------------------------------------------------------
806
     * Type checks (nlohmann-compatible)
807
     * ------------------------------------------------------------------ */
808
    value_t type() const;
809
0
    bool is_null()            const { return type_ == CJ_NULL; }
810
0
    bool is_boolean()         const { return type_ == CJ_BOOL; }
811
0
    bool is_number()          const { return type_ == CJ_INT || type_ == CJ_REAL; }
812
0
    bool is_number_integer()  const { return type_ == CJ_INT; }
813
0
    bool is_number_unsigned() const { return type_ == CJ_INT && i_ >= 0; }
814
0
    bool is_number_float()    const { return type_ == CJ_REAL; }
815
0
    bool is_string()          const { return type_ == CJ_STRING; }
816
0
    bool is_array()           const { return type_ == CJ_ARRAY; }
817
0
    bool is_object()          const { return type_ == CJ_OBJECT; }
818
819
    /* ------------------------------------------------------------------
820
     * Value access (template specializations after class)
821
     * ------------------------------------------------------------------ */
822
    template<typename T> T get() const;
823
824
    /* ------------------------------------------------------------------
825
     * Container methods
826
     * ------------------------------------------------------------------ */
827
    size_t size() const;
828
    bool   empty() const;
829
830
    /* ------------------------------------------------------------------
831
     * Array operations
832
     * ------------------------------------------------------------------ */
833
    void push_back(tinygltf_json &&v);
834
    void push_back(const tinygltf_json &v);
835
    /* Ensure value is an array (no-op if already array). */
836
0
    void set_array() { if (type_ != CJ_ARRAY) make_array_(); }
837
838
    /* ------------------------------------------------------------------
839
     * Object operations
840
     * ------------------------------------------------------------------ */
841
    tinygltf_json &operator[](const char *key);
842
    tinygltf_json &operator[](const std::string &key);
843
844
    /* ------------------------------------------------------------------
845
     * Iterators
846
     * ------------------------------------------------------------------ */
847
    iterator begin();
848
    iterator end();
849
    iterator begin() const;
850
    iterator end() const;
851
    iterator find(const char *key) const;
852
    iterator find(const char *key);
853
    void erase(iterator &it);
854
855
    /* ------------------------------------------------------------------
856
     * Static factories
857
     * ------------------------------------------------------------------ */
858
    static tinygltf_json object();
859
860
    /* ------------------------------------------------------------------
861
     * Serialization / deserialization
862
     * ------------------------------------------------------------------ */
863
    std::string dump(int indent = -1) const;
864
865
    /* allow_exceptions is honoured only when TINYGLTF_JSON_USE_EXCEPTIONS is
866
     * defined; otherwise it is accepted for API compatibility but has no
867
     * effect — parse errors always return a null value silently. */
868
    static tinygltf_json parse(const char *first, const char *last,
869
                               std::nullptr_t = nullptr,
870
                               bool allow_exceptions = false);
871
872
    /* Parse with float32 mode: floating-point values are parsed at single
873
     * precision for speed.  Breaks strict JSON double-precision conformance
874
     * but sufficient for glTF (which stores geometry/animation data as
875
     * single-precision floats in buffers anyway). */
876
    static tinygltf_json parse_float32(const char *first, const char *last);
877
};
878
879
/* ======================================================================
880
 * tinygltf_json_member FULL DEFINITION
881
 * (tinygltf_json must be complete before this)
882
 * ====================================================================== */
883
884
struct tinygltf_json_member {
885
    char             *key;      /* owned, null-terminated */
886
    size_t            key_len;
887
    tinygltf_json     val;      /* value stored inline */
888
889
0
    tinygltf_json_member() : key(NULL), key_len(0), val() {}
890
0
    ~tinygltf_json_member() { free(key); key = NULL; }
891
892
    tinygltf_json_member(const tinygltf_json_member &o)
893
0
        : key(NULL), key_len(o.key_len), val(o.val) {
894
0
        if (o.key) {
895
0
            key = (char *)malloc(o.key_len + 1);
896
0
            if (key) memcpy(key, o.key, o.key_len + 1);
897
0
            else key_len = 0; /* malloc failure: keep key==NULL, len==0 */
898
0
        }
899
0
    }
900
901
    tinygltf_json_member(tinygltf_json_member &&o) noexcept
902
0
        : key(o.key), key_len(o.key_len),
903
0
          val(static_cast<tinygltf_json &&>(o.val)) {
904
0
        o.key = NULL;
905
0
        o.key_len = 0;
906
0
    }
907
908
0
    tinygltf_json_member &operator=(const tinygltf_json_member &o) {
909
0
        if (this != &o) {
910
0
            free(key);
911
0
            key = NULL;
912
0
            key_len = o.key_len;
913
0
            val = o.val;
914
0
            if (o.key) {
915
0
                key = (char *)malloc(o.key_len + 1);
916
0
                if (key) memcpy(key, o.key, o.key_len + 1);
917
0
                else key_len = 0; /* malloc failure: keep key==NULL, len==0 */
918
0
            }
919
0
        }
920
0
        return *this;
921
0
    }
922
923
0
    tinygltf_json_member &operator=(tinygltf_json_member &&o) noexcept {
924
0
        if (this != &o) {
925
0
            free(key);
926
0
            key     = o.key;
927
0
            key_len = o.key_len;
928
0
            val     = static_cast<tinygltf_json &&>(o.val);
929
0
            o.key     = NULL;
930
0
            o.key_len = 0;
931
0
        }
932
0
        return *this;
933
0
    }
934
};
935
936
/* ======================================================================
937
 * tinygltf_json::iterator
938
 * ====================================================================== */
939
940
class tinygltf_json::iterator {
941
public:
942
    static const int MODE_ARRAY  = 0;
943
    static const int MODE_OBJECT = 1;
944
945
    int mode_;
946
    union {
947
        tinygltf_json        *arr_ptr_;
948
        tinygltf_json_member *obj_ptr_;
949
    };
950
951
0
    iterator() : mode_(MODE_ARRAY), arr_ptr_(NULL) {}
952
953
    explicit iterator(tinygltf_json *p)
954
0
        : mode_(MODE_ARRAY), arr_ptr_(p) {}
955
956
    explicit iterator(tinygltf_json_member *p)
957
0
        : mode_(MODE_OBJECT), obj_ptr_(p) {}
958
959
    /* Pre-increment */
960
0
    iterator &operator++() {
961
0
        if (mode_ == MODE_ARRAY)  ++arr_ptr_;
962
0
        else                      ++obj_ptr_;
963
0
        return *this;
964
0
    }
965
966
    /* Post-increment */
967
0
    iterator operator++(int) {
968
0
        iterator tmp = *this;
969
0
        ++(*this);
970
0
        return tmp;
971
0
    }
972
973
0
    tinygltf_json &operator*() {
974
0
        return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val;
975
0
    }
976
0
    const tinygltf_json &operator*() const {
977
0
        return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val;
978
0
    }
979
0
    tinygltf_json *operator->() {
980
0
        return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val;
981
0
    }
982
0
    const tinygltf_json *operator->() const {
983
0
        return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val;
984
0
    }
985
986
0
    std::string key() const {
987
0
        if (mode_ == MODE_OBJECT && obj_ptr_ && obj_ptr_->key)
988
0
            return std::string(obj_ptr_->key, obj_ptr_->key_len);
989
0
        return std::string();
990
0
    }
991
992
0
    tinygltf_json &value() {
993
0
        return operator*();
994
0
    }
995
0
    const tinygltf_json &value() const {
996
0
        return operator*();
997
0
    }
998
999
0
    bool operator==(const iterator &o) const {
1000
0
        if (mode_ != o.mode_) return false;
1001
0
        return (mode_ == MODE_ARRAY)
1002
0
            ? (arr_ptr_ == o.arr_ptr_)
1003
0
            : (obj_ptr_ == o.obj_ptr_);
1004
0
    }
1005
0
    bool operator!=(const iterator &o) const { return !(*this == o); }
1006
};
1007
1008
/* ======================================================================
1009
 * tinygltf_json METHOD IMPLEMENTATIONS
1010
 * (Now that tinygltf_json_member is fully defined)
1011
 * ====================================================================== */
1012
1013
0
inline void tinygltf_json::init_null_() {
1014
0
    type_     = CJ_NULL;
1015
0
    i_        = 0;
1016
0
    str_      = NULL;  str_len_  = 0;
1017
0
    arr_data_ = NULL;  arr_size_ = 0; arr_cap_ = 0;
1018
0
    obj_data_ = NULL;  obj_size_ = 0; obj_cap_ = 0;
1019
0
}
1020
1021
0
inline void tinygltf_json::destroy_() {
1022
0
    if (type_ == CJ_STRING) {
1023
0
        free(str_);
1024
0
        str_ = NULL;
1025
0
    } else if (type_ == CJ_ARRAY) {
1026
0
        for (size_t i = 0; i < arr_size_; ++i)
1027
0
            arr_data_[i].~tinygltf_json();
1028
0
        free(arr_data_);
1029
0
        arr_data_ = NULL;
1030
0
        arr_size_ = arr_cap_ = 0;
1031
0
    } else if (type_ == CJ_OBJECT) {
1032
0
        for (size_t i = 0; i < obj_size_; ++i)
1033
0
            obj_data_[i].~tinygltf_json_member();
1034
0
        free(obj_data_);
1035
0
        obj_data_ = NULL;
1036
0
        obj_size_ = obj_cap_ = 0;
1037
0
    }
1038
0
    type_ = CJ_NULL;
1039
0
}
1040
1041
0
inline void tinygltf_json::copy_from_(const tinygltf_json &o) {
1042
0
    type_     = o.type_;
1043
0
    i_        = o.i_;
1044
0
    str_      = NULL; str_len_  = 0;
1045
0
    arr_data_ = NULL; arr_size_ = 0; arr_cap_ = 0;
1046
0
    obj_data_ = NULL; obj_size_ = 0; obj_cap_ = 0;
1047
1048
0
    if (o.type_ == CJ_STRING) {
1049
0
        if (o.str_) {
1050
0
            str_len_ = o.str_len_;
1051
0
            str_ = (char *)malloc(str_len_ + 1);
1052
0
            if (str_) memcpy(str_, o.str_, str_len_ + 1);
1053
0
            else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
1054
0
        }
1055
0
    } else if (o.type_ == CJ_ARRAY) {
1056
0
        if (o.arr_size_ > 0) {
1057
            /* Guard against multiplication overflow */
1058
0
            if (o.arr_size_ <= SIZE_MAX / sizeof(tinygltf_json)) {
1059
0
                arr_data_ = (tinygltf_json *)malloc(
1060
0
                    o.arr_size_ * sizeof(tinygltf_json));
1061
0
                if (arr_data_) {
1062
0
                    arr_size_ = 0;
1063
0
                    arr_cap_  = o.arr_size_;
1064
0
                    for (size_t i = 0; i < o.arr_size_; ++i) {
1065
0
                        new (&arr_data_[i]) tinygltf_json(o.arr_data_[i]);
1066
0
                        ++arr_size_;
1067
0
                    }
1068
0
                }
1069
0
            }
1070
0
        }
1071
0
    } else if (o.type_ == CJ_OBJECT) {
1072
0
        if (o.obj_size_ > 0) {
1073
            /* Guard against multiplication overflow */
1074
0
            if (o.obj_size_ <= SIZE_MAX / sizeof(tinygltf_json_member)) {
1075
0
                obj_data_ = (tinygltf_json_member *)malloc(
1076
0
                    o.obj_size_ * sizeof(tinygltf_json_member));
1077
0
                if (obj_data_) {
1078
0
                    obj_size_ = 0;
1079
0
                    obj_cap_  = o.obj_size_;
1080
0
                    for (size_t i = 0; i < o.obj_size_; ++i) {
1081
0
                        new (&obj_data_[i]) tinygltf_json_member(o.obj_data_[i]);
1082
0
                        ++obj_size_;
1083
0
                    }
1084
0
                }
1085
0
            }
1086
0
        }
1087
0
    }
1088
0
}
1089
1090
inline tinygltf_json_member *tinygltf_json::find_member_(
1091
0
    const char *key) const {
1092
0
    if (!key) return NULL;
1093
0
    size_t klen = strlen(key);
1094
0
    for (size_t i = 0; i < obj_size_; ++i) {
1095
        /* Guard against NULL key (can occur if malloc failed during insert) */
1096
0
        if (obj_data_[i].key == NULL) continue;
1097
0
        if (obj_data_[i].key_len == klen &&
1098
0
            memcmp(obj_data_[i].key, key, klen) == 0)
1099
0
            return &obj_data_[i];
1100
0
    }
1101
0
    return NULL;
1102
0
}
1103
1104
0
inline int tinygltf_json::obj_reserve_() {
1105
0
    if (obj_size_ < obj_cap_) return 1;
1106
0
    size_t new_cap = obj_cap_ ? obj_cap_ * 2 : 8;
1107
    /* Guard against allocation overflow */
1108
0
    if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json_member)) return 0;
1109
0
    tinygltf_json_member *nd = (tinygltf_json_member *)malloc(
1110
0
        new_cap * sizeof(tinygltf_json_member));
1111
0
    if (!nd) return 0;
1112
0
    for (size_t i = 0; i < obj_size_; ++i) {
1113
0
        new (&nd[i]) tinygltf_json_member(
1114
0
            static_cast<tinygltf_json_member &&>(obj_data_[i]));
1115
0
        obj_data_[i].~tinygltf_json_member();
1116
0
    }
1117
0
    free(obj_data_);
1118
0
    obj_data_ = nd;
1119
0
    obj_cap_  = new_cap;
1120
0
    return 1;
1121
0
}
1122
1123
0
inline int tinygltf_json::arr_reserve_() {
1124
0
    if (arr_size_ < arr_cap_) return 1;
1125
0
    size_t new_cap = arr_cap_ ? arr_cap_ * 2 : 8;
1126
    /* Guard against allocation overflow */
1127
0
    if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json)) return 0;
1128
0
    tinygltf_json *nd = (tinygltf_json *)malloc(
1129
0
        new_cap * sizeof(tinygltf_json));
1130
0
    if (!nd) return 0;
1131
0
    for (size_t i = 0; i < arr_size_; ++i) {
1132
0
        new (&nd[i]) tinygltf_json(
1133
0
            static_cast<tinygltf_json &&>(arr_data_[i]));
1134
0
        arr_data_[i].destroy_();
1135
0
        arr_data_[i].type_ = CJ_NULL;
1136
0
    }
1137
0
    free(arr_data_);
1138
0
    arr_data_ = nd;
1139
0
    arr_cap_  = new_cap;
1140
0
    return 1;
1141
0
}
1142
1143
0
inline void tinygltf_json::make_object_() {
1144
0
    destroy_();
1145
0
    type_ = CJ_OBJECT;
1146
0
}
1147
1148
0
inline void tinygltf_json::make_array_() {
1149
0
    destroy_();
1150
0
    type_ = CJ_ARRAY;
1151
0
}
1152
1153
/* Constructors */
1154
0
inline tinygltf_json::tinygltf_json()           { init_null_(); }
1155
inline tinygltf_json::tinygltf_json(std::nullptr_t) { init_null_(); }
1156
0
inline tinygltf_json::tinygltf_json(bool b)     { init_null_(); type_ = CJ_BOOL; b_ = b ? 1 : 0; }
1157
0
inline tinygltf_json::tinygltf_json(int i)      { init_null_(); type_ = CJ_INT;  i_ = (int64_t)i; }
1158
inline tinygltf_json::tinygltf_json(int64_t i)  { init_null_(); type_ = CJ_INT;  i_ = i; }
1159
0
inline tinygltf_json::tinygltf_json(uint64_t u) {
1160
0
    init_null_();
1161
0
    if (u <= (uint64_t)INT64_MAX) {
1162
0
        type_ = CJ_INT;
1163
0
        i_    = (int64_t)u;
1164
0
    } else {
1165
0
        type_ = CJ_REAL;
1166
0
        d_    = (double)u;
1167
0
    }
1168
0
}
1169
0
inline tinygltf_json::tinygltf_json(double d)   { init_null_(); type_ = CJ_REAL; d_ = d; }
1170
inline tinygltf_json::tinygltf_json(float f)    { init_null_(); type_ = CJ_REAL; d_ = (double)f; }
1171
1172
0
inline tinygltf_json::tinygltf_json(const char *s) {
1173
0
    init_null_();
1174
0
    if (s) {
1175
0
        type_    = CJ_STRING;
1176
0
        str_len_ = strlen(s);
1177
0
        str_     = (char *)malloc(str_len_ + 1);
1178
0
        if (str_) memcpy(str_, s, str_len_ + 1);
1179
0
        else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
1180
0
    }
1181
0
}
1182
1183
0
inline tinygltf_json::tinygltf_json(const std::string &s) {
1184
0
    init_null_();
1185
0
    type_    = CJ_STRING;
1186
0
    str_len_ = s.size();
1187
0
    str_     = (char *)malloc(str_len_ + 1);
1188
0
    if (str_) memcpy(str_, s.c_str(), str_len_ + 1);
1189
0
    else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
1190
0
}
1191
1192
0
inline tinygltf_json::tinygltf_json(const tinygltf_json &o) {
1193
0
    init_null_();
1194
0
    copy_from_(o);
1195
0
}
1196
1197
0
inline tinygltf_json::tinygltf_json(tinygltf_json &&o) noexcept {
1198
0
    type_     = o.type_;
1199
0
    i_        = o.i_;
1200
0
    str_      = o.str_;
1201
0
    str_len_  = o.str_len_;
1202
0
    arr_data_ = o.arr_data_;  arr_size_ = o.arr_size_;  arr_cap_ = o.arr_cap_;
1203
0
    obj_data_ = o.obj_data_;  obj_size_ = o.obj_size_;  obj_cap_ = o.obj_cap_;
1204
0
    o.type_     = CJ_NULL;
1205
0
    o.str_      = NULL;
1206
0
    o.arr_data_ = NULL;  o.arr_size_ = 0;  o.arr_cap_ = 0;
1207
0
    o.obj_data_ = NULL;  o.obj_size_ = 0;  o.obj_cap_ = 0;
1208
0
}
1209
1210
0
inline tinygltf_json::~tinygltf_json() { destroy_(); }
1211
1212
0
inline tinygltf_json &tinygltf_json::operator=(const tinygltf_json &o) {
1213
0
    if (this != &o) { destroy_(); copy_from_(o); }
1214
0
    return *this;
1215
0
}
1216
1217
0
inline tinygltf_json &tinygltf_json::operator=(tinygltf_json &&o) noexcept {
1218
0
    if (this != &o) {
1219
0
        destroy_();
1220
0
        type_     = o.type_;
1221
0
        i_        = o.i_;
1222
0
        str_      = o.str_;
1223
0
        str_len_  = o.str_len_;
1224
0
        arr_data_ = o.arr_data_;  arr_size_ = o.arr_size_;  arr_cap_ = o.arr_cap_;
1225
0
        obj_data_ = o.obj_data_;  obj_size_ = o.obj_size_;  obj_cap_ = o.obj_cap_;
1226
0
        o.type_     = CJ_NULL;
1227
0
        o.str_      = NULL;
1228
0
        o.arr_data_ = NULL;  o.arr_size_ = 0;  o.arr_cap_ = 0;
1229
0
        o.obj_data_ = NULL;  o.obj_size_ = 0;  o.obj_cap_ = 0;
1230
0
    }
1231
0
    return *this;
1232
0
}
1233
1234
0
inline tinygltf_json::value_t tinygltf_json::type() const {
1235
0
    switch (type_) {
1236
0
        case CJ_NULL:   return value_t::null;
1237
0
        case CJ_BOOL:   return value_t::boolean;
1238
0
        case CJ_INT:    return i_ >= 0 ? value_t::number_unsigned
1239
0
                                       : value_t::number_integer;
1240
0
        case CJ_REAL:   return value_t::number_float;
1241
0
        case CJ_STRING: return value_t::string;
1242
0
        case CJ_ARRAY:  return value_t::array;
1243
0
        case CJ_OBJECT: return value_t::object;
1244
0
        default:        return value_t::null;
1245
0
    }
1246
0
}
1247
1248
0
inline size_t tinygltf_json::size() const {
1249
0
    if (type_ == CJ_ARRAY)  return arr_size_;
1250
0
    if (type_ == CJ_OBJECT) return obj_size_;
1251
0
    return 0;
1252
0
}
1253
1254
0
inline bool tinygltf_json::empty() const {
1255
0
    if (type_ == CJ_ARRAY)  return arr_size_ == 0;
1256
0
    if (type_ == CJ_OBJECT) return obj_size_ == 0;
1257
0
    return true;
1258
0
}
1259
1260
0
inline void tinygltf_json::push_back(tinygltf_json &&v) {
1261
0
    if (type_ != CJ_ARRAY) make_array_();
1262
0
    if (!arr_reserve_()) return;
1263
0
    new (&arr_data_[arr_size_]) tinygltf_json(
1264
0
        static_cast<tinygltf_json &&>(v));
1265
0
    ++arr_size_;
1266
0
}
1267
1268
0
inline void tinygltf_json::push_back(const tinygltf_json &v) {
1269
0
    push_back(tinygltf_json(v));
1270
0
}
1271
1272
0
inline tinygltf_json &tinygltf_json::operator[](const char *key) {
1273
    /* Degraded-mode fallback for API misuse (null key) or OOM.
1274
     * Returns a reference to a shared static null object.  This is the same
1275
     * best-effort pattern used for the OOM path below.
1276
     * CAUTION: the static is shared across calls; modifications through this
1277
     * reference persist (same caveat as the OOM fallback).  Callers should
1278
     * treat a null-key or OOM insert as a no-op. */
1279
0
    static tinygltf_json null_fallback;
1280
0
    if (!key) return null_fallback;
1281
0
    if (type_ != CJ_OBJECT) make_object_();
1282
0
    tinygltf_json_member *m = find_member_(key);
1283
0
    if (m) return m->val;
1284
0
    if (!obj_reserve_()) return null_fallback;
1285
0
    tinygltf_json_member *nm = &obj_data_[obj_size_];
1286
0
    new (nm) tinygltf_json_member();
1287
0
    size_t klen = strlen(key);
1288
0
    nm->key = (char *)malloc(klen + 1);
1289
0
    if (!nm->key) {
1290
        /* Roll back insertion on key allocation failure: destroy the
1291
         * placement-new'd member and do not bump obj_size_, keeping the
1292
         * object in a consistent state. */
1293
0
        nm->~tinygltf_json_member();
1294
0
        return null_fallback;
1295
0
    }
1296
0
    memcpy(nm->key, key, klen + 1);
1297
0
    nm->key_len = klen;
1298
0
    ++obj_size_;
1299
0
    return nm->val;
1300
0
}
1301
1302
0
inline tinygltf_json &tinygltf_json::operator[](const std::string &key) {
1303
0
    return operator[](key.c_str());
1304
0
}
1305
1306
0
inline tinygltf_json::iterator tinygltf_json::begin() {
1307
0
    if (type_ == CJ_ARRAY)  return iterator(arr_data_);
1308
0
    if (type_ == CJ_OBJECT) return iterator(obj_data_);
1309
0
    return iterator((tinygltf_json *)NULL);
1310
0
}
1311
0
inline tinygltf_json::iterator tinygltf_json::end() {
1312
0
    if (type_ == CJ_ARRAY)  return iterator(arr_data_ + arr_size_);
1313
0
    if (type_ == CJ_OBJECT) return iterator(obj_data_ + obj_size_);
1314
0
    return iterator((tinygltf_json *)NULL);
1315
0
}
1316
0
inline tinygltf_json::iterator tinygltf_json::begin() const {
1317
0
    tinygltf_json *self = const_cast<tinygltf_json *>(this);
1318
0
    return self->begin();
1319
0
}
1320
0
inline tinygltf_json::iterator tinygltf_json::end() const {
1321
0
    tinygltf_json *self = const_cast<tinygltf_json *>(this);
1322
0
    return self->end();
1323
0
}
1324
1325
0
inline tinygltf_json::iterator tinygltf_json::find(const char *key) {
1326
0
    if (type_ == CJ_OBJECT) {
1327
0
        tinygltf_json_member *m = find_member_(key);
1328
0
        if (m) return iterator(m);
1329
0
        return iterator(obj_data_ + obj_size_);
1330
0
    }
1331
0
    return iterator((tinygltf_json *)NULL);
1332
0
}
1333
0
inline tinygltf_json::iterator tinygltf_json::find(const char *key) const {
1334
0
    return const_cast<tinygltf_json *>(this)->find(key);
1335
0
}
1336
1337
0
inline void tinygltf_json::erase(tinygltf_json::iterator &it) {
1338
0
    if (type_ != CJ_OBJECT || it.mode_ != iterator::MODE_OBJECT) return;
1339
0
    ptrdiff_t idx = it.obj_ptr_ - obj_data_;
1340
0
    if (idx < 0 || (size_t)idx >= obj_size_) return;
1341
0
    obj_data_[idx].~tinygltf_json_member();
1342
0
    for (size_t i = (size_t)idx; i + 1 < obj_size_; ++i) {
1343
0
        new (&obj_data_[i]) tinygltf_json_member(
1344
0
            static_cast<tinygltf_json_member &&>(obj_data_[i + 1]));
1345
0
        obj_data_[i + 1].~tinygltf_json_member();
1346
0
    }
1347
0
    --obj_size_;
1348
0
    it = end();
1349
0
}
1350
1351
0
inline tinygltf_json tinygltf_json::object() {
1352
0
    tinygltf_json j;
1353
0
    j.make_object_();
1354
0
    return j;
1355
0
}
1356
1357
/* ======================================================================
1358
 * get<T>() specializations
1359
 * ====================================================================== */
1360
1361
0
template<> inline double tinygltf_json::get<double>() const {
1362
0
    if (type_ == CJ_REAL) return d_;
1363
0
    if (type_ == CJ_INT)  return (double)i_;
1364
0
    return 0.0;
1365
0
}
1366
1367
0
template<> inline int tinygltf_json::get<int>() const {
1368
0
    if (type_ == CJ_INT)  return (int)i_;
1369
0
    if (type_ == CJ_REAL) return (int)d_;
1370
0
    return 0;
1371
0
}
1372
1373
0
template<> inline int64_t tinygltf_json::get<int64_t>() const {
1374
0
    if (type_ == CJ_INT)  return i_;
1375
0
    if (type_ == CJ_REAL) return (int64_t)d_;
1376
0
    return 0;
1377
0
}
1378
1379
0
template<> inline uint64_t tinygltf_json::get<uint64_t>() const {
1380
0
    if (type_ == CJ_INT)  return (uint64_t)i_;
1381
0
    if (type_ == CJ_REAL) return (uint64_t)d_;
1382
0
    return 0;
1383
0
}
1384
1385
0
template<> inline bool tinygltf_json::get<bool>() const {
1386
0
    if (type_ == CJ_BOOL) return b_ != 0;
1387
0
    return false;
1388
0
}
1389
1390
0
template<> inline std::string tinygltf_json::get<std::string>() const {
1391
0
    if (type_ == CJ_STRING && str_)
1392
0
        return std::string(str_, str_len_);
1393
0
    return std::string();
1394
0
}
1395
1396
/* Primary template for any T not explicitly specialised (e.g. size_t on
1397
 * platforms where it is a distinct type from all of the above, such as
1398
 * macOS 64-bit where uint64_t=unsigned long long but size_t=unsigned long).
1399
 * Falls back to a static_cast from the stored integer or floating-point value.
1400
 * For unsigned T: negative integer values produce 0 rather than wrapping. */
1401
template<typename T>
1402
inline T tinygltf_json::get() const {
1403
    if (type_ == CJ_INT) {
1404
        /* Guard unsigned types against sign-extension of negative values */
1405
        if ((T)(-1) > (T)(0) && i_ < 0) return (T)(0);
1406
        return static_cast<T>(i_);
1407
    }
1408
    if (type_ == CJ_REAL) return static_cast<T>(d_);
1409
    if (type_ == CJ_BOOL) return static_cast<T>(b_);
1410
    return T();
1411
}
1412
1413
/* ======================================================================
1414
 * PARSER (C-style iterative, explicit frame stack)
1415
 *
1416
 * Uses an explicit cj_frame stack instead of C recursion so that deeply
1417
 * nested JSON cannot overflow the call stack.  CJ_MAX_ITER limits both
1418
 * the container nesting depth (stack size) and serves as the iteration
1419
 * safety budget: a malformed input that keeps pushing containers without
1420
 * consuming content is rejected once the stack is full.
1421
 * ====================================================================== */
1422
1423
/* Maximum container nesting depth (size of the explicit frame stack) */
1424
0
#define CJ_MAX_ITER 512
1425
1426
/* One entry per open container (array or object) on the explicit stack */
1427
struct cj_frame {
1428
    tinygltf_json *container;  /* The array or object being populated */
1429
    int            is_object;  /* 0 = array, 1 = object */
1430
};
1431
1432
struct cj_parse_ctx {
1433
    const char *cur;
1434
    const char *end;
1435
    int         err;
1436
    int         float32_mode; /* 0 = double (default), 1 = float32 */
1437
    char        errmsg[256];
1438
};
1439
1440
0
static void cj_ctx_error(cj_parse_ctx *ctx, const char *msg) {
1441
0
    if (!ctx->err) {
1442
0
        ctx->err = 1;
1443
0
        strncpy(ctx->errmsg, msg, sizeof(ctx->errmsg) - 1);
1444
0
        ctx->errmsg[sizeof(ctx->errmsg) - 1] = '\0';
1445
0
    }
1446
0
}
1447
1448
/*
1449
 * Parse a JSON string from the current position.
1450
 * cur must point to the opening '"'.
1451
 * On success, advances cur past the closing '"' and sets *out_str (owned).
1452
 */
1453
static void cj_parse_string_to(cj_parse_ctx *ctx, char **out_str,
1454
0
                                size_t *out_len) {
1455
0
    assert(ctx->cur < ctx->end && *ctx->cur == '"');
1456
0
    ++ctx->cur; /* skip opening '"' */
1457
1458
0
    const char *p = ctx->cur;
1459
1460
    /* Fast path: find closing '"' without escapes */
1461
0
    while (p < ctx->end) {
1462
0
        p = cj_scan_str(p, ctx->end);
1463
0
        if (p >= ctx->end) {
1464
0
            cj_ctx_error(ctx, "unterminated string");
1465
0
            *out_str = NULL; *out_len = 0;
1466
0
            return;
1467
0
        }
1468
0
        if (*p == '"') {
1469
            /* No escapes: copy directly */
1470
0
            size_t len = (size_t)(p - ctx->cur);
1471
0
            char *s = (char *)malloc(len + 1);
1472
0
            if (!s) { cj_ctx_error(ctx, "out of memory"); *out_str = NULL; *out_len = 0; return; }
1473
0
            memcpy(s, ctx->cur, len);
1474
0
            s[len] = '\0';
1475
0
            *out_str = s;
1476
0
            *out_len = len;
1477
0
            ctx->cur = p + 1;
1478
0
            return;
1479
0
        }
1480
0
        if (*p == '\\') {
1481
            /* Has escapes: find true end, then unescape */
1482
0
            const char *scan = p;
1483
0
            while (scan < ctx->end) {
1484
0
                scan = cj_scan_str(scan, ctx->end);
1485
0
                if (scan >= ctx->end) { cj_ctx_error(ctx, "unterminated string"); *out_str = NULL; *out_len = 0; return; }
1486
0
                if (*scan == '"') break;
1487
0
                if (*scan == '\\') {
1488
0
                    ++scan;
1489
0
                    if (scan >= ctx->end) { cj_ctx_error(ctx, "truncated escape"); *out_str = NULL; *out_len = 0; return; }
1490
0
                    if (*scan == 'u') {
1491
                        /* \uXXXX requires exactly 4 hex digits after 'u' */
1492
0
                        if (scan + 5 > ctx->end) {
1493
0
                            cj_ctx_error(ctx, "truncated \\u escape");
1494
0
                            *out_str = NULL; *out_len = 0; return;
1495
0
                        }
1496
0
                        scan += 5;
1497
0
                    } else {
1498
0
                        ++scan;
1499
0
                    }
1500
0
                } else {
1501
                    /* cj_scan_str stopped at a control char (<0x20): invalid JSON */
1502
0
                    cj_ctx_error(ctx, "invalid control character in string");
1503
0
                    *out_str = NULL; *out_len = 0; return;
1504
0
                }
1505
0
            }
1506
            /* After the loop, scan must point to the closing '"' */
1507
0
            if (scan >= ctx->end) {
1508
0
                cj_ctx_error(ctx, "unterminated string");
1509
0
                *out_str = NULL; *out_len = 0; return;
1510
0
            }
1511
0
            if (ctx->err) { *out_str = NULL; *out_len = 0; return; }
1512
0
            *out_str = cj_unescape_string(ctx->cur, scan, out_len);
1513
0
            if (!*out_str) { cj_ctx_error(ctx, "string unescape failed"); }
1514
0
            ctx->cur = scan + 1;
1515
0
            return;
1516
0
        }
1517
        /* Control char (< 0x20) - treat as parse error (invalid JSON) */
1518
0
        cj_ctx_error(ctx, "invalid control character in string");
1519
0
        *out_str = NULL; *out_len = 0;
1520
0
        return;
1521
0
    }
1522
0
    cj_ctx_error(ctx, "unterminated string");
1523
0
    *out_str = NULL; *out_len = 0;
1524
0
}
1525
1526
/*
1527
 * Parse a scalar JSON value (string, number, bool, null) into *slot.
1528
 * ctx->cur must point to the first character of the value (whitespace
1529
 * already consumed).
1530
 */
1531
0
static void cj_parse_scalar(cj_parse_ctx *ctx, tinygltf_json *slot) {
1532
0
    char c = *ctx->cur;
1533
1534
0
    if (c == '"') {
1535
0
        char *s = NULL; size_t slen = 0;
1536
0
        cj_parse_string_to(ctx, &s, &slen);
1537
0
        if (ctx->err || !s) { free(s); slot->destroy_(); slot->init_null_(); return; }
1538
0
        slot->destroy_(); slot->init_null_();
1539
0
        slot->type_ = CJ_STRING; slot->str_ = s; slot->str_len_ = slen;
1540
0
    } else if (c == 't') {
1541
0
        if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "true", 4) == 0) {
1542
0
            ctx->cur += 4;
1543
0
            slot->destroy_(); slot->init_null_();
1544
0
            slot->type_ = CJ_BOOL; slot->b_ = 1;
1545
0
        } else { cj_ctx_error(ctx, "invalid literal 'true'"); }
1546
0
    } else if (c == 'f') {
1547
0
        if (ctx->end - ctx->cur >= 5 && memcmp(ctx->cur, "false", 5) == 0) {
1548
0
            ctx->cur += 5;
1549
0
            slot->destroy_(); slot->init_null_();
1550
0
            slot->type_ = CJ_BOOL; slot->b_ = 0;
1551
0
        } else { cj_ctx_error(ctx, "invalid literal 'false'"); }
1552
0
    } else if (c == 'n') {
1553
0
        if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "null", 4) == 0) {
1554
0
            ctx->cur += 4;
1555
0
            slot->destroy_(); slot->init_null_();
1556
0
        } else { cj_ctx_error(ctx, "invalid literal 'null'"); }
1557
0
    } else if (c == '-' || (c >= '0' && c <= '9')) {
1558
0
        int is_int = 0; int64_t ival = 0; double dval = 0.0;
1559
0
        const char *next = cj_parse_number(ctx->cur, ctx->end, &is_int, &ival, &dval, ctx->float32_mode);
1560
0
        if (!next) { cj_ctx_error(ctx, "invalid number"); return; }
1561
0
        ctx->cur = next;
1562
0
        slot->destroy_(); slot->init_null_();
1563
0
        if (is_int) { slot->type_ = CJ_INT;  slot->i_ = ival; }
1564
0
        else        { slot->type_ = CJ_REAL; slot->d_ = dval; }
1565
0
    } else {
1566
0
        char errbuf[64];
1567
0
        snprintf(errbuf, sizeof(errbuf), "unexpected character '%c' (0x%02X)",
1568
0
                 (unsigned char)c >= 0x20u ? c : '?', (unsigned char)c);
1569
0
        cj_ctx_error(ctx, errbuf);
1570
0
        slot->destroy_(); slot->init_null_();
1571
0
    }
1572
0
}
1573
1574
/*
1575
 * cj_parse_json -- iterative JSON parser.
1576
 *
1577
 * Parses one complete JSON value from ctx into *root using an explicit
1578
 * cj_frame[CJ_MAX_ITER] stack instead of C recursion.  No C stack frames
1579
 * are consumed for nesting; the only stack growth comes from the fixed-size
1580
 * cj_frame array declared as a local variable here.
1581
 *
1582
 * Loop structure:
1583
 *   after_val == 0  ->  parse the next JSON value into *slot
1584
 *   after_val == 1  ->  a value was just completed; handle ',' / ']' / '}'
1585
 *
1586
 * CJ_MAX_ITER caps the container nesting depth.  Each '{' or '[' increments
1587
 * depth; reaching the cap produces an error rather than an out-of-bounds
1588
 * write.
1589
 */
1590
0
static void cj_parse_json(cj_parse_ctx *ctx, tinygltf_json *root) {
1591
0
    cj_frame stack[CJ_MAX_ITER];
1592
0
    int depth     = 0; /* frames in use */
1593
0
    int after_val = 0; /* 0 = need value, 1 = value just finished */
1594
1595
    /* Where to write the next parsed value */
1596
0
    tinygltf_json *slot = root;
1597
1598
0
    for (;;) {
1599
0
        if (ctx->err) break;
1600
1601
        /* ---------------------------------------------------------------
1602
         * POST-VALUE: handle separator / closing bracket
1603
         * ------------------------------------------------------------- */
1604
0
        if (after_val) {
1605
0
            after_val = 0;
1606
1607
0
            if (depth == 0) {
1608
                /* Root value complete: ensure only trailing whitespace remains */
1609
0
                ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1610
0
                if (ctx->cur != ctx->end) {
1611
0
                    cj_ctx_error(ctx, "trailing non-whitespace after JSON root value");
1612
0
                }
1613
0
                break;
1614
0
            }
1615
1616
0
            cj_frame *f = &stack[depth - 1];
1617
0
            ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1618
0
            if (ctx->cur >= ctx->end) {
1619
0
                cj_ctx_error(ctx, "unexpected EOF after value"); break;
1620
0
            }
1621
1622
0
            if (!f->is_object) {
1623
                /* ---- Array: expect ',' or ']' ---- */
1624
0
                if (*ctx->cur == ',') {
1625
0
                    ++ctx->cur;
1626
                    /* Allocate next element slot */
1627
0
                    tinygltf_json *cont = f->container;
1628
0
                    if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; }
1629
0
                    new (&cont->arr_data_[cont->arr_size_]) tinygltf_json();
1630
0
                    slot = &cont->arr_data_[cont->arr_size_];
1631
0
                    ++cont->arr_size_;
1632
                    /* Loop back to parse the element value */
1633
0
                } else if (*ctx->cur == ']') {
1634
0
                    ++ctx->cur;
1635
0
                    --depth;
1636
0
                    after_val = 1; /* the array itself is now the completed value */
1637
0
                } else {
1638
0
                    cj_ctx_error(ctx, "expected ',' or ']' in array"); break;
1639
0
                }
1640
0
            } else {
1641
                /* ---- Object: expect ',' or '}' ---- */
1642
0
                if (*ctx->cur == ',') {
1643
0
                    ++ctx->cur;
1644
0
                    ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1645
0
                    if (ctx->cur >= ctx->end) {
1646
0
                        cj_ctx_error(ctx, "unexpected EOF in object"); break;
1647
0
                    }
1648
0
                    if (*ctx->cur != '"') {
1649
0
                        cj_ctx_error(ctx, "expected object key after ','"); break;
1650
0
                    }
1651
                    /* Parse key and allocate member slot */
1652
0
                    char *k = NULL; size_t kl = 0;
1653
0
                    cj_parse_string_to(ctx, &k, &kl);
1654
0
                    if (ctx->err || !k) { free(k); break; }
1655
0
                    ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1656
0
                    if (ctx->cur >= ctx->end || *ctx->cur != ':') {
1657
0
                        free(k); cj_ctx_error(ctx, "expected ':' in object"); break;
1658
0
                    }
1659
0
                    ++ctx->cur;
1660
0
                    tinygltf_json *cont = f->container;
1661
0
                    if (!cont->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; }
1662
0
                    tinygltf_json_member *m = &cont->obj_data_[cont->obj_size_];
1663
0
                    new (m) tinygltf_json_member();
1664
0
                    m->key = k; m->key_len = kl;
1665
0
                    ++cont->obj_size_;
1666
0
                    slot = &m->val;
1667
                    /* Loop back to parse the member value */
1668
0
                } else if (*ctx->cur == '}') {
1669
0
                    ++ctx->cur;
1670
0
                    --depth;
1671
0
                    after_val = 1; /* the object itself is now the completed value */
1672
0
                } else {
1673
0
                    cj_ctx_error(ctx, "expected ',' or '}' in object"); break;
1674
0
                }
1675
0
            }
1676
0
            continue;
1677
0
        }
1678
1679
        /* ---------------------------------------------------------------
1680
         * PARSE VALUE: read *slot from ctx->cur
1681
         * ------------------------------------------------------------- */
1682
0
        ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1683
0
        if (ctx->cur >= ctx->end) {
1684
0
            if (depth == 0) break; /* trailing whitespace on root value is ok */
1685
0
            cj_ctx_error(ctx, "unexpected EOF"); break;
1686
0
        }
1687
1688
0
        char c = *ctx->cur;
1689
1690
0
        if (c == '{') {
1691
            /* ---- Begin object ---- */
1692
0
            if (depth >= CJ_MAX_ITER) {
1693
0
                cj_ctx_error(ctx, "nesting limit exceeded"); break;
1694
0
            }
1695
0
            ++ctx->cur;
1696
0
            slot->destroy_(); slot->init_null_(); slot->type_ = CJ_OBJECT;
1697
1698
0
            stack[depth].container = slot;
1699
0
            stack[depth].is_object = 1;
1700
0
            ++depth;
1701
1702
0
            ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1703
0
            if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in object"); break; }
1704
0
            if (*ctx->cur == '}') { ++ctx->cur; --depth; after_val = 1; continue; }
1705
1706
            /* Parse first key */
1707
0
            if (*ctx->cur != '"') { cj_ctx_error(ctx, "expected key in object"); break; }
1708
0
            {
1709
0
                char *k = NULL; size_t kl = 0;
1710
0
                cj_parse_string_to(ctx, &k, &kl);
1711
0
                if (ctx->err || !k) { free(k); break; }
1712
0
                ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1713
0
                if (ctx->cur >= ctx->end || *ctx->cur != ':') {
1714
0
                    free(k); cj_ctx_error(ctx, "expected ':' in object"); break;
1715
0
                }
1716
0
                ++ctx->cur;
1717
0
                if (!slot->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; }
1718
0
                tinygltf_json_member *m = &slot->obj_data_[slot->obj_size_];
1719
0
                new (m) tinygltf_json_member();
1720
0
                m->key = k; m->key_len = kl;
1721
0
                ++slot->obj_size_;
1722
0
                slot = &m->val; /* next iteration parses the first value */
1723
0
            }
1724
1725
0
        } else if (c == '[') {
1726
            /* ---- Begin array ---- */
1727
0
            if (depth >= CJ_MAX_ITER) {
1728
0
                cj_ctx_error(ctx, "nesting limit exceeded"); break;
1729
0
            }
1730
0
            ++ctx->cur;
1731
0
            slot->destroy_(); slot->init_null_(); slot->type_ = CJ_ARRAY;
1732
1733
0
            stack[depth].container = slot;
1734
0
            stack[depth].is_object = 0;
1735
0
            ++depth;
1736
1737
0
            ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
1738
0
            if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in array"); break; }
1739
0
            if (*ctx->cur == ']') { ++ctx->cur; --depth; after_val = 1; continue; }
1740
1741
            /* Allocate first element slot */
1742
0
            {
1743
0
                tinygltf_json *cont = stack[depth - 1].container;
1744
0
                if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; }
1745
0
                new (&cont->arr_data_[cont->arr_size_]) tinygltf_json();
1746
0
                slot = &cont->arr_data_[cont->arr_size_];
1747
0
                ++cont->arr_size_;
1748
0
            }
1749
            /* next iteration parses the first element */
1750
1751
0
        } else {
1752
            /* ---- Scalar value ---- */
1753
0
            cj_parse_scalar(ctx, slot);
1754
0
            after_val = 1;
1755
0
        }
1756
0
    }
1757
0
}
1758
1759
/* ======================================================================
1760
 * SERIALIZATION (C-style string builder)
1761
 * ====================================================================== */
1762
1763
struct cj_strbuf {
1764
    char  *data;
1765
    size_t len;
1766
    size_t cap;
1767
};
1768
1769
0
static int cj_strbuf_init(cj_strbuf *sb, size_t initial) {
1770
0
    sb->data = (char *)malloc(initial);
1771
0
    sb->len  = 0;
1772
0
    sb->cap  = initial;
1773
0
    return sb->data ? 1 : 0;
1774
0
}
1775
1776
0
static void cj_strbuf_free_data(cj_strbuf *sb) {
1777
0
    free(sb->data);
1778
0
    sb->data = NULL;
1779
0
    sb->len = sb->cap = 0;
1780
0
}
1781
1782
0
static int cj_strbuf_grow(cj_strbuf *sb, size_t extra) {
1783
    /* Guard against size_t overflow in needed = sb->len + extra */
1784
0
    if (extra > (size_t)-1 - sb->len) return 0;
1785
0
    size_t needed = sb->len + extra;
1786
0
    if (needed <= sb->cap) return 1;
1787
0
    size_t new_cap = sb->cap * 2;
1788
0
    if (new_cap < needed) {
1789
        /* Guard against overflow in needed + 256 */
1790
0
        if (needed > SIZE_MAX - 256) return 0;
1791
0
        new_cap = needed + 256;
1792
0
    }
1793
0
    char *nd = (char *)realloc(sb->data, new_cap);
1794
0
    if (!nd) return 0;
1795
0
    sb->data = nd;
1796
0
    sb->cap  = new_cap;
1797
0
    return 1;
1798
0
}
1799
1800
0
static int cj_sb_appendn(cj_strbuf *sb, const char *s, size_t n) {
1801
0
    if (!cj_strbuf_grow(sb, n)) return 0;
1802
0
    memcpy(sb->data + sb->len, s, n);
1803
0
    sb->len += n;
1804
0
    return 1;
1805
0
}
1806
1807
0
static int cj_sb_appendc(cj_strbuf *sb, char c) {
1808
0
    return cj_sb_appendn(sb, &c, 1);
1809
0
}
1810
1811
0
static int cj_sb_appends(cj_strbuf *sb, const char *s) {
1812
0
    return cj_sb_appendn(sb, s, strlen(s));
1813
0
}
1814
1815
0
static int cj_append_str_escaped(cj_strbuf *sb, const char *s, size_t len) {
1816
0
    if (!cj_sb_appendc(sb, '"')) return 0;
1817
0
    for (size_t i = 0; i < len; ++i) {
1818
0
        unsigned char c = (unsigned char)s[i];
1819
0
        switch (c) {
1820
0
            case '"':  if (!cj_sb_appendn(sb, "\\\"", 2)) return 0; break;
1821
0
            case '\\': if (!cj_sb_appendn(sb, "\\\\", 2)) return 0; break;
1822
0
            case '\b': if (!cj_sb_appendn(sb, "\\b",  2)) return 0; break;
1823
0
            case '\f': if (!cj_sb_appendn(sb, "\\f",  2)) return 0; break;
1824
0
            case '\n': if (!cj_sb_appendn(sb, "\\n",  2)) return 0; break;
1825
0
            case '\r': if (!cj_sb_appendn(sb, "\\r",  2)) return 0; break;
1826
0
            case '\t': if (!cj_sb_appendn(sb, "\\t",  2)) return 0; break;
1827
0
            default:
1828
0
                if (c < 0x20u) {
1829
0
                    char buf[8];
1830
0
                    snprintf(buf, sizeof(buf), "\\u%04x", (unsigned int)c);
1831
0
                    if (!cj_sb_appends(sb, buf)) return 0;
1832
0
                } else {
1833
0
                    if (!cj_sb_appendc(sb, (char)c)) return 0;
1834
0
                }
1835
0
                break;
1836
0
        }
1837
0
    }
1838
0
    return cj_sb_appendc(sb, '"');
1839
0
}
1840
1841
0
static int cj_indent_line(cj_strbuf *sb, int indent, int depth) {
1842
0
    if (indent <= 0) return 1;
1843
0
    if (!cj_sb_appendc(sb, '\n')) return 0;
1844
0
    for (int i = 0; i < indent * depth; ++i)
1845
0
        if (!cj_sb_appendc(sb, ' ')) return 0;
1846
0
    return 1;
1847
0
}
1848
1849
static int cj_serialize(cj_strbuf *sb, const tinygltf_json *v,
1850
0
                        int indent, int depth) {
1851
    /* Prevent C stack overflow on deeply nested JSON.
1852
     * Parser caps nesting at CJ_MAX_ITER; serializer uses the same limit. */
1853
0
    if (depth >= CJ_MAX_ITER) {
1854
0
        return cj_sb_appends(sb, "null");
1855
0
    }
1856
0
    switch (v->type_) {
1857
0
        case CJ_NULL:
1858
0
            return cj_sb_appends(sb, "null");
1859
0
        case CJ_BOOL:
1860
0
            return cj_sb_appends(sb, v->b_ ? "true" : "false");
1861
0
        case CJ_INT: {
1862
0
            char buf[32];
1863
0
            snprintf(buf, sizeof(buf), "%" PRId64, v->i_);
1864
0
            return cj_sb_appends(sb, buf);
1865
0
        }
1866
0
        case CJ_REAL: {
1867
0
            char buf[64];
1868
0
            double d = v->d_;
1869
            /* Non-finite values (NaN, Inf) cannot be represented in JSON.
1870
             * Detect by formatting first: nan/NaN starts with 'n'/'N'/'-n'/'-N',
1871
             * inf/Inf starts with 'i'/'I'/'-i'/'-I'. Output null for these. */
1872
0
            snprintf(buf, sizeof(buf), "%.17g", d);
1873
0
            {
1874
0
                const char *b = buf;
1875
0
                if (*b == '-') ++b;
1876
0
                if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I')
1877
0
                    return cj_sb_appends(sb, "null");
1878
0
            }
1879
            /* Ensure there's a decimal point so the value round-trips as float */
1880
0
            if (!strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'E')) {
1881
0
                size_t bl = strlen(buf);
1882
0
                if (bl + 2 < sizeof(buf)) {
1883
0
                    buf[bl]   = '.';
1884
0
                    buf[bl+1] = '0';
1885
0
                    buf[bl+2] = '\0';
1886
0
                }
1887
0
            }
1888
0
            return cj_sb_appends(sb, buf);
1889
0
        }
1890
0
        case CJ_STRING: {
1891
            /* Defensive: if str_ is NULL (OOM during construction), use length 0.
1892
             * The invariant str_==NULL→str_len_==0 is enforced at all construction
1893
             * sites, but guard here in case of future callers. */
1894
0
            const char *s = v->str_ ? v->str_ : "";
1895
0
            size_t      n = v->str_ ? v->str_len_ : 0u;
1896
0
            return cj_append_str_escaped(sb, s, n);
1897
0
        }
1898
0
        case CJ_ARRAY: {
1899
0
            if (!cj_sb_appendc(sb, '[')) return 0;
1900
0
            for (size_t i = 0; i < v->arr_size_; ++i) {
1901
0
                if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0;
1902
0
                if (!cj_serialize(sb, &v->arr_data_[i], indent, depth+1)) return 0;
1903
0
                if (i + 1 < v->arr_size_ && !cj_sb_appendc(sb, ',')) return 0;
1904
0
            }
1905
0
            if (indent > 0 && v->arr_size_ > 0)
1906
0
                if (!cj_indent_line(sb, indent, depth)) return 0;
1907
0
            return cj_sb_appendc(sb, ']');
1908
0
        }
1909
0
        case CJ_OBJECT: {
1910
0
            if (!cj_sb_appendc(sb, '{')) return 0;
1911
0
            for (size_t i = 0; i < v->obj_size_; ++i) {
1912
0
                if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0;
1913
0
                const tinygltf_json_member *m = &v->obj_data_[i];
1914
                /* Defensive: if key is NULL (OOM during insert), use length 0 */
1915
0
                const char *key    = m->key ? m->key : "";
1916
0
                size_t      keylen = m->key ? m->key_len : 0u;
1917
0
                if (!cj_append_str_escaped(sb, key, keylen)) return 0;
1918
0
                if (!cj_sb_appendc(sb, ':')) return 0;
1919
0
                if (indent > 0 && !cj_sb_appendc(sb, ' ')) return 0;
1920
0
                if (!cj_serialize(sb, &m->val, indent, depth + 1)) return 0;
1921
0
                if (i + 1 < v->obj_size_ && !cj_sb_appendc(sb, ',')) return 0;
1922
0
            }
1923
0
            if (indent > 0 && v->obj_size_ > 0)
1924
0
                if (!cj_indent_line(sb, indent, depth)) return 0;
1925
0
            return cj_sb_appendc(sb, '}');
1926
0
        }
1927
0
        default:
1928
0
            return cj_sb_appends(sb, "null");
1929
0
    }
1930
0
}
1931
1932
/* ======================================================================
1933
 * tinygltf_json::dump() and ::parse() IMPLEMENTATIONS
1934
 * ====================================================================== */
1935
1936
0
inline std::string tinygltf_json::dump(int indent) const {
1937
0
    cj_strbuf sb;
1938
0
    if (!cj_strbuf_init(&sb, 4096)) return std::string();
1939
0
    cj_serialize(&sb, this, indent, 0);
1940
0
    std::string result(sb.data, sb.len);
1941
0
    cj_strbuf_free_data(&sb);
1942
0
    return result;
1943
0
}
1944
1945
inline tinygltf_json tinygltf_json::parse(const char *first, const char *last,
1946
                                           std::nullptr_t,
1947
0
                                           bool allow_exceptions) {
1948
0
    cj_parse_ctx ctx;
1949
0
    ctx.cur          = first;
1950
0
    ctx.end          = last;
1951
0
    ctx.err          = 0;
1952
0
    ctx.float32_mode = 0;
1953
0
    ctx.errmsg[0]    = '\0';
1954
1955
0
    tinygltf_json result;
1956
0
    cj_parse_json(&ctx, &result);
1957
1958
0
    if (ctx.err) {
1959
#ifndef TINYGLTF_JSON_NO_EXCEPTIONS
1960
        if (allow_exceptions) {
1961
            throw std::invalid_argument(
1962
                std::string("tinygltf_json::parse error: ") + ctx.errmsg);
1963
        }
1964
#else
1965
0
        (void)allow_exceptions;
1966
0
#endif
1967
0
        return tinygltf_json(); /* null on error */
1968
0
    }
1969
0
    return result;
1970
0
}
1971
1972
0
inline tinygltf_json tinygltf_json::parse_float32(const char *first, const char *last) {
1973
0
    cj_parse_ctx ctx;
1974
0
    ctx.cur          = first;
1975
0
    ctx.end          = last;
1976
0
    ctx.err          = 0;
1977
0
    ctx.float32_mode = 1;
1978
0
    ctx.errmsg[0]    = '\0';
1979
0
1980
0
    tinygltf_json result;
1981
0
    cj_parse_json(&ctx, &result);
1982
0
1983
0
    if (ctx.err) return tinygltf_json();
1984
0
    return result;
1985
0
}
1986
1987
/* ======================================================================
1988
 * TINYGLTF DETAIL NAMESPACE COMPATIBILITY
1989
 *
1990
 * These declarations make the custom JSON backend available as
1991
 * tinygltf::detail types/functions when TINYGLTF_USE_CUSTOM_JSON is set.
1992
 * ====================================================================== */
1993
1994
namespace tinygltf {
1995
namespace detail {
1996
1997
using json                      = tinygltf_json;
1998
using json_iterator             = tinygltf_json::iterator;
1999
using json_const_iterator       = tinygltf_json::iterator;
2000
using json_const_array_iterator = tinygltf_json::iterator;
2001
using JsonDocument              = tinygltf_json;
2002
2003
inline void JsonParse(JsonDocument &doc, const char *str, size_t length,
2004
0
                      bool throwExc = false) {
2005
0
    doc = tinygltf_json::parse(str, str + length, nullptr, throwExc);
2006
0
}
2007
2008
/* --- Type accessors --- */
2009
2010
0
inline bool GetInt(const json &o, int &val) {
2011
0
    if (o.is_number_integer() || o.is_number_unsigned()) {
2012
0
        val = o.get<int>();
2013
0
        return true;
2014
0
    }
2015
0
    return false;
2016
0
}
2017
2018
0
inline bool GetDouble(const json &o, double &val) {
2019
0
    if (o.is_number_float()) {
2020
0
        val = o.get<double>();
2021
0
        return true;
2022
0
    }
2023
0
    return false;
2024
0
}
2025
2026
0
inline bool GetNumber(const json &o, double &val) {
2027
0
    if (o.is_number()) {
2028
0
        val = o.get<double>();
2029
0
        return true;
2030
0
    }
2031
0
    return false;
2032
0
}
2033
2034
0
inline bool GetString(const json &o, std::string &val) {
2035
0
    if (o.is_string()) {
2036
0
        val = o.get<std::string>();
2037
0
        return true;
2038
0
    }
2039
0
    return false;
2040
0
}
2041
2042
0
inline bool IsArray(const json &o)  { return o.is_array(); }
2043
0
inline bool IsObject(const json &o) { return o.is_object(); }
2044
0
inline bool IsEmpty(const json &o)  { return o.empty(); }
2045
2046
0
inline json_const_array_iterator ArrayBegin(const json &o) {
2047
0
    return o.begin();
2048
0
}
2049
0
inline json_const_array_iterator ArrayEnd(const json &o) {
2050
0
    return o.end();
2051
0
}
2052
2053
0
inline json_const_iterator ObjectBegin(const json &o) { return o.begin(); }
2054
0
inline json_const_iterator ObjectEnd(const json &o)   { return o.end(); }
2055
0
inline json_iterator       ObjectBegin(json &o)        { return o.begin(); }
2056
0
inline json_iterator       ObjectEnd(json &o)          { return o.end(); }
2057
2058
0
inline std::string GetKey(const json_const_iterator &it) { return it.key(); }
2059
0
inline std::string GetKey(json_iterator &it)              { return it.key(); }
2060
2061
0
inline const json &GetValue(const json_const_iterator &it) { return *it; }
2062
0
inline json       &GetValue(json_iterator &it)              { return *it; }
2063
2064
inline bool FindMember(const json &o, const char *member,
2065
0
                        json_const_iterator &it) {
2066
0
    it = o.find(member);
2067
0
    return it != o.end();
2068
0
}
2069
0
inline bool FindMember(json &o, const char *member, json_iterator &it) {
2070
0
    it = o.find(member);
2071
0
    return it != o.end();
2072
0
}
2073
2074
0
inline void Erase(json &o, json_iterator &it) { o.erase(it); }
2075
2076
0
inline std::string JsonToString(const json &o, int spacing = -1) {
2077
0
    return o.dump(spacing);
2078
0
}
2079
2080
/* --- Serialization helpers --- */
2081
2082
0
inline json JsonFromString(const char *s) { return json(s); }
2083
2084
0
inline void JsonAssign(json &dest, const json &src) { dest = src; }
2085
2086
0
inline void JsonAddMember(json &o, const char *key, json &&value) {
2087
0
    o[key] = static_cast<json &&>(value);
2088
0
}
2089
2090
0
inline void JsonPushBack(json &o, json &&value) {
2091
0
    o.push_back(static_cast<json &&>(value));
2092
0
}
2093
2094
0
inline bool JsonIsNull(const json &o) { return o.is_null(); }
2095
2096
0
inline void JsonSetObject(json &o) { o = json::object(); }
2097
2098
0
inline void JsonReserveArray(json &o, size_t /*s*/) {
2099
0
    o.set_array();
2100
0
}
2101
2102
/* Stub allocator for RapidJSON-compatibility (not used by custom backend) */
2103
struct CJ_NoAllocator {};
2104
0
inline CJ_NoAllocator &GetAllocator() {
2105
0
    static CJ_NoAllocator alloc;
2106
0
    return alloc;
2107
0
}
2108
2109
} /* namespace detail */
2110
} /* namespace tinygltf */
2111
2112
#endif /* TINYGLTF_JSON_H_ */