mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-08 03:03:50 +00:00
Cross-version verifier comparing v3 C parser against v1 ground truth
Adds a structured DIGEST block (asset, buffers w/ FNV-1a hash, bufferViews,
accessors w/ min/max, mesh primitives w/ sorted attribute maps, nodes w/
normalized TRS+matrix, materials, textures/samplers/images, skins,
animations, cameras, scenes) emitted by both loader_example (v1) and
tester_v3_c (v3 C, now accepting a file arg). test_runner.py runs both,
diffs the digests, and reports counts/digest mismatches with v1 as truth.
Also rolls in /simplify follow-ups on top of 7f736d1: a shared
tg3__json_number_to_double helper to dedupe inline number coercions, a
collapsed fuzz_gltf_v3_c harness using a single tg3_fuzz_run dispatcher,
a rewritten max_safe_uint64_real comment explaining the 53-bit mantissa
constraint, and a tests/Makefile fix so tester_v3_c is a real prerequisite
of `all` (built once via the dedicated rule, not duplicated).
Verifier passes 134/134 on the Khronos glTF-Sample-Models/2.0 suite.
bufferView.target and image.mime_type/uri are intentionally excluded from
the digest: v1 infers target from accessor usage and rewrites image
URIs/mime via stb_image, neither of which is a parse-fidelity concern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,10 @@
|
|||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
#include "tiny_gltf.h"
|
#include "tiny_gltf.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -852,6 +855,206 @@ static void Dump(const tinygltf::Model &model) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Digest helpers (used to compare v1 vs v3 parses) ===================== */
|
||||||
|
|
||||||
|
static uint64_t fnv64(const unsigned char *data, size_t n) {
|
||||||
|
uint64_t h = 0xcbf29ce484222325ULL;
|
||||||
|
for (size_t i = 0; i < n; ++i) { h ^= data[i]; h *= 0x100000001b3ULL; }
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_str(const std::string &s) {
|
||||||
|
putchar('"');
|
||||||
|
for (unsigned char c : s) {
|
||||||
|
if (c == '"' || c == '\\') { putchar('\\'); putchar((char)c); }
|
||||||
|
else if (c < 0x20 || c >= 0x7f) putchar('?');
|
||||||
|
else putchar((char)c);
|
||||||
|
}
|
||||||
|
putchar('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_dbl(double v) { printf("%.7g", v); }
|
||||||
|
|
||||||
|
static void d_dbl_arr(const double *v, size_t n) {
|
||||||
|
putchar('[');
|
||||||
|
for (size_t i = 0; i < n; ++i) { if (i) putchar(','); d_dbl(v[i]); }
|
||||||
|
putchar(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_dbl_vec(const std::vector<double> &v) {
|
||||||
|
d_dbl_arr(v.data(), v.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintDigest(const tinygltf::Model &m) {
|
||||||
|
printf("DIGEST_BEGIN\n");
|
||||||
|
|
||||||
|
printf("asset version=");
|
||||||
|
d_str(m.asset.version);
|
||||||
|
printf(" generator=");
|
||||||
|
d_str(m.asset.generator);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m.buffers.size(); ++i) {
|
||||||
|
const auto &b = m.buffers[i];
|
||||||
|
uint64_t h = b.data.empty() ? 0 : fnv64(b.data.data(), b.data.size());
|
||||||
|
printf("buffer %zu byte_length=%llu fnv64=0x%016llx\n",
|
||||||
|
i, (unsigned long long)b.data.size(), (unsigned long long)h);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.bufferViews.size(); ++i) {
|
||||||
|
const auto &bv = m.bufferViews[i];
|
||||||
|
printf("buffer_view %zu buffer=%d byte_offset=%llu byte_length=%llu byte_stride=%u\n",
|
||||||
|
i, bv.buffer, (unsigned long long)bv.byteOffset,
|
||||||
|
(unsigned long long)bv.byteLength, (unsigned)bv.byteStride);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.accessors.size(); ++i) {
|
||||||
|
const auto &a = m.accessors[i];
|
||||||
|
printf("accessor %zu buffer_view=%d byte_offset=%llu component_type=%d count=%llu type=%d normalized=%d min=",
|
||||||
|
i, a.bufferView, (unsigned long long)a.byteOffset, a.componentType,
|
||||||
|
(unsigned long long)a.count, a.type, a.normalized ? 1 : 0);
|
||||||
|
d_dbl_vec(a.minValues);
|
||||||
|
printf(" max=");
|
||||||
|
d_dbl_vec(a.maxValues);
|
||||||
|
printf(" sparse=%d\n", a.sparse.isSparse ? 1 : 0);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.meshes.size(); ++i) {
|
||||||
|
const auto &me = m.meshes[i];
|
||||||
|
printf("mesh %zu primitives_count=%zu weights_count=%zu\n",
|
||||||
|
i, me.primitives.size(), me.weights.size());
|
||||||
|
for (size_t j = 0; j < me.primitives.size(); ++j) {
|
||||||
|
const auto &p = me.primitives[j];
|
||||||
|
printf("prim %zu %zu indices=%d material=%d mode=%d attrs=[",
|
||||||
|
i, j, p.indices, p.material, p.mode);
|
||||||
|
// attributes is std::map → already sorted by key
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &kv : p.attributes) {
|
||||||
|
if (!first) putchar(',');
|
||||||
|
printf("%s:%d", kv.first.c_str(), kv.second);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
printf("] targets_count=%zu\n", p.targets.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.nodes.size(); ++i) {
|
||||||
|
const auto &n = m.nodes[i];
|
||||||
|
double t[3] = {0, 0, 0};
|
||||||
|
double r[4] = {0, 0, 0, 1};
|
||||||
|
double s[3] = {1, 1, 1};
|
||||||
|
double mat[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
|
||||||
|
int has_matrix = (n.matrix.size() == 16) ? 1 : 0;
|
||||||
|
if (n.translation.size() == 3) std::copy(n.translation.begin(), n.translation.end(), t);
|
||||||
|
if (n.rotation.size() == 4) std::copy(n.rotation.begin(), n.rotation.end(), r);
|
||||||
|
if (n.scale.size() == 3) std::copy(n.scale.begin(), n.scale.end(), s);
|
||||||
|
if (has_matrix) std::copy(n.matrix.begin(), n.matrix.end(), mat);
|
||||||
|
printf("node %zu mesh=%d skin=%d camera=%d light=%d children_count=%zu has_matrix=%d t=",
|
||||||
|
i, n.mesh, n.skin, n.camera, n.light, n.children.size(), has_matrix);
|
||||||
|
d_dbl_arr(t, 3);
|
||||||
|
printf(" r=");
|
||||||
|
d_dbl_arr(r, 4);
|
||||||
|
printf(" s=");
|
||||||
|
d_dbl_arr(s, 3);
|
||||||
|
printf(" matrix=");
|
||||||
|
d_dbl_arr(mat, 16);
|
||||||
|
printf(" weights_count=%zu\n", n.weights.size());
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.materials.size(); ++i) {
|
||||||
|
const auto &mat = m.materials[i];
|
||||||
|
double ef[3] = {0, 0, 0};
|
||||||
|
double bcf[4] = {1, 1, 1, 1};
|
||||||
|
if (mat.emissiveFactor.size() == 3)
|
||||||
|
std::copy(mat.emissiveFactor.begin(), mat.emissiveFactor.end(), ef);
|
||||||
|
if (mat.pbrMetallicRoughness.baseColorFactor.size() == 4)
|
||||||
|
std::copy(mat.pbrMetallicRoughness.baseColorFactor.begin(),
|
||||||
|
mat.pbrMetallicRoughness.baseColorFactor.end(), bcf);
|
||||||
|
printf("material %zu alpha_mode=", i);
|
||||||
|
d_str(mat.alphaMode);
|
||||||
|
printf(" alpha_cutoff=");
|
||||||
|
d_dbl(mat.alphaCutoff);
|
||||||
|
printf(" double_sided=%d emissive=", mat.doubleSided ? 1 : 0);
|
||||||
|
d_dbl_arr(ef, 3);
|
||||||
|
printf(" base_color_factor=");
|
||||||
|
d_dbl_arr(bcf, 4);
|
||||||
|
printf(" metallic=");
|
||||||
|
d_dbl(mat.pbrMetallicRoughness.metallicFactor);
|
||||||
|
printf(" roughness=");
|
||||||
|
d_dbl(mat.pbrMetallicRoughness.roughnessFactor);
|
||||||
|
printf(" base_color_tex=%d normal_tex=%d occlusion_tex=%d emissive_tex=%d\n",
|
||||||
|
mat.pbrMetallicRoughness.baseColorTexture.index,
|
||||||
|
mat.normalTexture.index,
|
||||||
|
mat.occlusionTexture.index,
|
||||||
|
mat.emissiveTexture.index);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.textures.size(); ++i) {
|
||||||
|
const auto &t = m.textures[i];
|
||||||
|
printf("texture %zu source=%d sampler=%d\n", i, t.source, t.sampler);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.samplers.size(); ++i) {
|
||||||
|
const auto &s = m.samplers[i];
|
||||||
|
printf("sampler %zu min_filter=%d mag_filter=%d wrap_s=%d wrap_t=%d\n",
|
||||||
|
i, s.minFilter, s.magFilter, s.wrapS, s.wrapT);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.images.size(); ++i) {
|
||||||
|
const auto &im = m.images[i];
|
||||||
|
/* mime_type and uri normalization differ between v1/v3 (data URIs,
|
||||||
|
extension inference); buffer_view reference is the parse-fidelity bit. */
|
||||||
|
printf("image %zu buffer_view=%d\n", i, im.bufferView);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.skins.size(); ++i) {
|
||||||
|
const auto &s = m.skins[i];
|
||||||
|
printf("skin %zu inverse_bind_matrices=%d skeleton=%d joints_count=%zu\n",
|
||||||
|
i, s.inverseBindMatrices, s.skeleton, s.joints.size());
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.animations.size(); ++i) {
|
||||||
|
const auto &a = m.animations[i];
|
||||||
|
printf("animation %zu channels_count=%zu samplers_count=%zu\n",
|
||||||
|
i, a.channels.size(), a.samplers.size());
|
||||||
|
for (size_t j = 0; j < a.channels.size(); ++j) {
|
||||||
|
const auto &c = a.channels[j];
|
||||||
|
printf("chan %zu %zu sampler=%d target_node=%d target_path=", i, j,
|
||||||
|
c.sampler, c.target_node);
|
||||||
|
d_str(c.target_path);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
for (size_t j = 0; j < a.samplers.size(); ++j) {
|
||||||
|
const auto &as = a.samplers[j];
|
||||||
|
printf("samp %zu %zu input=%d output=%d interpolation=", i, j,
|
||||||
|
as.input, as.output);
|
||||||
|
d_str(as.interpolation);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.cameras.size(); ++i) {
|
||||||
|
const auto &c = m.cameras[i];
|
||||||
|
bool is_persp = (c.type == "perspective");
|
||||||
|
printf("camera %zu type=", i);
|
||||||
|
d_str(c.type);
|
||||||
|
if (is_persp) {
|
||||||
|
printf(" yfov=");
|
||||||
|
d_dbl(c.perspective.yfov);
|
||||||
|
printf(" znear=");
|
||||||
|
d_dbl(c.perspective.znear);
|
||||||
|
printf(" zfar=");
|
||||||
|
d_dbl(c.perspective.zfar);
|
||||||
|
printf(" aspect=");
|
||||||
|
d_dbl(c.perspective.aspectRatio);
|
||||||
|
} else {
|
||||||
|
printf(" xmag=");
|
||||||
|
d_dbl(c.orthographic.xmag);
|
||||||
|
printf(" ymag=");
|
||||||
|
d_dbl(c.orthographic.ymag);
|
||||||
|
printf(" znear=");
|
||||||
|
d_dbl(c.orthographic.znear);
|
||||||
|
printf(" zfar=");
|
||||||
|
d_dbl(c.orthographic.zfar);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < m.scenes.size(); ++i) {
|
||||||
|
const auto &s = m.scenes[i];
|
||||||
|
printf("scene %zu nodes_count=%zu\n", i, s.nodes.size());
|
||||||
|
}
|
||||||
|
printf("DIGEST_END\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
printf("Needs input.gltf\n");
|
printf("Needs input.gltf\n");
|
||||||
@@ -900,6 +1103,20 @@ int main(int argc, char **argv) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("COUNTS"
|
||||||
|
" accessors=%zu animations=%zu buffers=%zu bufferViews=%zu"
|
||||||
|
" cameras=%zu images=%zu materials=%zu meshes=%zu nodes=%zu"
|
||||||
|
" samplers=%zu scenes=%zu skins=%zu textures=%zu lights=%zu\n",
|
||||||
|
model.accessors.size(), model.animations.size(),
|
||||||
|
model.buffers.size(), model.bufferViews.size(),
|
||||||
|
model.cameras.size(), model.images.size(),
|
||||||
|
model.materials.size(), model.meshes.size(),
|
||||||
|
model.nodes.size(), model.samplers.size(),
|
||||||
|
model.scenes.size(), model.skins.size(),
|
||||||
|
model.textures.size(), model.lights.size());
|
||||||
|
|
||||||
|
PrintDigest(model);
|
||||||
|
|
||||||
Dump(model);
|
Dump(model);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
166
test_runner.py
166
test_runner.py
@@ -2,63 +2,167 @@
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
## Simple test runner.
|
## Cross-version verifier: parses each sample model with the mature v1
|
||||||
|
## (loader_example) and the new v3 C tester, then compares both the COUNTS
|
||||||
|
## summary line and the structured DIGEST block both binaries emit.
|
||||||
|
## v1 is the ground truth.
|
||||||
|
|
||||||
# -- config -----------------------
|
# -- config -----------------------
|
||||||
|
|
||||||
# Absolute path pointing to your cloned git repo of https://github.com/KhronosGroup/glTF-Sample-Models
|
sample_model_dir = "/mnt/nfs/syoyo/glTF-Sample-Models"
|
||||||
sample_model_dir = "/home/syoyo/work/glTF-Sample-Models"
|
|
||||||
base_model_dir = os.path.join(sample_model_dir, "2.0")
|
base_model_dir = os.path.join(sample_model_dir, "2.0")
|
||||||
|
|
||||||
# Include `glTF-Draco` when you build `loader_example` with draco support.
|
v1_bin = "./loader_example"
|
||||||
kinds = [ "glTF", "glTF-Binary", "glTF-Embedded", "glTF-MaterialsCommon"]
|
v3_bin = "./tests/tester_v3_c"
|
||||||
|
|
||||||
|
kinds = ["glTF", "glTF-Binary", "glTF-Embedded", "glTF-MaterialsCommon"]
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
|
|
||||||
failed = []
|
COUNTS_RE = re.compile(r"^COUNTS\s+(.*)$", re.MULTILINE)
|
||||||
success = []
|
DIGEST_RE = re.compile(r"^DIGEST_BEGIN\n(.*?)^DIGEST_END$", re.MULTILINE | re.DOTALL)
|
||||||
|
|
||||||
def run(filename):
|
|
||||||
|
|
||||||
|
def parse_counts(output):
|
||||||
|
m = COUNTS_RE.search(output)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
counts = {}
|
||||||
|
for tok in m.group(1).split():
|
||||||
|
if "=" not in tok:
|
||||||
|
continue
|
||||||
|
k, v = tok.split("=", 1)
|
||||||
|
counts[k] = int(v)
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
def parse_digest(output):
|
||||||
|
m = DIGEST_RE.search(output)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
return [line for line in m.group(1).splitlines() if line]
|
||||||
|
|
||||||
|
|
||||||
|
def run_binary(binary, filename):
|
||||||
|
p = subprocess.Popen(
|
||||||
|
[binary, filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
out, err = p.communicate()
|
||||||
|
return p.returncode, out.decode("utf-8", "replace"), err.decode("utf-8", "replace")
|
||||||
|
|
||||||
|
|
||||||
|
def diff_digests(v1, v3, max_lines=20):
|
||||||
|
"""Return a short summary of differences between two digest line lists."""
|
||||||
|
diffs = []
|
||||||
|
n = max(len(v1), len(v3))
|
||||||
|
for i in range(n):
|
||||||
|
a = v1[i] if i < len(v1) else "<missing>"
|
||||||
|
b = v3[i] if i < len(v3) else "<missing>"
|
||||||
|
if a != b:
|
||||||
|
diffs.append(" v1[{0}]: {1}".format(i, a))
|
||||||
|
diffs.append(" v3[{0}]: {1}".format(i, b))
|
||||||
|
if len(diffs) >= max_lines * 2:
|
||||||
|
diffs.append(" ... (truncated)")
|
||||||
|
break
|
||||||
|
return diffs
|
||||||
|
|
||||||
|
|
||||||
|
parse_failed = [] # v3 returned non-zero or no COUNTS/DIGEST
|
||||||
|
v1_skipped = [] # v1 returned non-zero or no COUNTS/DIGEST
|
||||||
|
counts_diff = [] # counts disagree
|
||||||
|
digest_diff = [] # digest disagrees
|
||||||
|
ok = []
|
||||||
|
|
||||||
|
|
||||||
|
def verify(filename):
|
||||||
print("Testing: " + filename)
|
print("Testing: " + filename)
|
||||||
cmd = ["./loader_example", filename]
|
|
||||||
try:
|
|
||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
(stdout, stderr) = p.communicate()
|
|
||||||
except:
|
|
||||||
print("Failed to execute: ", cmd)
|
|
||||||
raise
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
rc1, out1, err1 = run_binary(v1_bin, filename)
|
||||||
failed.append(filename)
|
c1 = parse_counts(out1) if rc1 == 0 else None
|
||||||
print(stdout)
|
d1 = parse_digest(out1) if rc1 == 0 else None
|
||||||
print(stderr)
|
if c1 is None or d1 is None:
|
||||||
else:
|
v1_skipped.append(filename)
|
||||||
success.append(filename)
|
print(" v1 ground truth unavailable (rc={0}); skipping".format(rc1))
|
||||||
|
return
|
||||||
|
|
||||||
|
rc3, out3, err3 = run_binary(v3_bin, filename)
|
||||||
|
c3 = parse_counts(out3) if rc3 == 0 else None
|
||||||
|
d3 = parse_digest(out3) if rc3 == 0 else None
|
||||||
|
if c3 is None or d3 is None:
|
||||||
|
parse_failed.append((filename, rc3, err3.strip()))
|
||||||
|
print(" v3 FAILED (rc={0}): {1}".format(rc3, err3.strip()[:200]))
|
||||||
|
return
|
||||||
|
|
||||||
|
cdiffs = []
|
||||||
|
for k in sorted(set(c1) | set(c3)):
|
||||||
|
if c1.get(k) != c3.get(k):
|
||||||
|
cdiffs.append((k, c1.get(k), c3.get(k)))
|
||||||
|
if cdiffs:
|
||||||
|
counts_diff.append((filename, cdiffs))
|
||||||
|
print(" COUNTS MISMATCH:")
|
||||||
|
for k, a, b in cdiffs:
|
||||||
|
print(" {0}: v1={1} v3={2}".format(k, a, b))
|
||||||
|
return
|
||||||
|
|
||||||
|
if d1 != d3:
|
||||||
|
diffs = diff_digests(d1, d3)
|
||||||
|
digest_diff.append((filename, diffs))
|
||||||
|
print(" DIGEST MISMATCH ({0} v1 lines, {1} v3 lines):".format(len(d1), len(d3)))
|
||||||
|
for line in diffs[:8]:
|
||||||
|
print(line)
|
||||||
|
return
|
||||||
|
|
||||||
|
ok.append(filename)
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
|
for d in sorted(os.listdir(base_model_dir)):
|
||||||
for d in os.listdir(base_model_dir):
|
|
||||||
p = os.path.join(base_model_dir, d)
|
p = os.path.join(base_model_dir, d)
|
||||||
if os.path.isdir(p):
|
if not os.path.isdir(p):
|
||||||
|
continue
|
||||||
for k in kinds:
|
for k in kinds:
|
||||||
targetDir = os.path.join(p, k)
|
targetDir = os.path.join(p, k)
|
||||||
g = glob.glob(targetDir + "/*.gltf") + glob.glob(targetDir + "/*.glb")
|
g = sorted(
|
||||||
|
glob.glob(targetDir + "/*.gltf")
|
||||||
|
+ glob.glob(targetDir + "/*.glb")
|
||||||
|
)
|
||||||
for gltf in g:
|
for gltf in g:
|
||||||
run(gltf)
|
verify(gltf)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if not os.path.exists(v1_bin):
|
||||||
|
sys.exit("error: v1 binary not found at {0}".format(v1_bin))
|
||||||
|
if not os.path.exists(v3_bin):
|
||||||
|
sys.exit("error: v3 binary not found at {0}".format(v3_bin))
|
||||||
|
|
||||||
test()
|
test()
|
||||||
|
|
||||||
print("Success : {0}".format(len(success)))
|
print("")
|
||||||
print("Failed : {0}".format(len(failed)))
|
print("=== Summary ===")
|
||||||
|
print("OK : {0}".format(len(ok)))
|
||||||
|
print("Counts diff : {0}".format(len(counts_diff)))
|
||||||
|
print("Digest diff : {0}".format(len(digest_diff)))
|
||||||
|
print("v3 failed : {0}".format(len(parse_failed)))
|
||||||
|
print("v1 skipped : {0}".format(len(v1_skipped)))
|
||||||
|
|
||||||
for fail in failed:
|
for f, diffs in counts_diff:
|
||||||
print("FAIL: " + fail)
|
print("COUNTS DIFF: " + f)
|
||||||
|
for k, a, b in diffs:
|
||||||
|
print(" {0}: v1={1} v3={2}".format(k, a, b))
|
||||||
|
for f, diffs in digest_diff:
|
||||||
|
print("DIGEST DIFF: " + f)
|
||||||
|
for line in diffs:
|
||||||
|
print(line)
|
||||||
|
for f, rc, err in parse_failed:
|
||||||
|
print("V3 FAIL: {0} (rc={1}) {2}".format(f, rc, err[:200]))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if counts_diff or digest_diff or parse_failed:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# Use this for strict compilation check(will work on clang 3.8+)
|
# Use this for strict compilation check(will work on clang 3.8+)
|
||||||
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -DTINYGLTF_APPLY_CLANG_WEVERYTHING
|
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -DTINYGLTF_APPLY_CLANG_WEVERYTHING
|
||||||
|
|
||||||
all: ../tiny_gltf.h
|
all: ../tiny_gltf.h tester_v3_c
|
||||||
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
|
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
|
||||||
clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
|
clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
|
||||||
clang++ -DTINYGLTF_USE_CUSTOM_JSON -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_intensive_customjson tester_intensive_customjson.cc
|
clang++ -DTINYGLTF_USE_CUSTOM_JSON -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_intensive_customjson tester_intensive_customjson.cc
|
||||||
clang -I../ -std=c11 -g -O0 -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
|
|
||||||
|
|
||||||
tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
|
tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
|
||||||
clang -I../ -std=c11 -g -O0 -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
|
clang -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
|
||||||
|
|||||||
@@ -2,8 +2,218 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ===== Digest helpers (used to compare v1 vs v3 parses) ===================== */
|
||||||
|
|
||||||
|
static uint64_t fnv64(const uint8_t *data, uint64_t n) {
|
||||||
|
uint64_t h = 0xcbf29ce484222325ULL;
|
||||||
|
uint64_t i;
|
||||||
|
for (i = 0; i < n; ++i) { h ^= data[i]; h *= 0x100000001b3ULL; }
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_str(const tg3_str *s) {
|
||||||
|
uint32_t i;
|
||||||
|
putchar('"');
|
||||||
|
if (s && s->data) {
|
||||||
|
for (i = 0; i < s->len; ++i) {
|
||||||
|
unsigned char c = (unsigned char)s->data[i];
|
||||||
|
if (c == '"' || c == '\\') { putchar('\\'); putchar((char)c); }
|
||||||
|
else if (c < 0x20 || c >= 0x7f) putchar('?');
|
||||||
|
else putchar((char)c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putchar('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_dbl(double v) { printf("%.7g", v); }
|
||||||
|
|
||||||
|
static void d_dbl_arr(const double *v, uint32_t n) {
|
||||||
|
uint32_t i;
|
||||||
|
putchar('[');
|
||||||
|
for (i = 0; i < n; ++i) { if (i) putchar(','); d_dbl(v[i]); }
|
||||||
|
putchar(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmp_str_int_pair(const void *a, const void *b) {
|
||||||
|
const tg3_str_int_pair *pa = (const tg3_str_int_pair *)a;
|
||||||
|
const tg3_str_int_pair *pb = (const tg3_str_int_pair *)b;
|
||||||
|
uint32_t la = pa->key.len, lb = pb->key.len;
|
||||||
|
uint32_t m = la < lb ? la : lb;
|
||||||
|
int r = memcmp(pa->key.data, pb->key.data, m);
|
||||||
|
if (r) return r;
|
||||||
|
return (la < lb) ? -1 : (la > lb ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d_attrs(const tg3_str_int_pair *attrs, uint32_t n) {
|
||||||
|
tg3_str_int_pair *sorted;
|
||||||
|
uint32_t i;
|
||||||
|
if (n == 0) { fputs("[]", stdout); return; }
|
||||||
|
sorted = (tg3_str_int_pair *)malloc(n * sizeof(*sorted));
|
||||||
|
memcpy(sorted, attrs, n * sizeof(*sorted));
|
||||||
|
qsort(sorted, n, sizeof(*sorted), cmp_str_int_pair);
|
||||||
|
putchar('[');
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
if (i) putchar(',');
|
||||||
|
printf("%.*s:%d", (int)sorted[i].key.len, sorted[i].key.data, sorted[i].value);
|
||||||
|
}
|
||||||
|
putchar(']');
|
||||||
|
free(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_digest(const tg3_model *m) {
|
||||||
|
uint32_t i, j;
|
||||||
|
printf("DIGEST_BEGIN\n");
|
||||||
|
|
||||||
|
printf("asset version=");
|
||||||
|
d_str(&m->asset.version);
|
||||||
|
printf(" generator=");
|
||||||
|
d_str(&m->asset.generator);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for (i = 0; i < m->buffers_count; ++i) {
|
||||||
|
const tg3_buffer *b = &m->buffers[i];
|
||||||
|
uint64_t h = b->data.data ? fnv64(b->data.data, b->data.count) : 0;
|
||||||
|
printf("buffer %u byte_length=%llu fnv64=0x%016llx\n",
|
||||||
|
i, (unsigned long long)b->data.count, (unsigned long long)h);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->buffer_views_count; ++i) {
|
||||||
|
const tg3_buffer_view *bv = &m->buffer_views[i];
|
||||||
|
printf("buffer_view %u buffer=%d byte_offset=%llu byte_length=%llu byte_stride=%u\n",
|
||||||
|
i, bv->buffer, (unsigned long long)bv->byte_offset,
|
||||||
|
(unsigned long long)bv->byte_length, bv->byte_stride);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->accessors_count; ++i) {
|
||||||
|
const tg3_accessor *a = &m->accessors[i];
|
||||||
|
printf("accessor %u buffer_view=%d byte_offset=%llu component_type=%d count=%llu type=%d normalized=%d min=",
|
||||||
|
i, a->buffer_view, (unsigned long long)a->byte_offset, a->component_type,
|
||||||
|
(unsigned long long)a->count, a->type, a->normalized);
|
||||||
|
d_dbl_arr(a->min_values, a->min_values_count);
|
||||||
|
printf(" max=");
|
||||||
|
d_dbl_arr(a->max_values, a->max_values_count);
|
||||||
|
printf(" sparse=%d\n", a->sparse.is_sparse);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->meshes_count; ++i) {
|
||||||
|
const tg3_mesh *me = &m->meshes[i];
|
||||||
|
printf("mesh %u primitives_count=%u weights_count=%u\n",
|
||||||
|
i, me->primitives_count, me->weights_count);
|
||||||
|
for (j = 0; j < me->primitives_count; ++j) {
|
||||||
|
const tg3_primitive *p = &me->primitives[j];
|
||||||
|
printf("prim %u %u indices=%d material=%d mode=%d attrs=", i, j,
|
||||||
|
p->indices, p->material, p->mode);
|
||||||
|
d_attrs(p->attributes, p->attributes_count);
|
||||||
|
printf(" targets_count=%u\n", p->targets_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->nodes_count; ++i) {
|
||||||
|
const tg3_node *n = &m->nodes[i];
|
||||||
|
printf("node %u mesh=%d skin=%d camera=%d light=%d children_count=%u has_matrix=%d t=",
|
||||||
|
i, n->mesh, n->skin, n->camera, n->light, n->children_count, n->has_matrix);
|
||||||
|
d_dbl_arr(n->translation, 3);
|
||||||
|
printf(" r=");
|
||||||
|
d_dbl_arr(n->rotation, 4);
|
||||||
|
printf(" s=");
|
||||||
|
d_dbl_arr(n->scale, 3);
|
||||||
|
printf(" matrix=");
|
||||||
|
d_dbl_arr(n->matrix, 16);
|
||||||
|
printf(" weights_count=%u\n", n->weights_count);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->materials_count; ++i) {
|
||||||
|
const tg3_material *mat = &m->materials[i];
|
||||||
|
printf("material %u alpha_mode=", i);
|
||||||
|
d_str(&mat->alpha_mode);
|
||||||
|
printf(" alpha_cutoff=");
|
||||||
|
d_dbl(mat->alpha_cutoff);
|
||||||
|
printf(" double_sided=%d emissive=", mat->double_sided);
|
||||||
|
d_dbl_arr(mat->emissive_factor, 3);
|
||||||
|
printf(" base_color_factor=");
|
||||||
|
d_dbl_arr(mat->pbr_metallic_roughness.base_color_factor, 4);
|
||||||
|
printf(" metallic=");
|
||||||
|
d_dbl(mat->pbr_metallic_roughness.metallic_factor);
|
||||||
|
printf(" roughness=");
|
||||||
|
d_dbl(mat->pbr_metallic_roughness.roughness_factor);
|
||||||
|
printf(" base_color_tex=%d normal_tex=%d occlusion_tex=%d emissive_tex=%d\n",
|
||||||
|
mat->pbr_metallic_roughness.base_color_texture.index,
|
||||||
|
mat->normal_texture.index,
|
||||||
|
mat->occlusion_texture.index,
|
||||||
|
mat->emissive_texture.index);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->textures_count; ++i) {
|
||||||
|
const tg3_texture *t = &m->textures[i];
|
||||||
|
printf("texture %u source=%d sampler=%d\n", i, t->source, t->sampler);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->samplers_count; ++i) {
|
||||||
|
const tg3_sampler *s = &m->samplers[i];
|
||||||
|
printf("sampler %u min_filter=%d mag_filter=%d wrap_s=%d wrap_t=%d\n",
|
||||||
|
i, s->min_filter, s->mag_filter, s->wrap_s, s->wrap_t);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->images_count; ++i) {
|
||||||
|
const tg3_image *im = &m->images[i];
|
||||||
|
/* mime_type and uri normalization differ between v1/v3 (data URIs,
|
||||||
|
extension inference); buffer_view reference is the parse-fidelity bit. */
|
||||||
|
printf("image %u buffer_view=%d\n", i, im->buffer_view);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->skins_count; ++i) {
|
||||||
|
const tg3_skin *s = &m->skins[i];
|
||||||
|
printf("skin %u inverse_bind_matrices=%d skeleton=%d joints_count=%u\n",
|
||||||
|
i, s->inverse_bind_matrices, s->skeleton, s->joints_count);
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->animations_count; ++i) {
|
||||||
|
const tg3_animation *a = &m->animations[i];
|
||||||
|
printf("animation %u channels_count=%u samplers_count=%u\n",
|
||||||
|
i, a->channels_count, a->samplers_count);
|
||||||
|
for (j = 0; j < a->channels_count; ++j) {
|
||||||
|
const tg3_animation_channel *c = &a->channels[j];
|
||||||
|
printf("chan %u %u sampler=%d target_node=%d target_path=", i, j,
|
||||||
|
c->sampler, c->target.node);
|
||||||
|
d_str(&c->target.path);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
for (j = 0; j < a->samplers_count; ++j) {
|
||||||
|
const tg3_animation_sampler *as = &a->samplers[j];
|
||||||
|
printf("samp %u %u input=%d output=%d interpolation=", i, j,
|
||||||
|
as->input, as->output);
|
||||||
|
d_str(&as->interpolation);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->cameras_count; ++i) {
|
||||||
|
const tg3_camera *c = &m->cameras[i];
|
||||||
|
int is_persp = (c->type.len == 11 && memcmp(c->type.data, "perspective", 11) == 0);
|
||||||
|
printf("camera %u type=", i);
|
||||||
|
d_str(&c->type);
|
||||||
|
if (is_persp) {
|
||||||
|
printf(" yfov=");
|
||||||
|
d_dbl(c->perspective.yfov);
|
||||||
|
printf(" znear=");
|
||||||
|
d_dbl(c->perspective.znear);
|
||||||
|
printf(" zfar=");
|
||||||
|
d_dbl(c->perspective.zfar);
|
||||||
|
printf(" aspect=");
|
||||||
|
d_dbl(c->perspective.aspect_ratio);
|
||||||
|
} else {
|
||||||
|
printf(" xmag=");
|
||||||
|
d_dbl(c->orthographic.xmag);
|
||||||
|
printf(" ymag=");
|
||||||
|
d_dbl(c->orthographic.ymag);
|
||||||
|
printf(" znear=");
|
||||||
|
d_dbl(c->orthographic.znear);
|
||||||
|
printf(" zfar=");
|
||||||
|
d_dbl(c->orthographic.zfar);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
for (i = 0; i < m->scenes_count; ++i) {
|
||||||
|
const tg3_scene *s = &m->scenes[i];
|
||||||
|
printf("scene %u nodes_count=%u\n", i, s->nodes_count);
|
||||||
|
}
|
||||||
|
printf("DIGEST_END\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================ */
|
||||||
|
|
||||||
static int mem_contains(const uint8_t *data, uint64_t size, const char *needle) {
|
static int mem_contains(const uint8_t *data, uint64_t size, const char *needle) {
|
||||||
size_t needle_len = strlen(needle);
|
size_t needle_len = strlen(needle);
|
||||||
uint64_t i;
|
uint64_t i;
|
||||||
@@ -139,8 +349,9 @@ static int check_parse_file_failure_initializes_model(void) {
|
|||||||
tg3_error_stack_init(&errors);
|
tg3_error_stack_init(&errors);
|
||||||
tg3_parse_options_init(&opts);
|
tg3_parse_options_init(&opts);
|
||||||
|
|
||||||
err = tg3_parse_file(&model, &errors, "scene.gltf", 10, &opts);
|
err = tg3_parse_file(&model, &errors,
|
||||||
if (err != TG3_ERR_FS_NOT_AVAILABLE) {
|
"tg3-tester-nonexistent-path.gltf", 32, &opts);
|
||||||
|
if (err != TG3_ERR_FS_NOT_AVAILABLE && err != TG3_ERR_FILE_NOT_FOUND) {
|
||||||
fprintf(stderr, "tg3_parse_file unexpected error: %d\n", (int)err);
|
fprintf(stderr, "tg3_parse_file unexpected error: %d\n", (int)err);
|
||||||
tg3_error_stack_free(&errors);
|
tg3_error_stack_free(&errors);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -212,7 +423,89 @@ static int check_huge_integer_field_rejected(void) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
static int parse_file_arg(const char *path) {
|
||||||
|
FILE *fp = fopen(path, "rb");
|
||||||
|
uint8_t *buf;
|
||||||
|
long sz;
|
||||||
|
size_t got;
|
||||||
|
tg3_model model;
|
||||||
|
tg3_error_stack errors;
|
||||||
|
tg3_parse_options opts;
|
||||||
|
tg3_error_code err;
|
||||||
|
int ok;
|
||||||
|
const char *slash;
|
||||||
|
size_t base_len;
|
||||||
|
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "open failed: %s\n", path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fseek(fp, 0, SEEK_END) != 0 || (sz = ftell(fp)) < 0 ||
|
||||||
|
fseek(fp, 0, SEEK_SET) != 0) {
|
||||||
|
fprintf(stderr, "seek failed: %s\n", path);
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
buf = (uint8_t *)malloc((size_t)sz);
|
||||||
|
if (!buf) {
|
||||||
|
fprintf(stderr, "alloc failed: %s\n", path);
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
got = fread(buf, 1, (size_t)sz, fp);
|
||||||
|
fclose(fp);
|
||||||
|
if (got != (size_t)sz) {
|
||||||
|
fprintf(stderr, "short read: %s\n", path);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
slash = strrchr(path, '/');
|
||||||
|
base_len = slash ? (size_t)(slash - path) : 0;
|
||||||
|
|
||||||
|
tg3_error_stack_init(&errors);
|
||||||
|
tg3_parse_options_init(&opts);
|
||||||
|
err = tg3_parse_auto(&model, &errors, buf, (uint64_t)sz,
|
||||||
|
path, (uint32_t)base_len, &opts);
|
||||||
|
ok = (err == TG3_OK);
|
||||||
|
if (!ok) {
|
||||||
|
uint32_t i;
|
||||||
|
fprintf(stderr, "parse failed (%d): %s\n", (int)err, path);
|
||||||
|
for (i = 0; i < errors.count; ++i) {
|
||||||
|
fprintf(stderr, " [%u] code=%d sev=%d path=%s msg=%s offset=%lld\n",
|
||||||
|
i, (int)errors.entries[i].code, (int)errors.entries[i].severity,
|
||||||
|
errors.entries[i].json_path ? errors.entries[i].json_path : "",
|
||||||
|
errors.entries[i].message ? errors.entries[i].message : "",
|
||||||
|
(long long)errors.entries[i].byte_offset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("COUNTS"
|
||||||
|
" accessors=%u animations=%u buffers=%u bufferViews=%u"
|
||||||
|
" cameras=%u images=%u materials=%u meshes=%u nodes=%u"
|
||||||
|
" samplers=%u scenes=%u skins=%u textures=%u lights=%u\n",
|
||||||
|
model.accessors_count, model.animations_count,
|
||||||
|
model.buffers_count, model.buffer_views_count,
|
||||||
|
model.cameras_count, model.images_count,
|
||||||
|
model.materials_count, model.meshes_count,
|
||||||
|
model.nodes_count, model.samplers_count,
|
||||||
|
model.scenes_count, model.skins_count,
|
||||||
|
model.textures_count, model.lights_count);
|
||||||
|
print_digest(&model);
|
||||||
|
}
|
||||||
|
tg3_model_free(&model);
|
||||||
|
tg3_error_stack_free(&errors);
|
||||||
|
free(buf);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc > 1) {
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < argc; ++i) {
|
||||||
|
if (!parse_file_arg(argv[i])) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!check_minimal_parse()) {
|
if (!check_minimal_parse()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
static const uint64_t FUZZ_MEMORY_BUDGET = 64ULL * 1024 * 1024;
|
static const uint64_t FUZZ_MEMORY_BUDGET = 64ULL * 1024 * 1024;
|
||||||
|
|
||||||
static void tg3_fuzz_parse_auto(const uint8_t *data, size_t size) {
|
typedef tg3_error_code (*tg3_fuzz_parse_fn)(tg3_model *, tg3_error_stack *,
|
||||||
|
const uint8_t *, uint64_t, const char *, uint32_t, const tg3_parse_options *);
|
||||||
|
|
||||||
|
static void tg3_fuzz_run(tg3_fuzz_parse_fn fn, int parse_float32,
|
||||||
|
const uint8_t *data, size_t size) {
|
||||||
tg3_model model;
|
tg3_model model;
|
||||||
tg3_error_stack errors;
|
tg3_error_stack errors;
|
||||||
tg3_parse_options opts;
|
tg3_parse_options opts;
|
||||||
@@ -13,83 +17,22 @@ static void tg3_fuzz_parse_auto(const uint8_t *data, size_t size) {
|
|||||||
tg3_error_stack_init(&errors);
|
tg3_error_stack_init(&errors);
|
||||||
tg3_parse_options_init(&opts);
|
tg3_parse_options_init(&opts);
|
||||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
||||||
|
opts.parse_float32 = parse_float32;
|
||||||
|
|
||||||
tg3_parse_auto(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
fn(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
||||||
|
|
||||||
tg3_model_free(&model);
|
|
||||||
tg3_error_stack_free(&errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tg3_fuzz_parse_json(const uint8_t *data, size_t size) {
|
|
||||||
tg3_model model;
|
|
||||||
tg3_error_stack errors;
|
|
||||||
tg3_parse_options opts;
|
|
||||||
|
|
||||||
tg3_error_stack_init(&errors);
|
|
||||||
tg3_parse_options_init(&opts);
|
|
||||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
|
||||||
|
|
||||||
tg3_parse(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
|
||||||
|
|
||||||
tg3_model_free(&model);
|
|
||||||
tg3_error_stack_free(&errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tg3_fuzz_parse_glb(const uint8_t *data, size_t size) {
|
|
||||||
tg3_model model;
|
|
||||||
tg3_error_stack errors;
|
|
||||||
tg3_parse_options opts;
|
|
||||||
|
|
||||||
tg3_error_stack_init(&errors);
|
|
||||||
tg3_parse_options_init(&opts);
|
|
||||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
|
||||||
|
|
||||||
tg3_parse_glb(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
|
||||||
|
|
||||||
tg3_model_free(&model);
|
|
||||||
tg3_error_stack_free(&errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tg3_fuzz_parse_float32(const uint8_t *data, size_t size) {
|
|
||||||
tg3_model model;
|
|
||||||
tg3_error_stack errors;
|
|
||||||
tg3_parse_options opts;
|
|
||||||
|
|
||||||
tg3_error_stack_init(&errors);
|
|
||||||
tg3_parse_options_init(&opts);
|
|
||||||
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
|
|
||||||
opts.parse_float32 = 1;
|
|
||||||
|
|
||||||
tg3_parse_auto(&model, &errors, data, (uint64_t)size, "", 0, &opts);
|
|
||||||
|
|
||||||
tg3_model_free(&model);
|
tg3_model_free(&model);
|
||||||
tg3_error_stack_free(&errors);
|
tg3_error_stack_free(&errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
uint8_t selector;
|
|
||||||
const uint8_t *payload;
|
|
||||||
size_t payload_size;
|
|
||||||
|
|
||||||
if (size == 0) return 0;
|
if (size == 0) return 0;
|
||||||
|
|
||||||
selector = data[0] % 4;
|
switch (data[0] % 4) {
|
||||||
payload = data + 1;
|
case 0: tg3_fuzz_run(tg3_parse_auto, 0, data + 1, size - 1); break;
|
||||||
payload_size = size - 1;
|
case 1: tg3_fuzz_run(tg3_parse, 0, data + 1, size - 1); break;
|
||||||
|
case 2: tg3_fuzz_run(tg3_parse_glb, 0, data + 1, size - 1); break;
|
||||||
switch (selector) {
|
case 3: tg3_fuzz_run(tg3_parse_auto, 1, data + 1, size - 1); break;
|
||||||
case 0:
|
|
||||||
tg3_fuzz_parse_auto(payload, payload_size);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
tg3_fuzz_parse_json(payload, payload_size);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
tg3_fuzz_parse_glb(payload, payload_size);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
tg3_fuzz_parse_float32(payload, payload_size);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -473,7 +473,9 @@ static int tg3__json_number_to_int32(const tg3json_value *v, int32_t *out) {
|
|||||||
static int tg3__json_number_to_uint64(const tg3json_value *v, uint64_t *out) {
|
static int tg3__json_number_to_uint64(const tg3json_value *v, uint64_t *out) {
|
||||||
double real;
|
double real;
|
||||||
uint64_t converted;
|
uint64_t converted;
|
||||||
/* Largest safely castable integer expressible as double below 2^64. */
|
/* Doubles have a 53-bit mantissa, so 2^64 itself rounds up and would be UB
|
||||||
|
on cast to uint64_t. Cap at the largest representable value strictly
|
||||||
|
below 2^64 (== 2^64 - 2^11). */
|
||||||
const double max_safe_uint64_real = 18446744073709547520.0;
|
const double max_safe_uint64_real = 18446744073709547520.0;
|
||||||
if (!tg3__json_is_number(v) || !out) return 0;
|
if (!tg3__json_is_number(v) || !out) return 0;
|
||||||
if (v->type == TG3JSON_INT) {
|
if (v->type == TG3JSON_INT) {
|
||||||
@@ -491,6 +493,13 @@ static int tg3__json_number_to_uint64(const tg3json_value *v, uint64_t *out) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double tg3__json_number_to_double(const tg3json_value *v) {
|
||||||
|
if (!v) return 0.0;
|
||||||
|
if (v->type == TG3JSON_INT) return (double)v->u.integer;
|
||||||
|
if (v->type == TG3JSON_REAL) return v->u.real;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
static int tg3__json_is_object(const tg3json_value *v) { return v && v->type == TG3JSON_OBJECT; }
|
static int tg3__json_is_object(const tg3json_value *v) { return v && v->type == TG3JSON_OBJECT; }
|
||||||
static int tg3__json_is_array(const tg3json_value *v) { return v && v->type == TG3JSON_ARRAY; }
|
static int tg3__json_is_array(const tg3json_value *v) { return v && v->type == TG3JSON_ARRAY; }
|
||||||
|
|
||||||
@@ -588,7 +597,7 @@ static int tg3__parse_double(tg3__parse_ctx *ctx, const tg3json_value *o, const
|
|||||||
parent, "Field '%s' must be a number", key);
|
parent, "Field '%s' must be a number", key);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*out = (it->type == TG3JSON_INT) ? (double)it->u.integer : it->u.real;
|
*out = tg3__json_number_to_double(it);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,8 +656,7 @@ static int tg3__parse_number_array(tg3__parse_ctx *ctx, const tg3json_value *o,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for (i = 0; i < count; ++i) {
|
for (i = 0; i < count; ++i) {
|
||||||
const tg3json_value *item = tg3json_array_get(it, i);
|
arr[i] = tg3__json_number_to_double(tg3json_array_get(it, i));
|
||||||
arr[i] = (item && item->type == TG3JSON_REAL) ? item->u.real : (item ? (double)item->u.integer : 0.0);
|
|
||||||
}
|
}
|
||||||
*out = arr;
|
*out = arr;
|
||||||
*out_count = (uint32_t)count;
|
*out_count = (uint32_t)count;
|
||||||
@@ -715,7 +723,7 @@ static void tg3__parse_number_to_fixed(const tg3json_value *o, const char *key,
|
|||||||
for (i = 0; i < count; ++i) {
|
for (i = 0; i < count; ++i) {
|
||||||
const tg3json_value *item = tg3json_array_get(it, i);
|
const tg3json_value *item = tg3json_array_get(it, i);
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
out[i] = (item->type == TG3JSON_REAL) ? item->u.real : (double)item->u.integer;
|
out[i] = tg3__json_number_to_double(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user