json: improve supported numbers

Use 64bit signed integers and fix double value conversion.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/525>
This commit is contained in:
Igor V. Kovalenko 2021-03-27 10:07:22 +03:00 committed by PulseAudio Marge Bot
parent 02027518af
commit 1df4a311d4
3 changed files with 58 additions and 61 deletions

View file

@ -35,7 +35,7 @@ struct pa_json_object {
pa_json_type type; pa_json_type type;
union { union {
int int_value; int64_t int_value;
double double_value; double double_value;
bool bool_value; bool bool_value;
char *string_value; char *string_value;
@ -194,33 +194,22 @@ error:
} }
static const char* parse_number(const char *str, pa_json_object *obj) { static const char* parse_number(const char *str, pa_json_object *obj) {
bool negative = false, has_fraction = false, has_exponent = false, valid = false; bool has_fraction = false, has_exponent = false, valid = false;
unsigned int integer = 0; char *candidate = NULL;
unsigned int fraction = 0; const char *s = str;
unsigned int fraction_digits = 0;
int exponent = 0;
if (*str == '-') { if (*s == '-')
negative = true; s++;
str++;
}
if (*str == '0') { if (*s == '0') {
valid = true; valid = true;
str++; s++;
goto fraction; goto fraction;
} }
while (is_digit(*str)) { while (is_digit(*s)) {
valid = true; valid = true;
s++;
if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
pa_log("Integer overflow while parsing number");
goto error;
}
integer = (integer * 10) + (*str - '0');
str++;
} }
fraction: fraction:
@ -230,22 +219,14 @@ fraction:
goto error; goto error;
} }
if (*str == '.') { if (*s == '.') {
has_fraction = true; has_fraction = true;
str++; s++;
valid = false; valid = false;
while (is_digit(*str)) { while (is_digit(*s)) {
valid = true; valid = true;
s++;
if (fraction > (UINT_MAX / 10)) {
pa_log("Integer overflow while parsing fractional part of number");
goto error;
}
fraction = (fraction * 10) + (*str - '0');
fraction_digits++;
str++;
} }
if (!valid) { if (!valid) {
@ -254,52 +235,50 @@ fraction:
} }
} }
if (*str == 'e' || *str == 'E') { if (*s == 'e' || *s == 'E') {
bool exponent_negative = false;
has_exponent = true; has_exponent = true;
str++; s++;
valid = false; valid = false;
if (*str == '-') { if (*s == '-' || *s == '+')
exponent_negative = true; s++;
str++;
} else if (*str == '+')
str++;
while (is_digit(*str)) { while (is_digit(*s)) {
valid = true; valid = true;
s++;
if (exponent > (INT_MAX / 10)) {
pa_log("Integer overflow while parsing exponent part of number");
goto error;
}
exponent = (exponent * 10) + (*str - '0');
str++;
} }
if (!valid) { if (!valid) {
pa_log("No digit in exponent while parsing fraction"); pa_log("No digit in exponent while parsing fraction");
goto error; goto error;
} }
if (exponent_negative)
exponent *= -1;
} }
/* Number format looks good, now try to extract the value.
* Here 's' points just after the string which will be consumed. */
candidate = pa_xstrndup(str, s - str);
if (has_fraction || has_exponent) { if (has_fraction || has_exponent) {
if (pa_atod(candidate, &obj->double_value) < 0) {
pa_log("Cannot convert string '%s' to double value", str);
goto error;
}
obj->type = PA_JSON_TYPE_DOUBLE; obj->type = PA_JSON_TYPE_DOUBLE;
obj->double_value =
(negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent);
} else { } else {
if (pa_atoi64(candidate, &obj->int_value) < 0) {
pa_log("Cannot convert string '%s' to int64_t value", str);
goto error;
}
obj->type = PA_JSON_TYPE_INT; obj->type = PA_JSON_TYPE_INT;
obj->int_value = (negative ? -1 : 1) * integer;
} }
return str; pa_xfree(candidate);
return s;
error: error:
pa_xfree(candidate);
return NULL; return NULL;
} }
@ -522,7 +501,7 @@ void pa_json_object_free(pa_json_object *obj) {
pa_xfree(obj); pa_xfree(obj);
} }
int pa_json_object_get_int(const pa_json_object *o) { int64_t pa_json_object_get_int(const pa_json_object *o) {
pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT); pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
return o->int_value; return o->int_value;
} }

View file

@ -18,6 +18,7 @@
***/ ***/
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001) #define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
@ -40,7 +41,7 @@ void pa_json_object_free(pa_json_object *obj);
/* All pointer members that are returned are valid while the corresponding object is valid */ /* All pointer members that are returned are valid while the corresponding object is valid */
int pa_json_object_get_int(const pa_json_object *o); int64_t pa_json_object_get_int(const pa_json_object *o);
double pa_json_object_get_double(const pa_json_object *o); double pa_json_object_get_double(const pa_json_object *o);
bool pa_json_object_get_bool(const pa_json_object *o); bool pa_json_object_get_bool(const pa_json_object *o);
const char* pa_json_object_get_string(const pa_json_object *o); const char* pa_json_object_get_string(const pa_json_object *o);

View file

@ -54,7 +54,8 @@ START_TEST(int_test) {
pa_json_object *o; pa_json_object *o;
unsigned int i; unsigned int i;
const char *ints_parse[] = { "1", "-1", "1234", "0" }; const char *ints_parse[] = { "1", "-1", "1234", "0" };
const int ints_compare[] = { 1, -1, 1234, 0 }; const int64_t ints_compare[] = { 1, -1, 1234, 0 };
char *ulong_max_str;
for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) { for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) {
o = pa_json_parse(ints_parse[i]); o = pa_json_parse(ints_parse[i]);
@ -65,12 +66,19 @@ START_TEST(int_test) {
pa_json_object_free(o); pa_json_object_free(o);
} }
/* test that parser would fail on integer overflow */
ulong_max_str = pa_sprintf_malloc("%"PRIu64, ULONG_MAX);
o = pa_json_parse(ulong_max_str);
fail_unless(o == NULL);
pa_xfree(ulong_max_str);
} }
END_TEST END_TEST
START_TEST(double_test) { START_TEST(double_test) {
pa_json_object *o; pa_json_object *o;
unsigned int i; unsigned int i;
char *very_large_double_str;
const char *doubles_parse[] = { const char *doubles_parse[] = {
"1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2", "1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2",
}; };
@ -87,6 +95,12 @@ START_TEST(double_test) {
pa_json_object_free(o); pa_json_object_free(o);
} }
/* test that parser would fail on double exponent overflow */
very_large_double_str = pa_sprintf_malloc("%"PRIu64"e%"PRIu64, ULONG_MAX, ULONG_MAX);
o = pa_json_parse(very_large_double_str);
fail_unless(o == NULL);
pa_xfree(very_large_double_str);
} }
END_TEST END_TEST
@ -234,8 +248,11 @@ START_TEST(bad_test) {
const char *bad_parse[] = { const char *bad_parse[] = {
"\"" /* Quote not closed */, "\"" /* Quote not closed */,
"123456789012345678901234567890" /* Overflow */, "123456789012345678901234567890" /* Overflow */,
#if 0 /* TODO: check rounding the value is OK */
"0.123456789012345678901234567890" /* Overflow */, "0.123456789012345678901234567890" /* Overflow */,
#endif
"1e123456789012345678901234567890" /* Overflow */, "1e123456789012345678901234567890" /* Overflow */,
"1e-10000" /* Underflow */,
"1e" /* Bad number string */, "1e" /* Bad number string */,
"1." /* Bad number string */, "1." /* Bad number string */,
"1.e3" /* Bad number string */, "1.e3" /* Bad number string */,