Skip to content

Commit ee83daa

Browse files
authored
Merge pull request json-c#882 from simonresch/handle-nan-to-int-conversion
Explicitly handle NaN values when converting to integer
2 parents 961c31f + 0c35883 commit ee83daa

File tree

5 files changed

+105
-20
lines changed

5 files changed

+105
-20
lines changed

json_object.c

Lines changed: 56 additions & 8 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;
@@ -756,17 +757,34 @@ int32_t json_object_get_int(const struct json_object *jso)
756757
{
757758
case json_type_int:
758759
/* Make sure we return the correct values for out of range numbers. */
759-
if (cint64 <= INT32_MIN)
760+
if (cint64 < INT32_MIN)
761+
{
762+
errno = ERANGE;
760763
return INT32_MIN;
761-
if (cint64 >= INT32_MAX)
764+
}
765+
if (cint64 > INT32_MAX)
766+
{
767+
errno = ERANGE;
762768
return INT32_MAX;
769+
}
763770
return (int32_t)cint64;
764771
case json_type_double:
765772
cdouble = JC_DOUBLE_C(jso)->c_double;
766-
if (cdouble <= INT32_MIN)
773+
if (cdouble < INT32_MIN)
774+
{
775+
errno = ERANGE;
767776
return INT32_MIN;
768-
if (cdouble >= INT32_MAX)
777+
}
778+
if (cdouble > INT32_MAX)
779+
{
780+
errno = ERANGE;
769781
return INT32_MAX;
782+
}
783+
if (isnan(cdouble))
784+
{
785+
errno = EINVAL;
786+
return INT32_MIN;
787+
}
770788
return (int32_t)cdouble;
771789
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
772790
default: return 0;
@@ -801,6 +819,7 @@ struct json_object *json_object_new_uint64(uint64_t i)
801819
int64_t json_object_get_int64(const struct json_object *jso)
802820
{
803821
int64_t cint;
822+
errno = 0;
804823

805824
if (!jso)
806825
return 0;
@@ -813,19 +832,33 @@ int64_t json_object_get_int64(const struct json_object *jso)
813832
{
814833
case json_object_int_type_int64: return jsoint->cint.c_int64;
815834
case json_object_int_type_uint64:
816-
if (jsoint->cint.c_uint64 >= INT64_MAX)
835+
if (jsoint->cint.c_uint64 > INT64_MAX)
836+
{
837+
errno = ERANGE;
817838
return INT64_MAX;
839+
}
818840
return (int64_t)jsoint->cint.c_uint64;
819841
default: json_abort("invalid cint_type");
820842
}
821843
}
822844
case json_type_double:
823845
// INT64_MAX can't be exactly represented as a double
824846
// so cast to tell the compiler it's ok to round up.
825-
if (JC_DOUBLE_C(jso)->c_double >= (double)INT64_MAX)
847+
if (JC_DOUBLE_C(jso)->c_double > (double)INT64_MAX)
848+
{
849+
errno = ERANGE;
826850
return INT64_MAX;
827-
if (JC_DOUBLE_C(jso)->c_double <= INT64_MIN)
851+
}
852+
if (JC_DOUBLE_C(jso)->c_double < (double)INT64_MIN)
853+
{
854+
errno = ERANGE;
855+
return INT64_MIN;
856+
}
857+
if (isnan(JC_DOUBLE_C(jso)->c_double))
858+
{
859+
errno = EINVAL;
828860
return INT64_MIN;
861+
}
829862
return (int64_t)JC_DOUBLE_C(jso)->c_double;
830863
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
831864
case json_type_string:
@@ -839,6 +872,7 @@ int64_t json_object_get_int64(const struct json_object *jso)
839872
uint64_t json_object_get_uint64(const struct json_object *jso)
840873
{
841874
uint64_t cuint;
875+
errno = 0;
842876

843877
if (!jso)
844878
return 0;
@@ -851,7 +885,10 @@ uint64_t json_object_get_uint64(const struct json_object *jso)
851885
{
852886
case json_object_int_type_int64:
853887
if (jsoint->cint.c_int64 < 0)
888+
{
889+
errno = ERANGE;
854890
return 0;
891+
}
855892
return (uint64_t)jsoint->cint.c_int64;
856893
case json_object_int_type_uint64: return jsoint->cint.c_uint64;
857894
default: json_abort("invalid cint_type");
@@ -860,10 +897,21 @@ uint64_t json_object_get_uint64(const struct json_object *jso)
860897
case json_type_double:
861898
// UINT64_MAX can't be exactly represented as a double
862899
// so cast to tell the compiler it's ok to round up.
863-
if (JC_DOUBLE_C(jso)->c_double >= (double)UINT64_MAX)
900+
if (JC_DOUBLE_C(jso)->c_double > (double)UINT64_MAX)
901+
{
902+
errno = ERANGE;
864903
return UINT64_MAX;
904+
}
865905
if (JC_DOUBLE_C(jso)->c_double < 0)
906+
{
907+
errno = ERANGE;
908+
return 0;
909+
}
910+
if (isnan(JC_DOUBLE_C(jso)->c_double))
911+
{
912+
errno = EINVAL;
866913
return 0;
914+
}
867915
return (uint64_t)JC_DOUBLE_C(jso)->c_double;
868916
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
869917
case json_type_string:

json_object.h

Lines changed: 19 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,12 @@ 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).
747+
* Sets errno to ERANGE if the value exceeds the range of int.
745748
*
746749
* Note that integers are stored internally as 64-bit values.
747750
* If the value of too big or too small to fit into 32-bit, INT32_MAX or
@@ -783,8 +786,12 @@ JSON_EXPORT int json_object_int_inc(struct json_object *obj, int64_t val);
783786
/** Get the int value of a json_object
784787
*
785788
* 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.
789+
* double objects will return their int64 conversion except for NaN values
790+
* which return INT64_MIN and the errno is set to EINVAL.
791+
* Strings will be parsed as an int64. If no conversion exists then 0 is
792+
* returned and errno is set to EINVAL. null is equivalent to 0 (no error values
793+
* set).
794+
* Sets errno to ERANGE if the value exceeds the range of int64.
788795
*
789796
* NOTE: Set errno to 0 directly before a call to this function to determine
790797
* whether or not conversion was successful (it does not clear the value for
@@ -798,8 +805,12 @@ JSON_EXPORT int64_t json_object_get_int64(const struct json_object *obj);
798805
/** Get the uint value of a json_object
799806
*
800807
* 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.
808+
* double objects will return their uint64 conversion except for NaN values
809+
* which return 0 and the errno is set to EINVAL.
810+
* Strings will be parsed as an uint64. If no conversion exists then 0 is
811+
* returned and errno is set to EINVAL. null is equivalent to 0 (no error values
812+
* set).
813+
* Sets errno to ERANGE if the value exceeds the range of uint64.
803814
*
804815
* NOTE: Set errno to 0 directly before a call to this function to determine
805816
* 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: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
#include <assert.h>
55
#include <stdio.h>
66
#include <errno.h>
7+
#include <math.h>
78

89
#include "json.h"
910

11+
#define I32_MAX_S "2147483647"
12+
#define I32_MIN_S "-2147483648"
1013
#define I64_MAX_S "9223372036854775807"
1114
#define I64_OVER "9223372036854775808"
1215
#define I64_MIN_S "-9223372036854775808"
@@ -25,6 +28,7 @@
2528
#define N_I64 json_object_new_int64
2629
#define N_U64 json_object_new_uint64
2730
#define N_STR json_object_new_string
31+
#define N_DBL json_object_new_double
2832

2933
int main(int argc, char **argv)
3034
{
@@ -41,10 +45,15 @@ int main(int argc, char **argv)
4145

4246
CHECK_GET_INT(N_I64(INT32_MAX), INT32_MAX && errno == 0);
4347
CHECK_GET_INT(N_I64(INT32_MIN), INT32_MIN && errno == 0);
44-
CHECK_GET_INT(N_I64(INT64_MAX), INT32_MAX && errno == 0);
45-
CHECK_GET_INT(N_I64(INT64_MIN), INT32_MIN && errno == 0);
46-
CHECK_GET_INT(N_STR(I64_MAX_S), INT32_MAX && errno == 0);
47-
CHECK_GET_INT(N_STR(I64_MIN_S), INT32_MIN && errno == 0);
48+
CHECK_GET_INT(N_I64(INT64_MAX), INT32_MAX && errno == ERANGE);
49+
CHECK_GET_INT(N_I64(INT64_MIN), INT32_MIN && errno == ERANGE);
50+
CHECK_GET_INT(N_STR(I32_MAX_S), INT32_MAX && errno == 0);
51+
CHECK_GET_INT(N_STR(I32_MIN_S), INT32_MIN && errno == 0);
52+
CHECK_GET_INT(N_STR(I64_MAX_S), INT32_MAX && errno == ERANGE);
53+
CHECK_GET_INT(N_STR(I64_MIN_S), INT32_MIN && errno == ERANGE);
54+
CHECK_GET_INT(N_DBL(INFINITY), INT32_MAX && errno == ERANGE);
55+
CHECK_GET_INT(N_DBL(-INFINITY), INT32_MIN && errno == ERANGE);
56+
CHECK_GET_INT(N_DBL(NAN), INT32_MIN && errno == EINVAL);
4857
printf("INT GET PASSED\n");
4958

5059
CHECK_GET_INT64(N_I64(INT64_MAX), INT64_MAX && errno == 0);
@@ -53,11 +62,17 @@ int main(int argc, char **argv)
5362
CHECK_GET_INT64(N_STR(I64_MIN_S), INT64_MIN && errno == 0);
5463
CHECK_GET_INT64(N_STR(I64_OVER), INT64_MAX && errno == ERANGE);
5564
CHECK_GET_INT64(N_STR(I64_UNDER), INT64_MIN && errno == ERANGE);
65+
CHECK_GET_INT64(N_DBL(INFINITY), INT64_MAX && errno == ERANGE);
66+
CHECK_GET_INT64(N_DBL(-INFINITY), INT64_MIN && errno == ERANGE);
67+
CHECK_GET_INT64(N_DBL(NAN), INT64_MIN && errno == EINVAL);
5668
printf("INT64 GET PASSED\n");
5769

5870
CHECK_GET_UINT64(N_U64(UINT64_MAX), UINT64_MAX && errno == 0);
5971
CHECK_GET_UINT64(N_U64(-1), UINT64_MAX && errno == 0);
6072
CHECK_GET_UINT64(N_STR(U64_OUT_S), UINT64_MAX && errno == ERANGE);
73+
CHECK_GET_UINT64(N_DBL(INFINITY), UINT64_MAX && errno == ERANGE);
74+
CHECK_GET_UINT64(N_DBL(-INFINITY), 0 && errno == ERANGE);
75+
CHECK_GET_UINT64(N_DBL(NAN), 0 && errno == EINVAL);
6176
printf("UINT64 GET PASSED\n");
6277

6378
printf("PASSED\n");

0 commit comments

Comments
 (0)