]> git.ipfire.org Git - thirdparty/json-c.git/commitdiff
Explicitly handle NaN values when converting to int
authorSimon Resch <simon.resch@code-intelligence.de>
Thu, 14 Nov 2024 12:47:18 +0000 (13:47 +0100)
committerSimon Resch <simon.resch@code-intelligence.de>
Wed, 27 Nov 2024 06:17:20 +0000 (07:17 +0100)
Json objects of type double with the value NaN could cause undefined
behavior when casting double to int in `json_object_get_int`.

json_object.c
json_object.h
tests/test_cast.c
tests/test_cast.expected
tests/test_int_get.c

index cc4c1e22bedb6d499baa69d2d964cefa0637f6f2..995b0eda9bb86bf5b32bc36d1e2a7e9b3f65bbdf 100644 (file)
@@ -721,6 +721,7 @@ int32_t json_object_get_int(const struct json_object *jso)
        int64_t cint64 = 0;
        double cdouble;
        enum json_type o_type;
+       errno = 0;
 
        if (!jso)
                return 0;
@@ -767,6 +768,11 @@ int32_t json_object_get_int(const struct json_object *jso)
                        return INT32_MIN;
                if (cdouble >= INT32_MAX)
                        return INT32_MAX;
+               if (isnan(cdouble))
+               {
+                       errno = EINVAL;
+                       return INT32_MIN;
+               }
                return (int32_t)cdouble;
        case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
        default: return 0;
@@ -801,6 +807,7 @@ struct json_object *json_object_new_uint64(uint64_t i)
 int64_t json_object_get_int64(const struct json_object *jso)
 {
        int64_t cint;
+       errno = 0;
 
        if (!jso)
                return 0;
@@ -826,6 +833,11 @@ int64_t json_object_get_int64(const struct json_object *jso)
                        return INT64_MAX;
                if (JC_DOUBLE_C(jso)->c_double <= INT64_MIN)
                        return INT64_MIN;
+               if (isnan(JC_DOUBLE_C(jso)->c_double))
+               {
+                       errno = EINVAL;
+                       return INT64_MIN;
+               }
                return (int64_t)JC_DOUBLE_C(jso)->c_double;
        case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
        case json_type_string:
@@ -839,6 +851,7 @@ int64_t json_object_get_int64(const struct json_object *jso)
 uint64_t json_object_get_uint64(const struct json_object *jso)
 {
        uint64_t cuint;
+       errno = 0;
 
        if (!jso)
                return 0;
@@ -864,6 +877,11 @@ uint64_t json_object_get_uint64(const struct json_object *jso)
                        return UINT64_MAX;
                if (JC_DOUBLE_C(jso)->c_double < 0)
                        return 0;
+               if (isnan(JC_DOUBLE_C(jso)->c_double))
+               {
+                       errno = EINVAL;
+                       return 0;
+               }
                return (uint64_t)JC_DOUBLE_C(jso)->c_double;
        case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
        case json_type_string:
index b767b0213eea48f5f9982af75cd2862528ecc6ca..b6f3dc70cf59bd6077483075c58c734c52ea94aa 100644 (file)
@@ -693,7 +693,7 @@ JSON_EXPORT struct json_object *json_object_new_boolean(json_bool b);
  * The type is coerced to a json_bool if the passed object is not a json_bool.
  * integer and double objects will return 0 if there value is zero
  * or 1 otherwise. If the passed object is a string it will return
- * 1 if it has a non zero length. 
+ * 1 if it has a non zero length.
  * If any other object type is passed 0 will be returned, even non-empty
  *  json_type_array and json_type_object objects.
  *
@@ -739,9 +739,11 @@ JSON_EXPORT struct json_object *json_object_new_uint64(uint64_t i);
 /** Get the int value of a json_object
  *
  * The type is coerced to a int if the passed object is not a int.
- * double objects will return their integer conversion. Strings will be
- * parsed as an integer. If no conversion exists then 0 is returned
- * and errno is set to EINVAL. null is equivalent to 0 (no error values set)
+ * double objects will return their integer conversion except for NaN values
+ * which return INT32_MIN and the errno is set to EINVAL.
+ * Strings will be parsed as an integer. If no conversion exists then 0 is
+ * returned and errno is set to EINVAL. null is equivalent to 0 (no error values
+ * set)
  *
  * Note that integers are stored internally as 64-bit values.
  * If the value of too big or too small to fit into 32-bit, INT32_MAX or
@@ -783,8 +785,11 @@ JSON_EXPORT int json_object_int_inc(struct json_object *obj, int64_t val);
 /** Get the int value of a json_object
  *
  * The type is coerced to a int64 if the passed object is not a int64.
- * double objects will return their int64 conversion. Strings will be
- * parsed as an int64. If no conversion exists then 0 is returned.
+ * double objects will return their int64 conversion except for NaN values
+ * which return INT64_MIN and the errno is set to EINVAL.
+ * Strings will be parsed as an int64. If no conversion exists then 0 is
+ * returned and errno is set to EINVAL. null is equivalent to 0 (no error values
+ * set)
  *
  * NOTE: Set errno to 0 directly before a call to this function to determine
  * whether or not conversion was successful (it does not clear the value for
@@ -798,8 +803,11 @@ JSON_EXPORT int64_t json_object_get_int64(const struct json_object *obj);
 /** Get the uint value of a json_object
  *
  * The type is coerced to a uint64 if the passed object is not a uint64.
- * double objects will return their uint64 conversion. Strings will be
- * parsed as an uint64. If no conversion exists then 0 is returned.
+ * double objects will return their uint64 conversion except for NaN values
+ * which return 0 and the errno is set to EINVAL.
+ * Strings will be parsed as an uint64. If no conversion exists then 0 is
+ * returned and errno is set to EINVAL. null is equivalent to 0 (no error values
+ * set)
  *
  * NOTE: Set errno to 0 directly before a call to this function to determine
  * whether or not conversion was successful (it does not clear the value for
index 02e19eaa11b65e397ea92e7eb67ef47d4690cd88..32aa92e34ca24ea16dde1fc488e8771593c46042 100644 (file)
@@ -36,6 +36,7 @@ int main(int argc, char **argv)
                \"array_with_zero\": [ 0 ],\n\
                \"empty_object\": {},\n\
                \"nonempty_object\": { \"a\": 123 },\n\
+               \"nan\": NaN,\n\
        }";
        /* Note: 2147483649 = INT_MAX + 2 */
        /* Note: 9223372036854775809 = INT64_MAX + 2 */
@@ -62,6 +63,7 @@ int main(int argc, char **argv)
        getit(new_obj, "array_with_zero");
        getit(new_obj, "empty_object");
        getit(new_obj, "nonempty_object");
+       getit(new_obj, "nan");
 
        // Now check the behaviour of the json_object_is_type() function.
        printf("\n================================\n");
@@ -75,6 +77,7 @@ int main(int argc, char **argv)
        checktype(new_obj, "int64_number");
        checktype(new_obj, "negative_number");
        checktype(new_obj, "a_null");
+       checktype(new_obj, "nan");
 
        json_object_put(new_obj);
 
index 6a19de9a865ca74d7eb4593095b681f781356399..3ec61071b7fc417955adbb6ab84d9a08093f10d9 100644 (file)
@@ -12,6 +12,7 @@ Parsed input: {
                "array_with_zero": [ 0 ],
                "empty_object": {},
                "nonempty_object": { "a": 123 },
+               "nan": NaN,
        }
 Result is not NULL
 new_obj.string_of_digits json_object_get_type()=string
@@ -92,6 +93,12 @@ new_obj.nonempty_object json_object_get_int64()=0
 new_obj.nonempty_object json_object_get_uint64()=0
 new_obj.nonempty_object json_object_get_boolean()=0
 new_obj.nonempty_object json_object_get_double()=0.000000
+new_obj.nan json_object_get_type()=double
+new_obj.nan json_object_get_int()=-2147483648
+new_obj.nan json_object_get_int64()=-9223372036854775808
+new_obj.nan json_object_get_uint64()=0
+new_obj.nan json_object_get_boolean()=1
+new_obj.nan json_object_get_double()=nan
 
 ================================
 json_object_is_type: null,boolean,double,int,object,array,string
@@ -104,3 +111,4 @@ new_obj.boolean_false     : 0,1,0,0,0,0,0
 new_obj.int64_number      : 0,0,0,1,0,0,0
 new_obj.negative_number   : 0,0,0,1,0,0,0
 new_obj.a_null            : 1,0,0,0,0,0,0
+new_obj.nan               : 0,0,1,0,0,0,0
index be303641f70ec12414e4c38b504e7c8d9c81a0a6..2da08c6306cb6697b0c861aacf944295be594e62 100644 (file)
@@ -4,6 +4,7 @@
 #include <assert.h>
 #include <stdio.h>
 #include <errno.h>
+#include <math.h>
 
 #include "json.h"
 
@@ -25,6 +26,7 @@
 #define N_I64     json_object_new_int64
 #define N_U64     json_object_new_uint64
 #define N_STR     json_object_new_string
+#define N_DBL     json_object_new_double
 
 int main(int argc, char **argv)
 {
@@ -45,6 +47,8 @@ int main(int argc, char **argv)
        CHECK_GET_INT(N_I64(INT64_MIN), INT32_MIN && errno == 0);
        CHECK_GET_INT(N_STR(I64_MAX_S), INT32_MAX && errno == 0);
        CHECK_GET_INT(N_STR(I64_MIN_S), INT32_MIN && errno == 0);
+       CHECK_GET_INT(N_DBL(NAN),       INT32_MIN && errno == EINVAL);
+
        printf("INT GET PASSED\n");
 
        CHECK_GET_INT64(N_I64(INT64_MAX), INT64_MAX && errno == 0);
@@ -53,11 +57,13 @@ int main(int argc, char **argv)
        CHECK_GET_INT64(N_STR(I64_MIN_S), INT64_MIN && errno == 0);
        CHECK_GET_INT64(N_STR(I64_OVER),  INT64_MAX && errno == ERANGE);
        CHECK_GET_INT64(N_STR(I64_UNDER), INT64_MIN && errno == ERANGE);
+       CHECK_GET_INT64(N_DBL(NAN),       INT64_MIN && errno == EINVAL);
        printf("INT64 GET PASSED\n");
 
        CHECK_GET_UINT64(N_U64(UINT64_MAX), UINT64_MAX && errno == 0);
        CHECK_GET_UINT64(N_U64(-1),         UINT64_MAX && errno == 0);
        CHECK_GET_UINT64(N_STR(U64_OUT_S),  UINT64_MAX && errno == ERANGE);
+       CHECK_GET_UINT64(N_DBL(NAN),        0          && errno == EINVAL);
        printf("UINT64 GET PASSED\n");
 
        printf("PASSED\n");