From 7c03a10584da2729c97980e87850c4e6cb71b1a5 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 13 Jul 2021 13:12:13 +0200 Subject: [PATCH 1/3] Fix #79908: json_decode decodes negative zero as positive zero While the JSON standard knows only numbers (i.e. it doesn't distinguish between int and float), and does not mention negative zero at all, PHP makes that distinction, so a negative zero should be parsed as such, even if the number is not otherwise designated as a float. --- ext/json/json_scanner.re | 7 ++++--- ext/json/tests/bug79908.phpt | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 ext/json/tests/bug79908.phpt diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index a64d09bfc3dae..5db1969ccc5fe 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -170,8 +170,9 @@ std: return PHP_JSON_T_FALSE; } INT { - zend_bool bigint = 0, negative = s->token[0] == '-'; + zend_bool bigint = 0, negative = s->token[0] == '-', neg_zero; size_t digits = (size_t) (s->cursor - s->token - negative); + neg_zero = negative && digits == 1 && s->token[1] == '0'; if (digits >= PHP_JSON_INT_MAX_LENGTH) { if (digits == PHP_JSON_INT_MAX_LENGTH) { int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH); @@ -182,10 +183,10 @@ std: bigint = 1; } } - if (!bigint) { + if (EXPECTED(!neg_zero) && !bigint) { ZVAL_LONG(&s->value, ZEND_STRTOL((char *) s->token, NULL, 10)); return PHP_JSON_T_INT; - } else if (s->options & PHP_JSON_BIGINT_AS_STRING) { + } else if (EXPECTED(!neg_zero) && s->options & PHP_JSON_BIGINT_AS_STRING) { ZVAL_STRINGL(&s->value, (char *) s->token, s->cursor - s->token); return PHP_JSON_T_STRING; } else { diff --git a/ext/json/tests/bug79908.phpt b/ext/json/tests/bug79908.phpt new file mode 100644 index 0000000000000..4607408c8b42a --- /dev/null +++ b/ext/json/tests/bug79908.phpt @@ -0,0 +1,12 @@ +--TEST-- +Bug #79908 (json_decode decodes negative zero as positive zero) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +float(-0) From 456c796219a90cdc09d808fd0c0058d4c0d3caa3 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 13 Jul 2021 14:30:59 +0200 Subject: [PATCH 2/3] Revert "Fix #79908: json_decode decodes negative zero as positive zero" This reverts commit 7c03a10584da2729c97980e87850c4e6cb71b1a5. --- ext/json/json_scanner.re | 7 +++---- ext/json/tests/bug79908.phpt | 12 ------------ 2 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 ext/json/tests/bug79908.phpt diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 5db1969ccc5fe..a64d09bfc3dae 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -170,9 +170,8 @@ std: return PHP_JSON_T_FALSE; } INT { - zend_bool bigint = 0, negative = s->token[0] == '-', neg_zero; + zend_bool bigint = 0, negative = s->token[0] == '-'; size_t digits = (size_t) (s->cursor - s->token - negative); - neg_zero = negative && digits == 1 && s->token[1] == '0'; if (digits >= PHP_JSON_INT_MAX_LENGTH) { if (digits == PHP_JSON_INT_MAX_LENGTH) { int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH); @@ -183,10 +182,10 @@ std: bigint = 1; } } - if (EXPECTED(!neg_zero) && !bigint) { + if (!bigint) { ZVAL_LONG(&s->value, ZEND_STRTOL((char *) s->token, NULL, 10)); return PHP_JSON_T_INT; - } else if (EXPECTED(!neg_zero) && s->options & PHP_JSON_BIGINT_AS_STRING) { + } else if (s->options & PHP_JSON_BIGINT_AS_STRING) { ZVAL_STRINGL(&s->value, (char *) s->token, s->cursor - s->token); return PHP_JSON_T_STRING; } else { diff --git a/ext/json/tests/bug79908.phpt b/ext/json/tests/bug79908.phpt deleted file mode 100644 index 4607408c8b42a..0000000000000 --- a/ext/json/tests/bug79908.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Bug #79908 (json_decode decodes negative zero as positive zero) ---SKIPIF-- - ---FILE-- - ---EXPECT-- -float(-0) From b3ab9dce817e5fe014f8c43fde12970ffeb6476d Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 13 Jul 2021 14:42:42 +0200 Subject: [PATCH 3/3] Fix #79908: json_encode encodes negative zero as int Encoding a negative zero as `-0` is likely to loose the sign when decoding (at least it does with `json_decode()`). Therefore, we encode it as if `JSON_PRESERVE_ZERO_FRACTION` was specified, i.e. as `-0.0`. --- ext/json/json_encoder.c | 3 ++- ext/json/tests/bug79908.phpt | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 ext/json/tests/bug79908.phpt diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index c0fcbe328202a..d56cc24695130 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -104,7 +104,8 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options) php_gcvt(d, (int)PG(serialize_precision), '.', 'e', num); len = strlen(num); - if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) { + if ((options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) + || (UNEXPECTED(len == 2 && num[0] == '-' && num[1] == '0'))) { num[len++] = '.'; num[len++] = '0'; num[len] = '\0'; diff --git a/ext/json/tests/bug79908.phpt b/ext/json/tests/bug79908.phpt new file mode 100644 index 0000000000000..b2aea31069a12 --- /dev/null +++ b/ext/json/tests/bug79908.phpt @@ -0,0 +1,12 @@ +--TEST-- +Bug #79908 (json_encode encodes negative zero as int) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +string(4) "-0.0"