From 9d37c6f01aa4dfbd3ab62b4ce35801bf5f9a029a Mon Sep 17 00:00:00 2001 From: 1820893135-pixel <1820893135@qq.com> Date: Wed, 23 Sep 2026 16:35:22 +0800 Subject: [PATCH] Fix undefined double-to-int casts when encoding a number value Three helpers in ecma-helpers-value.c converted a double to ecma_integer_value_t and only then checked whether the result was in range: ecma_integer_value_t integer_value = (ecma_integer_value_t) ecma_number; if ((ecma_number_t) integer_value == ecma_number && ...) The cast is already undefined behaviour when the double is outside the ecma_integer_value_t range, so the check comes too late. Under -fsanitize=undefined (the OSS-Fuzz configuration) this traps: ecma-helpers-value.c:572:40: runtime error: 1.5e+300 is outside the range of representable values of type 'int' Reached from ecma_make_number_value (line 572), ecma_update_float_number (line 1021) and ecma_value_assign_number (line 1073); a value such as 1.5e300 walks from one to the next, so all three need the same treatment. Move the range test in front of the cast in each function. Values outside the range fall through to ecma_create_float_number(), which is what happened for every value the existing round-trip check rejected. JerryScript-DCO-1.0-Signed-off-by: 1820893135-pixel <1820893135@qq.com> --- jerry-core/ecma/base/ecma-helpers-value.c | 65 +++++++++++++++-------- tests/unit-core/test-api-value-type.c | 21 ++++++++ 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/jerry-core/ecma/base/ecma-helpers-value.c b/jerry-core/ecma/base/ecma-helpers-value.c index cc79baa9c0..ac9e85574a 100644 --- a/jerry-core/ecma/base/ecma-helpers-value.c +++ b/jerry-core/ecma/base/ecma-helpers-value.c @@ -569,13 +569,21 @@ ecma_make_length_value (ecma_length_t number) /**< number to be encoded */ ecma_value_t ecma_make_number_value (ecma_number_t ecma_number) /**< number to be encoded */ { - ecma_integer_value_t integer_value = (ecma_integer_value_t) ecma_number; - - if ((ecma_number_t) integer_value == ecma_number - && ((integer_value == 0) ? ecma_is_number_equal_to_positive_zero (ecma_number) - : ECMA_IS_INTEGER_NUMBER (integer_value))) + /* The value can only be stored as a simple integer when it is inside the + * ecma_integer_value_t range; converting a double outside of it (e.g. 1e308, + * or a number literal coming from a snapshot) is undefined behaviour and traps + * under -fsanitize=undefined. */ + if (ecma_number >= (ecma_number_t) ECMA_INTEGER_NUMBER_MIN + && ecma_number <= (ecma_number_t) ECMA_INTEGER_NUMBER_MAX) { - return ecma_make_integer_value (integer_value); + ecma_integer_value_t integer_value = (ecma_integer_value_t) ecma_number; + + if ((ecma_number_t) integer_value == ecma_number + && ((integer_value == 0) ? ecma_is_number_equal_to_positive_zero (ecma_number) + : ECMA_IS_INTEGER_NUMBER (integer_value))) + { + return ecma_make_integer_value (integer_value); + } } return ecma_create_float_number (ecma_number); @@ -1010,15 +1018,22 @@ ecma_update_float_number (ecma_value_t float_value, /**< original float value */ { JERRY_ASSERT (ecma_is_value_float_number (float_value)); - ecma_integer_value_t integer_number = (ecma_integer_value_t) new_number; ecma_number_t *number_p = (ecma_number_t *) ecma_get_pointer_from_ecma_value (float_value); - if ((ecma_number_t) integer_number == new_number - && ((integer_number == 0) ? ecma_is_number_equal_to_positive_zero (new_number) - : ECMA_IS_INTEGER_NUMBER (integer_number))) + /* See the note in ecma_make_number_value: the cast is only defined when the + * double is inside the ecma_integer_value_t range. */ + if (new_number >= (ecma_number_t) ECMA_INTEGER_NUMBER_MIN + && new_number <= (ecma_number_t) ECMA_INTEGER_NUMBER_MAX) { - ecma_dealloc_number (number_p); - return ecma_make_integer_value (integer_number); + ecma_integer_value_t integer_number = (ecma_integer_value_t) new_number; + + if ((ecma_number_t) integer_number == new_number + && ((integer_number == 0) ? ecma_is_number_equal_to_positive_zero (new_number) + : ECMA_IS_INTEGER_NUMBER (integer_number))) + { + ecma_dealloc_number (number_p); + return ecma_make_integer_value (integer_number); + } } *number_p = new_number; @@ -1062,19 +1077,25 @@ void ecma_value_assign_number (ecma_value_t *value_p, /**< [in, out] ecma value */ ecma_number_t ecma_number) /**< number to assign */ { - ecma_integer_value_t integer_value = (ecma_integer_value_t) ecma_number; - - if ((ecma_number_t) integer_value == ecma_number - && ((integer_value == 0) ? ecma_is_number_equal_to_positive_zero (ecma_number) - : ECMA_IS_INTEGER_NUMBER (integer_value))) + /* See the note in ecma_make_number_value: the cast is only defined when the + * double is inside the ecma_integer_value_t range. */ + if (ecma_number >= (ecma_number_t) ECMA_INTEGER_NUMBER_MIN + && ecma_number <= (ecma_number_t) ECMA_INTEGER_NUMBER_MAX) { - if (ecma_get_value_type_field (*value_p) != ECMA_TYPE_DIRECT - && ecma_get_value_type_field (*value_p) != ECMA_TYPE_OBJECT) + ecma_integer_value_t integer_value = (ecma_integer_value_t) ecma_number; + + if ((ecma_number_t) integer_value == ecma_number + && ((integer_value == 0) ? ecma_is_number_equal_to_positive_zero (ecma_number) + : ECMA_IS_INTEGER_NUMBER (integer_value))) { - ecma_free_value (*value_p); + if (ecma_get_value_type_field (*value_p) != ECMA_TYPE_DIRECT + && ecma_get_value_type_field (*value_p) != ECMA_TYPE_OBJECT) + { + ecma_free_value (*value_p); + } + *value_p = ecma_make_integer_value (integer_value); + return; } - *value_p = ecma_make_integer_value (integer_value); - return; } ecma_value_assign_float_number (value_p, ecma_number); diff --git a/tests/unit-core/test-api-value-type.c b/tests/unit-core/test-api-value-type.c index 9890c1eada..37d5c2dee4 100644 --- a/tests/unit-core/test-api-value-type.c +++ b/tests/unit-core/test-api-value-type.c @@ -147,6 +147,27 @@ main (void) jerry_set_realm (old_realm); } + /* Numbers outside the ecma_integer_value_t range must round-trip as float + * values. Converting them to an integer to test for a small integer used to + * be undefined behaviour and trapped under -fsanitize=undefined. */ + { + static const double out_of_range_numbers[] = { 1e308, -1e308, 1.5e300, -1.5e10, 1e18 }; + for (size_t i = 0; i < sizeof (out_of_range_numbers) / sizeof (double); i++) + { + jerry_value_t value = jerry_number (out_of_range_numbers[i]); + TEST_ASSERT (!jerry_value_is_exception (value)); + TEST_ASSERT (jerry_value_is_number (value)); + TEST_ASSERT (jerry_value_as_number (value) == out_of_range_numbers[i]); + jerry_value_free (value); + } + + /* Values inside the range must keep behaving as small integers. */ + jerry_value_t small = jerry_number (42.0); + TEST_ASSERT (jerry_value_is_number (small)); + TEST_ASSERT (jerry_value_as_number (small) == 42.0); + jerry_value_free (small); + } + jerry_cleanup (); return 0;