mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-09 11:43:49 +00:00
Compare commits
19 Commits
copilot/su
...
copilot/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
826b71cc24 | ||
|
|
dfd94f03fb | ||
|
|
131c4489fa | ||
|
|
594c3a057b | ||
|
|
ad316367b9 | ||
|
|
1f15c2d140 | ||
|
|
1d5e721a24 | ||
|
|
c9a9b1175a | ||
|
|
5e0c5b9ada | ||
|
|
03b9db782e | ||
|
|
c99e713fab | ||
|
|
8c8cbfa0ba | ||
|
|
0949983acc | ||
|
|
c870bd5fd6 | ||
|
|
b76cf7aa21 | ||
|
|
f7bd377a69 | ||
|
|
5d6984b9fd | ||
|
|
3331c6cee2 | ||
|
|
97316e140c |
@@ -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 */
|
||||
|
||||
@@ -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; \
|
||||
}
|
||||
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user