From 19b806e052ce6c20bed425f455736befb877a01a Mon Sep 17 00:00:00 2001 From: Jeff McGlynn Date: Fri, 26 Apr 2019 14:49:38 -0700 Subject: [PATCH] Parse integers directly instead of casting doubles When parsing numeric values as doubles, its possible for users to specify values that cannot be converted to integers, such as Inf, NaN, and extremes such as 1e100. If this value is received, and then cast to an int, it is undefined behavior, which trips ubsan when running tinygltf under a fuzzer. Instead of parsing integral values as doubles, use nlohmann/json's built-in support to parse integer and unsigned values directly, with .is_number_integer() and .is_number_unsigned(). Add ParseIntegerProperty, ParseUnsignedProperty, and ParseIntegerArrayProperty helpers that allow parsing directly to int/uint values and update code to use them when appropriate. --- .../BoundsChecking/integer-out-of-bounds.gltf | 67 +++ tests/tester.cc | 133 +++++- tiny_gltf.h | 439 +++++++++++------- 3 files changed, 468 insertions(+), 171 deletions(-) create mode 100644 models/BoundsChecking/integer-out-of-bounds.gltf diff --git a/models/BoundsChecking/integer-out-of-bounds.gltf b/models/BoundsChecking/integer-out-of-bounds.gltf new file mode 100644 index 0000000..8f73d63 --- /dev/null +++ b/models/BoundsChecking/integer-out-of-bounds.gltf @@ -0,0 +1,67 @@ +{ + "scenes": [ + { + "nodes": [0] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 1 + }, + "indices": 0 + } + ] + } + ], + "buffers": [ + { + "uri": "simpleTriangle.bin", + "byteLength": 44 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 1e300, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 8, + "byteLength": 36, + "target": 34962 + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 3, + "type": "SCALAR", + "max": [2], + "min": [0] + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3", + "max": [1, 1, 0], + "min": [0, 0, 0] + } + ], + "asset": { + "version": "2.0" + } +} diff --git a/tests/tester.cc b/tests/tester.cc index a07c549..f3e96dd 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -37,7 +37,7 @@ TEST_CASE("datauri-in-glb", "[issue-79]") { if (!err.empty()) { std::cerr << err << std::endl; } - + REQUIRE(true == ret); } @@ -148,3 +148,134 @@ TEST_CASE("glb-invalid-length", "[bounds-checking]") { REQUIRE_THAT(err, Catch::Contains("Invalid glTF binary.")); REQUIRE_FALSE(ret); } + +TEST_CASE("integer-out-of-bounds", "[bounds-checking]") { + tinygltf::Model model; + tinygltf::TinyGLTF ctx; + std::string err; + std::string warn; + + // Loading is expected to fail, but not crash. + bool ret = ctx.LoadASCIIFromFile( + &model, &err, &warn, + "../models/BoundsChecking/integer-out-of-bounds.gltf"); + REQUIRE_THAT(err, Catch::Contains("not a positive integer")); + REQUIRE_FALSE(ret); +} + +TEST_CASE("parse-integer", "[bounds-checking]") { + SECTION("parses valid numbers") { + std::string err; + int result = 123; + CHECK(tinygltf::ParseIntegerProperty(&result, &err, {{"zero", 0}}, "zero", + true)); + REQUIRE(err == ""); + REQUIRE(result == 0); + + CHECK(tinygltf::ParseIntegerProperty(&result, &err, {{"int", -1234}}, "int", + true)); + REQUIRE(err == ""); + REQUIRE(result == -1234); + } + + SECTION("detects missing properties") { + std::string err; + int result = -1; + CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {}, "int", true)); + REQUIRE_THAT(err, Catch::Contains("'int' property is missing")); + REQUIRE(result == -1); + } + + SECTION("handled missing but not required properties") { + std::string err; + int result = -1; + CHECK_FALSE( + tinygltf::ParseIntegerProperty(&result, &err, {}, "int", false)); + REQUIRE(err == ""); + REQUIRE(result == -1); + } + + SECTION("invalid integers") { + std::string err; + int result = -1; + + CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {{"int", 0.5}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not an integer type")); + + // Excessively large values and NaN aren't allowed either. + err.clear(); + CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {{"int", 1e300}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not an integer type")); + + err.clear(); + CHECK_FALSE(tinygltf::ParseIntegerProperty( + &result, &err, {{"int", std::numeric_limits::quiet_NaN()}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not an integer type")); + } +} + +TEST_CASE("parse-unsigned", "[bounds-checking]") { + SECTION("parses valid unsigned integers") { + // Use string-based parsing here, using the initializer list syntax doesn't + // parse 0 as unsigned. + json zero_obj = json::parse("{\"zero\": 0}"); + + std::string err; + size_t result = 123; + CHECK( + tinygltf::ParseUnsignedProperty(&result, &err, zero_obj, "zero", true)); + REQUIRE(err == ""); + REQUIRE(result == 0); + } + + SECTION("invalid integers") { + std::string err; + size_t result = -1; + + CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", -1234}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not a positive integer")); + + err.clear(); + CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", 0.5}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not a positive integer")); + + // Excessively large values and NaN aren't allowed either. + err.clear(); + CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", 1e300}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not a positive integer")); + + err.clear(); + CHECK_FALSE(tinygltf::ParseUnsignedProperty( + &result, &err, {{"int", std::numeric_limits::quiet_NaN()}}, + "int", true)); + REQUIRE_THAT(err, Catch::Contains("not a positive integer")); + } +} + +TEST_CASE("parse-integer-array", "[bounds-checking]") { + SECTION("parses valid integers") { + std::string err; + std::vector result; + CHECK(tinygltf::ParseIntegerArrayProperty(&result, &err, + {{"x", {-1, 2, 3}}}, "x", true)); + REQUIRE(err == ""); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == -1); + REQUIRE(result[1] == 2); + REQUIRE(result[2] == 3); + } + + SECTION("invalid integers") { + std::string err; + std::vector result; + CHECK_FALSE(tinygltf::ParseIntegerArrayProperty( + &result, &err, {{"x", {-1, 1e300, 3}}}, "x", true)); + REQUIRE_THAT(err, Catch::Contains("not an integer type")); + } +} diff --git a/tiny_gltf.h b/tiny_gltf.h index 7f2e239..b499184 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -2271,6 +2271,74 @@ static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, return true; } +static bool ParseIntegerProperty(int *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_number_integer()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; +} + +static bool ParseUnsignedProperty(size_t *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_number_unsigned()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a positive integer.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; +} + static bool ParseNumberProperty(double *ret, std::string *err, const json &o, const std::string &property, const bool required, @@ -2357,6 +2425,59 @@ static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, return true; } +static bool ParseIntegerArrayProperty(std::vector *ret, std::string *err, + const json &o, + const std::string &property, + bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_array()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + ret->clear(); + for (json::const_iterator i = it.value().begin(); i != it.value().end(); + i++) { + if (!i.value().is_number_integer()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(i.value()); + } + + return true; +} + static bool ParseStringProperty( std::string *ret, std::string *err, const json &o, const std::string &property, bool required, @@ -2392,10 +2513,11 @@ static bool ParseStringProperty( return true; } -static bool ParseStringIntProperty(std::map *ret, - std::string *err, const json &o, - const std::string &property, bool required, - const std::string &parent = "") { +static bool ParseStringIntegerProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, + bool required, + const std::string &parent = "") { json::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -2428,17 +2550,17 @@ static bool ParseStringIntProperty(std::map *ret, json::const_iterator dictItEnd(dict.end()); for (; dictIt != dictItEnd; ++dictIt) { - if (!dictIt.value().is_number()) { + if (!dictIt.value().is_number_integer()) { if (required) { if (err) { - (*err) += "'" + property + "' value is not an int.\n"; + (*err) += "'" + property + "' value is not an integer type.\n"; } } return false; } // Insert into the list. - (*ret)[dictIt.key()] = static_cast(dictIt.value()); + (*ret)[dictIt.key()] = dictIt.value(); } return true; } @@ -2589,8 +2711,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, ParseExtrasProperty(&image->extras, o); if (hasBufferView) { - double bufferView = -1; - if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { + int bufferView = -1; + if (!ParseIntegerProperty(&bufferView, err, o, "bufferView", true)) { if (err) { (*err) += "Failed to parse `bufferView` for image[" + std::to_string(image_idx) + "] name = \"" + image->name + @@ -2602,18 +2724,18 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, std::string mime_type; ParseStringProperty(&mime_type, err, o, "mimeType", false); - double width = 0.0; - ParseNumberProperty(&width, err, o, "width", false); + int width = 0; + ParseIntegerProperty(&width, err, o, "width", false); - double height = 0.0; - ParseNumberProperty(&height, err, o, "height", false); + int height = 0; + ParseIntegerProperty(&height, err, o, "height", false); // Just only save some information here. Loading actual image data from // bufferView is done after this `ParseImage` function. - image->bufferView = static_cast(bufferView); + image->bufferView = bufferView; image->mimeType = mime_type; - image->width = static_cast(width); - image->height = static_cast(height); + image->width = width; + image->height = height; return true; } @@ -2681,14 +2803,14 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, static bool ParseTexture(Texture *texture, std::string *err, const json &o, const std::string &basedir) { (void)basedir; - double sampler = -1.0; - double source = -1.0; - ParseNumberProperty(&sampler, err, o, "sampler", false); + int sampler = -1; + int source = -1; + ParseIntegerProperty(&sampler, err, o, "sampler", false); - ParseNumberProperty(&source, err, o, "source", false); + ParseIntegerProperty(&source, err, o, "source", false); - texture->sampler = static_cast(sampler); - texture->source = static_cast(source); + texture->sampler = sampler; + texture->source = source; ParseExtensionsProperty(&texture->extensions, err, o); ParseExtrasProperty(&texture->extras, o); @@ -2703,8 +2825,9 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, bool is_binary = false, const unsigned char *bin_data = nullptr, size_t bin_size = 0) { - double byteLength; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, "Buffer")) { + size_t byteLength; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "Buffer")) { return false; } @@ -2729,14 +2852,13 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, } } - size_t bytes = static_cast(byteLength); if (is_binary) { // Still binary glTF accepts external dataURI. if (!buffer->uri.empty()) { // First try embedded data URI. if (IsDataURI(buffer->uri)) { std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, true)) { if (err) { (*err) += @@ -2747,7 +2869,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, } else { // External .bin file. if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, - buffer->uri, basedir, true, bytes, true, fs)) { + buffer->uri, basedir, true, byteLength, true, + fs)) { return false; } } @@ -2780,7 +2903,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, } else { if (IsDataURI(buffer->uri)) { std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, true)) { + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, + true)) { if (err) { (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; } @@ -2789,7 +2913,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, } else { // Assume external .bin file. if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, - basedir, true, bytes, true, fs)) { + basedir, true, byteLength, true, fs)) { return false; } } @@ -2802,31 +2926,28 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, static bool ParseBufferView(BufferView *bufferView, std::string *err, const json &o) { - double buffer = -1.0; - if (!ParseNumberProperty(&buffer, err, o, "buffer", true, "BufferView")) { + int buffer = -1; + if (!ParseIntegerProperty(&buffer, err, o, "buffer", true, "BufferView")) { return false; } - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false); + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false); - double byteLength = 1.0; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, - "BufferView")) { + size_t byteLength = 1; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "BufferView")) { return false; } size_t byteStride = 0; - double byteStrideValue = 0.0; - if (!ParseNumberProperty(&byteStrideValue, err, o, "byteStride", false)) { + if (!ParseUnsignedProperty(&byteStride, err, o, "byteStride", false)) { // Spec says: When byteStride of referenced bufferView is not defined, it // means that accessor elements are tightly packed, i.e., effective stride // equals the size of the element. // We cannot determine the actual byteStride until Accessor are parsed, thus // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) byteStride = 0; - } else { - byteStride = static_cast(byteStrideValue); } if ((byteStride > 252) || ((byteStride % 4) != 0)) { @@ -2841,23 +2962,22 @@ static bool ParseBufferView(BufferView *bufferView, std::string *err, return false; } - double target = 0.0; - ParseNumberProperty(&target, err, o, "target", false); - int targetValue = static_cast(target); - if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || - (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { + int target = 0; + ParseIntegerProperty(&target, err, o, "target", false); + if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) || + (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { // OK } else { - targetValue = 0; + target = 0; } - bufferView->target = targetValue; + bufferView->target = target; ParseStringProperty(&bufferView->name, err, o, "name", false); - bufferView->buffer = static_cast(buffer); - bufferView->byteOffset = static_cast(byteOffset); - bufferView->byteLength = static_cast(byteLength); - bufferView->byteStride = static_cast(byteStride); + bufferView->buffer = buffer; + bufferView->byteOffset = byteOffset; + bufferView->byteLength = byteLength; + bufferView->byteStride = byteStride; return true; } @@ -2865,8 +2985,8 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err, const json &o) { accessor->sparse.isSparse = true; - double count = 0.0; - ParseNumberProperty(&count, err, o, "count", true); + int count = 0; + ParseIntegerProperty(&count, err, o, "count", true); const auto indices_iterator = o.find("indices"); const auto values_iterator = o.find("values"); @@ -2883,24 +3003,26 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err, const json &indices_obj = *indices_iterator; const json &values_obj = *values_iterator; - double indices_buffer_view = 0.0, indices_byte_offset = 0.0, - component_type = 0.0; - ParseNumberProperty(&indices_buffer_view, err, indices_obj, "bufferView", - true); - ParseNumberProperty(&indices_byte_offset, err, indices_obj, "byteOffset", - true); - ParseNumberProperty(&component_type, err, indices_obj, "componentType", true); + int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0; + ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView", + true); + ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset", + true); + ParseIntegerProperty(&component_type, err, indices_obj, "componentType", + true); - double values_buffer_view = 0.0, values_byte_offset = 0.0; - ParseNumberProperty(&values_buffer_view, err, values_obj, "bufferView", true); - ParseNumberProperty(&values_byte_offset, err, values_obj, "byteOffset", true); + int values_buffer_view = 0, values_byte_offset = 0; + ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView", + true); + ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset", + true); - accessor->sparse.count = static_cast(count); - accessor->sparse.indices.bufferView = static_cast(indices_buffer_view); - accessor->sparse.indices.byteOffset = static_cast(indices_byte_offset); - accessor->sparse.indices.componentType = static_cast(component_type); - accessor->sparse.values.bufferView = static_cast(values_buffer_view); - accessor->sparse.values.byteOffset = static_cast(values_byte_offset); + accessor->sparse.count = count; + accessor->sparse.indices.bufferView = indices_buffer_view; + accessor->sparse.indices.byteOffset = indices_byte_offset; + accessor->sparse.indices.componentType = component_type; + accessor->sparse.values.bufferView = values_buffer_view; + accessor->sparse.values.byteOffset = values_byte_offset; // todo check theses values @@ -2908,23 +3030,23 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err, } static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { - double bufferView = -1.0; - ParseNumberProperty(&bufferView, err, o, "bufferView", false, "Accessor"); + int bufferView = -1; + ParseIntegerProperty(&bufferView, err, o, "bufferView", false, "Accessor"); - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); bool normalized = false; ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); - double componentType = 0.0; - if (!ParseNumberProperty(&componentType, err, o, "componentType", true, - "Accessor")) { + size_t componentType = 0; + if (!ParseUnsignedProperty(&componentType, err, o, "componentType", true, + "Accessor")) { return false; } - double count = 0.0; - if (!ParseNumberProperty(&count, err, o, "count", true, "Accessor")) { + size_t count = 0; + if (!ParseUnsignedProperty(&count, err, o, "count", true, "Accessor")) { return false; } @@ -2966,19 +3088,19 @@ static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, "Accessor"); - accessor->count = static_cast(count); - accessor->bufferView = static_cast(bufferView); - accessor->byteOffset = static_cast(byteOffset); + accessor->count = count; + accessor->bufferView = bufferView; + accessor->byteOffset = byteOffset; accessor->normalized = normalized; { - int comp = static_cast(componentType); - if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && - comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { + if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE && + componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { // OK - accessor->componentType = comp; + accessor->componentType = componentType; } else { std::stringstream ss; - ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; + ss << "Invalid `componentType` in accessor. Got " << componentType + << "\n"; if (err) { (*err) += ss.str(); } @@ -3192,21 +3314,19 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model, static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, const json &o) { - double material = -1.0; - ParseNumberProperty(&material, err, o, "material", false); - primitive->material = static_cast(material); + int material = -1; + ParseIntegerProperty(&material, err, o, "material", false); + primitive->material = material; - double mode = static_cast(TINYGLTF_MODE_TRIANGLES); - ParseNumberProperty(&mode, err, o, "mode", false); + int mode = TINYGLTF_MODE_TRIANGLES; + ParseIntegerProperty(&mode, err, o, "mode", false); + primitive->mode = mode; // Why only triangled were supported ? - int primMode = static_cast(mode); - primitive->mode = primMode; // Why only triangled were supported ? - - double indices = -1.0; - ParseNumberProperty(&indices, err, o, "indices", false); - primitive->indices = static_cast(indices); - if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", - true, "Primitive")) { + int indices = -1; + ParseIntegerProperty(&indices, err, o, "indices", false); + primitive->indices = indices; + if (!ParseStringIntegerProperty(&primitive->attributes, err, o, "attributes", + true, "Primitive")) { return false; } @@ -3299,9 +3419,9 @@ static bool ParseLight(Light *light, std::string *err, const json &o) { static bool ParseNode(Node *node, std::string *err, const json &o) { ParseStringProperty(&node->name, err, o, "name", false); - double skin = -1.0; - ParseNumberProperty(&skin, err, o, "skin", false); - node->skin = static_cast(skin); + int skin = -1; + ParseIntegerProperty(&skin, err, o, "skin", false); + node->skin = skin; // Matrix and T/R/S are exclusive if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { @@ -3310,29 +3430,16 @@ static bool ParseNode(Node *node, std::string *err, const json &o) { ParseNumberArrayProperty(&node->translation, err, o, "translation", false); } - double camera = -1.0; - ParseNumberProperty(&camera, err, o, "camera", false); - node->camera = static_cast(camera); + int camera = -1; + ParseIntegerProperty(&camera, err, o, "camera", false); + node->camera = camera; - double mesh = -1.0; - ParseNumberProperty(&mesh, err, o, "mesh", false); - node->mesh = int(mesh); + int mesh = -1; + ParseIntegerProperty(&mesh, err, o, "mesh", false); + node->mesh = mesh; node->children.clear(); - json::const_iterator childrenObject = o.find("children"); - if ((childrenObject != o.end()) && childrenObject.value().is_array()) { - for (json::const_iterator i = childrenObject.value().begin(); - i != childrenObject.value().end(); i++) { - if (!i.value().is_number()) { - if (err) { - (*err) += "Invalid `children` array.\n"; - } - return false; - } - const int &childrenNode = static_cast(i.value()); - node->children.push_back(childrenNode); - } - } + ParseIntegerArrayProperty(&node->children, err, o, "children", false); ParseExtensionsProperty(&node->extensions, err, o); ParseExtrasProperty(&(node->extras), o); @@ -3383,10 +3490,10 @@ static bool ParseMaterial(Material *material, std::string *err, const json &o) { static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, const json &o) { - double samplerIndex = -1.0; - double targetIndex = -1.0; - if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true, - "AnimationChannel")) { + int samplerIndex = -1; + int targetIndex = -1; + if (!ParseIntegerProperty(&samplerIndex, err, o, "sampler", true, + "AnimationChannel")) { if (err) { (*err) += "`sampler` field is missing in animation channels\n"; } @@ -3397,7 +3504,7 @@ static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, if ((targetIt != o.end()) && targetIt.value().is_object()) { const json &target_object = targetIt.value(); - if (!ParseNumberProperty(&targetIndex, err, target_object, "node", true)) { + if (!ParseIntegerProperty(&targetIndex, err, target_object, "node", true)) { if (err) { (*err) += "`node` field is missing in animation.channels.target\n"; } @@ -3413,8 +3520,8 @@ static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, } } - channel->sampler = static_cast(samplerIndex); - channel->target_node = static_cast(targetIndex); + channel->sampler = samplerIndex; + channel->target_node = targetIndex; ParseExtrasProperty(&(channel->extras), o); @@ -3449,9 +3556,9 @@ static bool ParseAnimation(Animation *animation, std::string *err, const json &s = it->get(); AnimationSampler sampler; - double inputIndex = -1.0; - double outputIndex = -1.0; - if (!ParseNumberProperty(&inputIndex, err, s, "input", true)) { + int inputIndex = -1; + int outputIndex = -1; + if (!ParseIntegerProperty(&inputIndex, err, s, "input", true)) { if (err) { (*err) += "`input` field is missing in animation.sampler\n"; } @@ -3459,14 +3566,14 @@ static bool ParseAnimation(Animation *animation, std::string *err, } ParseStringProperty(&sampler.interpolation, err, s, "interpolation", false); - if (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { + if (!ParseIntegerProperty(&outputIndex, err, s, "output", true)) { if (err) { (*err) += "`output` field is missing in animation.sampler\n"; } return false; } - sampler.input = static_cast(inputIndex); - sampler.output = static_cast(outputIndex); + sampler.input = inputIndex; + sampler.output = outputIndex; ParseExtrasProperty(&(sampler.extras), s); animation->samplers.push_back(sampler); } @@ -3483,20 +3590,19 @@ static bool ParseAnimation(Animation *animation, std::string *err, static bool ParseSampler(Sampler *sampler, std::string *err, const json &o) { ParseStringProperty(&sampler->name, err, o, "name", false); - double minFilter = - static_cast(TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR); - double magFilter = static_cast(TINYGLTF_TEXTURE_FILTER_LINEAR); - double wrapS = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); - double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); - ParseNumberProperty(&minFilter, err, o, "minFilter", false); - ParseNumberProperty(&magFilter, err, o, "magFilter", false); - ParseNumberProperty(&wrapS, err, o, "wrapS", false); - ParseNumberProperty(&wrapT, err, o, "wrapT", false); + int minFilter = TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR; + int magFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; + int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; + int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; + ParseIntegerProperty(&minFilter, err, o, "minFilter", false); + ParseIntegerProperty(&magFilter, err, o, "magFilter", false); + ParseIntegerProperty(&wrapS, err, o, "wrapS", false); + ParseIntegerProperty(&wrapT, err, o, "wrapT", false); - sampler->minFilter = static_cast(minFilter); - sampler->magFilter = static_cast(magFilter); - sampler->wrapS = static_cast(wrapS); - sampler->wrapT = static_cast(wrapT); + sampler->minFilter = minFilter; + sampler->magFilter = magFilter; + sampler->wrapS = wrapS; + sampler->wrapT = wrapT; ParseExtrasProperty(&(sampler->extras), o); @@ -3506,23 +3612,19 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const json &o) { static bool ParseSkin(Skin *skin, std::string *err, const json &o) { ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); - std::vector joints; - if (!ParseNumberArrayProperty(&joints, err, o, "joints", false, "Skin")) { + std::vector joints; + if (!ParseIntegerArrayProperty(&joints, err, o, "joints", false, "Skin")) { return false; } + skin->joints = std::move(joints); - double skeleton = -1.0; - ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); - skin->skeleton = static_cast(skeleton); + int skeleton = -1; + ParseIntegerProperty(&skeleton, err, o, "skeleton", false, "Skin"); + skin->skeleton = skeleton; - skin->joints.resize(joints.size()); - for (size_t i = 0; i < joints.size(); i++) { - skin->joints[i] = static_cast(joints[i]); - } - - double invBind = -1.0; - ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); - skin->inverseBindMatrices = static_cast(invBind); + int invBind = -1; + ParseIntegerProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); + skin->inverseBindMatrices = invBind; return true; } @@ -3999,18 +4101,15 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, return false; } const json &o = it->get(); - std::vector nodes; - if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { + std::vector nodes; + if (!ParseIntegerArrayProperty(&nodes, err, o, "nodes", false)) { return false; } Scene scene; + scene.nodes = std::move(nodes); + ParseStringProperty(&scene.name, err, o, "name", false); - std::vector nodesIds; - for (size_t i = 0; i < nodes.size(); i++) { - nodesIds.push_back(static_cast(nodes[i])); - } - scene.nodes = nodesIds; ParseExtensionsProperty(&scene.extensions, err, o); ParseExtrasProperty(&scene.extras, o); @@ -4023,10 +4122,10 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, // 9. Parse default scenes. { json::const_iterator rootIt = v.find("scene"); - if ((rootIt != v.end()) && rootIt.value().is_number()) { + if ((rootIt != v.end()) && rootIt.value().is_number_integer()) { const int defaultScene = rootIt.value(); - model->defaultScene = static_cast(defaultScene); + model->defaultScene = defaultScene; } }