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;
union {
int int_value;
int64_t int_value;
double double_value;
bool bool_value;
char *string_value;
@ -194,33 +194,22 @@ error:
}
static const char* parse_number(const char *str, pa_json_object *obj) {
bool negative = false, has_fraction = false, has_exponent = false, valid = false;
unsigned int integer = 0;
unsigned int fraction = 0;
unsigned int fraction_digits = 0;
int exponent = 0;
bool has_fraction = false, has_exponent = false, valid = false;
char *candidate = NULL;
const char *s = str;
if (*str == '-') {
negative = true;
str++;
}
if (*s == '-')
s++;
if (*str == '0') {
if (*s == '0') {
valid = true;
str++;
s++;
goto fraction;
}
while (is_digit(*str)) {
while (is_digit(*s)) {
valid = true;
if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
pa_log("Integer overflow while parsing number");
goto error;
}
integer = (integer * 10) + (*str - '0');
str++;
s++;
}
fraction:
@ -230,22 +219,14 @@ fraction:
goto error;
}
if (*str == '.') {
if (*s == '.') {
has_fraction = true;
str++;
s++;
valid = false;
while (is_digit(*str)) {
while (is_digit(*s)) {
valid = true;
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++;
s++;
}
if (!valid) {
@ -254,52 +235,50 @@ fraction:
}
}
if (*str == 'e' || *str == 'E') {
bool exponent_negative = false;
if (*s == 'e' || *s == 'E') {
has_exponent = true;
str++;
s++;
valid = false;
if (*str == '-') {
exponent_negative = true;
str++;
} else if (*str == '+')
str++;
if (*s == '-' || *s == '+')
s++;
while (is_digit(*str)) {
while (is_digit(*s)) {
valid = true;
if (exponent > (INT_MAX / 10)) {
pa_log("Integer overflow while parsing exponent part of number");
goto error;
}
exponent = (exponent * 10) + (*str - '0');
str++;
s++;
}
if (!valid) {
pa_log("No digit in exponent while parsing fraction");
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 (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->double_value =
(negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent);
} 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->int_value = (negative ? -1 : 1) * integer;
}
return str;
pa_xfree(candidate);
return s;
error:
pa_xfree(candidate);
return NULL;
}
@ -522,7 +501,7 @@ void pa_json_object_free(pa_json_object *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);
return o->int_value;
}

View file

@ -18,6 +18,7 @@
***/
#include <stdbool.h>
#include <stdint.h>
#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 */
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);
bool pa_json_object_get_bool(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;
unsigned int i;
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++) {
o = pa_json_parse(ints_parse[i]);
@ -65,12 +66,19 @@ START_TEST(int_test) {
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
START_TEST(double_test) {
pa_json_object *o;
unsigned int i;
char *very_large_double_str;
const char *doubles_parse[] = {
"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);
}
/* 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
@ -234,8 +248,11 @@ START_TEST(bad_test) {
const char *bad_parse[] = {
"\"" /* Quote not closed */,
"123456789012345678901234567890" /* Overflow */,
#if 0 /* TODO: check rounding the value is OK */
"0.123456789012345678901234567890" /* Overflow */,
#endif
"1e123456789012345678901234567890" /* Overflow */,
"1e-10000" /* Underflow */,
"1e" /* Bad number string */,
"1." /* Bad number string */,
"1.e3" /* Bad number string */,