diff --git a/src/rest_utils.cpp b/src/rest_utils.cpp index 8d65fe8ee5..912ac1a9dc 100644 --- a/src/rest_utils.cpp +++ b/src/rest_utils.cpp @@ -15,6 +15,7 @@ //***************************************************************************** #include "rest_utils.hpp" +#include #include #include #include @@ -59,6 +60,21 @@ enum : unsigned int { namespace ovms { +namespace { +// Integral types are always JSON-representable; only floating values can be NaN/Inf, +// which rapidjson cannot serialize (and which are invalid per the JSON spec). +template +inline bool isJsonRepresentable(T) { + return true; +} +inline bool isJsonRepresentable(float v) { + return std::isfinite(v); +} +inline bool isJsonRepresentable(double v) { + return std::isfinite(v); +} +} // namespace + static Status checkValField(const size_t& fieldSize, const size_t& expectedElementsNumber) { if (fieldSize != expectedElementsNumber) { std::stringstream ss; @@ -107,6 +123,11 @@ Status makeJsonFromPredictResponse( for (size_t i = 0; i < tensor.tensor_content().size(); i += sizeof(float)) tensor.add_float_val(*reinterpret_cast(tensor.mutable_tensor_content()->data() + i)); } + for (float v : tensor.float_val()) { + if (!isJsonRepresentable(v)) + return Status(StatusCode::JSON_SERIALIZATION_ERROR, + "Output \"" + kv.first + "\" contains a non-finite (NaN/Inf) value that cannot be serialized to JSON"); + } break; case DataType::DT_INT32: if (seekDataInValField) { @@ -147,6 +168,11 @@ Status makeJsonFromPredictResponse( for (size_t i = 0; i < tensor.tensor_content().size(); i += sizeof(double)) tensor.add_double_val(*reinterpret_cast(tensor.mutable_tensor_content()->data() + i)); } + for (double v : tensor.double_val()) { + if (!isJsonRepresentable(v)) + return Status(StatusCode::JSON_SERIALIZATION_ERROR, + "Output \"" + kv.first + "\" contains a non-finite (NaN/Inf) value that cannot be serialized to JSON"); + } break; case DataType::DT_INT16: if (seekDataInValField) { @@ -291,6 +317,9 @@ static void appendBinaryOutput(std::string& bytesOutputsBuffer, char* output, si appendBinaryOutput(bytesOutputsBuffer, (char*)tensor.contents().CONTENTS_FIELD().data(), expectedContentSize); \ } else { \ for (auto& number : tensor.contents().CONTENTS_FIELD()) { \ + if (!isJsonRepresentable(number)) \ + return Status(StatusCode::JSON_SERIALIZATION_ERROR, \ + "Output \"" + tensor.name() + "\" contains a non-finite (NaN/Inf) value that cannot be serialized to JSON"); \ writer.WRITER_TYPE(number); \ } \ } \ @@ -298,8 +327,13 @@ static void appendBinaryOutput(std::string& bytesOutputsBuffer, char* output, si if (binaryOutput) { \ appendBinaryOutput(bytesOutputsBuffer, (char*)response_proto.raw_output_contents(tensor_it).data(), expectedContentSize); \ } else { \ - for (size_t i = 0; i < response_proto.raw_output_contents(tensor_it).size(); i += sizeof(DATATYPE)) \ - writer.WRITER_TYPE(*(reinterpret_cast(response_proto.raw_output_contents(tensor_it).data() + i))); \ + for (size_t i = 0; i < response_proto.raw_output_contents(tensor_it).size(); i += sizeof(DATATYPE)) { \ + DATATYPE valToWrite = *(reinterpret_cast(response_proto.raw_output_contents(tensor_it).data() + i)); \ + if (!isJsonRepresentable(valToWrite)) \ + return Status(StatusCode::JSON_SERIALIZATION_ERROR, \ + "Output \"" + tensor.name() + "\" contains a non-finite (NaN/Inf) value that cannot be serialized to JSON"); \ + writer.WRITER_TYPE(valToWrite); \ + } \ } \ } diff --git a/src/test/rest_utils_test.cpp b/src/test/rest_utils_test.cpp index 6d6df46be5..b2676891fa 100644 --- a/src/test/rest_utils_test.cpp +++ b/src/test/rest_utils_test.cpp @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. //***************************************************************************** +#include #include #include @@ -133,6 +134,33 @@ TEST_F(TFSMakeJsonFromPredictResponseRawTest, CannotConvertInvalidPrecision) { EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, Order::COLUMN), StatusCode::REST_UNSUPPORTED_PRECISION); } +TEST_F(TFSMakeJsonFromPredictResponseRawTest, NonFiniteInfFloatError) { + float data[8] = {5.0f, std::numeric_limits::infinity(), -3.0f, 2.5f, + 9.0f, 55.5f, -0.5f, -1.5f}; + output1->mutable_tensor_content()->assign(reinterpret_cast(data), 8 * sizeof(float)); + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, Order::COLUMN), StatusCode::JSON_SERIALIZATION_ERROR); +} + +TEST_F(TFSMakeJsonFromPredictResponseRawTest, NonFiniteNaNFloatError) { + float data[8] = {5.0f, 10.0f, std::numeric_limits::quiet_NaN(), 2.5f, + 9.0f, 55.5f, -0.5f, -1.5f}; + output1->mutable_tensor_content()->assign(reinterpret_cast(data), 8 * sizeof(float)); + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, Order::COLUMN), StatusCode::JSON_SERIALIZATION_ERROR); +} + +TEST_F(TFSMakeJsonFromPredictResponseRawTest, NonFiniteDoubleError) { + output1->set_dtype(tensorflow::DataType::DT_DOUBLE); + double data[2] = {1.0, std::numeric_limits::infinity()}; + output1->mutable_tensor_content()->assign(reinterpret_cast(data), 2 * sizeof(double)); + output1->clear_tensor_shape(); + output1->mutable_tensor_shape()->add_dim()->set_size(2); + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, Order::COLUMN), StatusCode::JSON_SERIALIZATION_ERROR); +} + +TEST_F(TFSMakeJsonFromPredictResponseRawTest, IntOutputsUnaffectedByFiniteCheck) { + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, Order::COLUMN), StatusCode::OK); +} + const char* rawPositiveFirstOrderResponseRow = R"({ "predictions": [ { @@ -791,6 +819,23 @@ TEST_F(KFSMakeJsonFromPredictResponseRawTest, Positive) { })"); } +TEST_F(KFSMakeJsonFromPredictResponseRawTest, NonFiniteInfFloatError) { + float bad[8] = {5.0f, std::numeric_limits::infinity(), -3.0f, 2.5f, 9.0f, 55.5f, -0.5f, -1.5f}; + proto.mutable_raw_output_contents(0)->assign(reinterpret_cast(bad), 8 * sizeof(float)); + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, inferenceHeaderContentLength), StatusCode::JSON_SERIALIZATION_ERROR); +} + +TEST_F(KFSMakeJsonFromPredictResponseRawTest, NonFiniteNaNFloatError) { + float bad[8] = {std::numeric_limits::quiet_NaN(), 10.0f, -3.0f, 2.5f, 9.0f, 55.5f, -0.5f, -1.5f}; + proto.mutable_raw_output_contents(0)->assign(reinterpret_cast(bad), 8 * sizeof(float)); + EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, inferenceHeaderContentLength), StatusCode::JSON_SERIALIZATION_ERROR); +} + +TEST_F(KFSMakeJsonFromPredictResponseRawTest, IntOutputsUnaffectedByFiniteCheck) { + // sanity: integer outputs must still serialize fine (helper returns true for them) + ASSERT_EQ(makeJsonFromPredictResponse(proto, &json, inferenceHeaderContentLength), StatusCode::OK); +} + TEST_F(KFSMakeJsonFromPredictResponseRawTest, EmptyRawOutputContentsError) { proto.mutable_raw_output_contents()->Clear(); EXPECT_EQ(makeJsonFromPredictResponse(proto, &json, inferenceHeaderContentLength), StatusCode::REST_SERIALIZE_VAL_FIELD_INVALID_SIZE);