Skip to content

Commit 8c13801

Browse files
committed
Explicitly handle NaN values when converting to int
Json objects of type double with the value NaN could cause undefined behavior when casting double to int in `json_object_get_int`.
1 parent 565f181 commit 8c13801

File tree

5 files changed

+51
-8
lines changed

5 files changed

+51
-8
lines changed

json_object.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ int32_t json_object_get_int(const struct json_object *jso)
721721
int64_t cint64 = 0;
722722
double cdouble;
723723
enum json_type o_type;
724+
errno = 0;
724725

725726
if (!jso)
726727
return 0;
@@ -767,6 +768,11 @@ int32_t json_object_get_int(const struct json_object *jso)
767768
return INT32_MIN;
768769
if (cdouble >= INT32_MAX)
769770
return INT32_MAX;
771+
if (isnan(cdouble))
772+
{
773+
errno = EINVAL;
774+
return INT32_MIN;
775+
}
770776
return (int32_t)cdouble;
771777
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
772778
default: return 0;
@@ -801,6 +807,7 @@ struct json_object *json_object_new_uint64(uint64_t i)
801807
int64_t json_object_get_int64(const struct json_object *jso)
802808
{
803809
int64_t cint;
810+
errno = 0;
804811

805812
if (!jso)
806813
return 0;
@@ -826,6 +833,11 @@ int64_t json_object_get_int64(const struct json_object *jso)
826833
return INT64_MAX;
827834
if (JC_DOUBLE_C(jso)->c_double <= INT64_MIN)
828835
return INT64_MIN;
836+
if (isnan(JC_DOUBLE_C(jso)->c_double))
837+
{
838+
errno = EINVAL;
839+
return INT64_MIN;
840+
}
829841
return (int64_t)JC_DOUBLE_C(jso)->c_double;
830842
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
831843
case json_type_string:
@@ -839,6 +851,7 @@ int64_t json_object_get_int64(const struct json_object *jso)
839851
uint64_t json_object_get_uint64(const struct json_object *jso)
840852
{
841853
uint64_t cuint;
854+
errno = 0;
842855

843856
if (!jso)
844857
return 0;
@@ -864,6 +877,11 @@ uint64_t json_object_get_uint64(const struct json_object *jso)
864877
return UINT64_MAX;
865878
if (JC_DOUBLE_C(jso)->c_double < 0)
866879
return 0;
880+
if (isnan(JC_DOUBLE_C(jso)->c_double))
881+
{
882+
errno = EINVAL;
883+
return 0;
884+
}
867885
return (uint64_t)JC_DOUBLE_C(jso)->c_double;
868886
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
869887
case json_type_string:

json_object.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ JSON_EXPORT struct json_object *json_object_new_boolean(json_bool b);
693693
* The type is coerced to a json_bool if the passed object is not a json_bool.
694694
* integer and double objects will return 0 if there value is zero
695695
* or 1 otherwise. If the passed object is a string it will return
696-
* 1 if it has a non zero length.
696+
* 1 if it has a non zero length.
697697
* If any other object type is passed 0 will be returned, even non-empty
698698
* json_type_array and json_type_object objects.
699699
*
@@ -739,9 +739,11 @@ JSON_EXPORT struct json_object *json_object_new_uint64(uint64_t i);
739739
/** Get the int value of a json_object
740740
*
741741
* The type is coerced to a int if the passed object is not a int.
742-
* double objects will return their integer conversion. Strings will be
743-
* parsed as an integer. If no conversion exists then 0 is returned
744-
* and errno is set to EINVAL. null is equivalent to 0 (no error values set)
742+
* double objects will return their integer conversion except for NaN values
743+
* which return INT32_MIN and the errno is set to EINVAL.
744+
* Strings will be parsed as an integer. If no conversion exists then 0 is
745+
* returned and errno is set to EINVAL. null is equivalent to 0 (no error values
746+
* set)
745747
*
746748
* Note that integers are stored internally as 64-bit values.
747749
* 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);
783785
/** Get the int value of a json_object
784786
*
785787
* The type is coerced to a int64 if the passed object is not a int64.
786-
* double objects will return their int64 conversion. Strings will be
787-
* parsed as an int64. If no conversion exists then 0 is returned.
788+
* double objects will return their int64 conversion except for NaN values
789+
* which return INT64_MIN and the errno is set to EINVAL.
790+
* Strings will be parsed as an int64. If no conversion exists then 0 is
791+
* returned and errno is set to EINVAL. null is equivalent to 0 (no error values
792+
* set)
788793
*
789794
* NOTE: Set errno to 0 directly before a call to this function to determine
790795
* 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);
798803
/** Get the uint value of a json_object
799804
*
800805
* The type is coerced to a uint64 if the passed object is not a uint64.
801-
* double objects will return their uint64 conversion. Strings will be
802-
* parsed as an uint64. If no conversion exists then 0 is returned.
806+
* double objects will return their uint64 conversion except for NaN values
807+
* which return 0 and the errno is set to EINVAL.
808+
* Strings will be parsed as an uint64. If no conversion exists then 0 is
809+
* returned and errno is set to EINVAL. null is equivalent to 0 (no error values
810+
* set)
803811
*
804812
* NOTE: Set errno to 0 directly before a call to this function to determine
805813
* whether or not conversion was successful (it does not clear the value for

tests/test_cast.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ int main(int argc, char **argv)
3636
\"array_with_zero\": [ 0 ],\n\
3737
\"empty_object\": {},\n\
3838
\"nonempty_object\": { \"a\": 123 },\n\
39+
\"nan\": NaN,\n\
3940
}";
4041
/* Note: 2147483649 = INT_MAX + 2 */
4142
/* Note: 9223372036854775809 = INT64_MAX + 2 */
@@ -62,6 +63,7 @@ int main(int argc, char **argv)
6263
getit(new_obj, "array_with_zero");
6364
getit(new_obj, "empty_object");
6465
getit(new_obj, "nonempty_object");
66+
getit(new_obj, "nan");
6567

6668
// Now check the behaviour of the json_object_is_type() function.
6769
printf("\n================================\n");
@@ -75,6 +77,7 @@ int main(int argc, char **argv)
7577
checktype(new_obj, "int64_number");
7678
checktype(new_obj, "negative_number");
7779
checktype(new_obj, "a_null");
80+
checktype(new_obj, "nan");
7881

7982
json_object_put(new_obj);
8083

tests/test_cast.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Parsed input: {
1212
"array_with_zero": [ 0 ],
1313
"empty_object": {},
1414
"nonempty_object": { "a": 123 },
15+
"nan": NaN,
1516
}
1617
Result is not NULL
1718
new_obj.string_of_digits json_object_get_type()=string
@@ -92,6 +93,12 @@ new_obj.nonempty_object json_object_get_int64()=0
9293
new_obj.nonempty_object json_object_get_uint64()=0
9394
new_obj.nonempty_object json_object_get_boolean()=0
9495
new_obj.nonempty_object json_object_get_double()=0.000000
96+
new_obj.nan json_object_get_type()=double
97+
new_obj.nan json_object_get_int()=-2147483648
98+
new_obj.nan json_object_get_int64()=-9223372036854775808
99+
new_obj.nan json_object_get_uint64()=0
100+
new_obj.nan json_object_get_boolean()=1
101+
new_obj.nan json_object_get_double()=nan
95102

96103
================================
97104
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
104111
new_obj.int64_number : 0,0,0,1,0,0,0
105112
new_obj.negative_number : 0,0,0,1,0,0,0
106113
new_obj.a_null : 1,0,0,0,0,0,0
114+
new_obj.nan : 0,0,1,0,0,0,0

tests/test_int_get.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <assert.h>
55
#include <stdio.h>
66
#include <errno.h>
7+
#include <math.h>
78

89
#include "json.h"
910

@@ -25,6 +26,7 @@
2526
#define N_I64 json_object_new_int64
2627
#define N_U64 json_object_new_uint64
2728
#define N_STR json_object_new_string
29+
#define N_DBL json_object_new_double
2830

2931
int main(int argc, char **argv)
3032
{
@@ -45,6 +47,8 @@ int main(int argc, char **argv)
4547
CHECK_GET_INT(N_I64(INT64_MIN), INT32_MIN && errno == 0);
4648
CHECK_GET_INT(N_STR(I64_MAX_S), INT32_MAX && errno == 0);
4749
CHECK_GET_INT(N_STR(I64_MIN_S), INT32_MIN && errno == 0);
50+
CHECK_GET_INT(N_DBL(NAN), INT32_MIN && errno == EINVAL);
51+
4852
printf("INT GET PASSED\n");
4953

5054
CHECK_GET_INT64(N_I64(INT64_MAX), INT64_MAX && errno == 0);
@@ -53,11 +57,13 @@ int main(int argc, char **argv)
5357
CHECK_GET_INT64(N_STR(I64_MIN_S), INT64_MIN && errno == 0);
5458
CHECK_GET_INT64(N_STR(I64_OVER), INT64_MAX && errno == ERANGE);
5559
CHECK_GET_INT64(N_STR(I64_UNDER), INT64_MIN && errno == ERANGE);
60+
CHECK_GET_INT64(N_DBL(NAN), INT64_MIN && errno == EINVAL);
5661
printf("INT64 GET PASSED\n");
5762

5863
CHECK_GET_UINT64(N_U64(UINT64_MAX), UINT64_MAX && errno == 0);
5964
CHECK_GET_UINT64(N_U64(-1), UINT64_MAX && errno == 0);
6065
CHECK_GET_UINT64(N_STR(U64_OUT_S), UINT64_MAX && errno == ERANGE);
66+
CHECK_GET_UINT64(N_DBL(NAN), 0 && errno == EINVAL);
6167
printf("UINT64 GET PASSED\n");
6268

6369
printf("PASSED\n");

0 commit comments

Comments
 (0)