diff --git a/xpcom/string/nsTString.cpp b/xpcom/string/nsTString.cpp index 42ea25acd7b3..4e845f62df2e 100644 --- a/xpcom/string/nsTString.cpp +++ b/xpcom/string/nsTString.cpp @@ -26,40 +26,6 @@ bool nsTString::SetCharAt(char16_t aChar, index_type aIndex) { return true; } -/** - * nsTString::ToDouble - */ -template <> -double nsTString::ToDouble(bool aAllowTrailingChars, - nsresult* aErrorCode) const { - double res = 0.0; - if (this->Length() > 0) { - char* conv_stopped; - const char* str = this->get(); - // Use PR_strtod, not strtod, since we don't want locale involved. - res = PR_strtod(str, &conv_stopped); - if (aAllowTrailingChars && conv_stopped != str) { - *aErrorCode = NS_OK; - } else if (!aAllowTrailingChars && conv_stopped == str + this->Length()) { - *aErrorCode = NS_OK; - } else { - *aErrorCode = NS_ERROR_ILLEGAL_VALUE; - } - } else { - // The string was too short (0 characters) - *aErrorCode = NS_ERROR_ILLEGAL_VALUE; - } - return res; -} - -template <> -double nsTString::ToDouble(bool aAllowTrailingChars, - nsresult* aErrorCode) const { - NS_LossyConvertUTF16toASCII cString(BeginReading(), Length()); - return aAllowTrailingChars ? cString.ToDoubleAllowTrailingChars(aErrorCode) - : cString.ToDouble(aErrorCode); -} - template void nsTString::Rebind(const char_type* data, size_type length) { // If we currently own a buffer, release it. diff --git a/xpcom/string/nsTString.h b/xpcom/string/nsTString.h index 453436f2dccf..3f7e6435fcae 100644 --- a/xpcom/string/nsTString.h +++ b/xpcom/string/nsTString.h @@ -180,37 +180,6 @@ class nsTString : public nsTSubstring { char_type operator[](index_type aIndex) const { return CharAt(aIndex); } - /** - * Perform string to double-precision float conversion. - * - * @param aErrorCode will contain error if one occurs - * @return double-precision float rep of string value - */ - double ToDouble(nsresult* aErrorCode) const { - return ToDouble(/* aAllowTrailingChars = */ false, aErrorCode); - } - - /** - * Perform string to single-precision float conversion. - * - * @param aErrorCode will contain error if one occurs - * @return single-precision float rep of string value - */ - float ToFloat(nsresult* aErrorCode) const { - return float(ToDouble(aErrorCode)); - } - - /** - * Similar to above ToDouble and ToFloat but allows trailing characters that - * are not converted. - */ - double ToDoubleAllowTrailingChars(nsresult* aErrorCode) const { - return ToDouble(/* aAllowTrailingChars = */ true, aErrorCode); - } - float ToFloatAllowTrailingChars(nsresult* aErrorCode) const { - return float(ToDoubleAllowTrailingChars(aErrorCode)); - } - /** * Set a char inside this string at given index * @@ -248,8 +217,6 @@ class nsTString : public nsTSubstring { friend const nsTString& VoidCString(); friend const nsTString& VoidString(); - double ToDouble(bool aAllowTrailingChars, nsresult* aErrorCode) const; - // Used by Null[C]String. explicit nsTString(DataFlags aDataFlags) : substring_type(char_traits::sEmptyBuffer, 0, @@ -257,13 +224,6 @@ class nsTString : public nsTSubstring { ClassFlags::NULL_TERMINATED) {} }; -template <> -double nsTString::ToDouble(bool aAllowTrailingChars, - nsresult* aErrorCode) const; -template <> -double nsTString::ToDouble(bool aAllowTrailingChars, - nsresult* aErrorCode) const; - extern template class nsTString; extern template class nsTString; diff --git a/xpcom/string/nsTStringRepr.cpp b/xpcom/string/nsTStringRepr.cpp index 08c626c9f17b..0dd8eafb3495 100644 --- a/xpcom/string/nsTStringRepr.cpp +++ b/xpcom/string/nsTStringRepr.cpp @@ -6,6 +6,9 @@ #include "nsTStringRepr.h" +#include "double-conversion/string-to-double.h" +#include "mozilla/FloatingPoint.h" +#include "nsError.h" #include "nsString.h" namespace mozilla::detail { @@ -181,6 +184,89 @@ bool nsTStringRepr::EqualsIgnoreCase(const std::string_view& aString) const { }); } +// We can't use the method `StringToDoubleConverter::ToDouble` due to linking +// issues on Windows as it's in mozglue. Instead, implement the selection logic +// using an overload set. +// +// StringToFloat is used instead of StringToDouble for floats due to differences +// in rounding behaviour. +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(reinterpret_cast(aData), + aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(reinterpret_cast(aData), + aLength, aProcessed); +} + +template +static FloatT ParseFloatingPoint(const nsTStringRepr& aString, + bool aAllowTrailingChars, + nsresult* aErrorCode) { + // Performs conversion to double following the "rules for parsing + // floating-point number values" from the HTML standard. + // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values + // + // This behaviour allows for leading spaces, and will not generate infinity or + // NaN values except in error conditions. + int flags = double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES; + if (aAllowTrailingChars) { + flags |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK; + } + double_conversion::StringToDoubleConverter converter( + flags, mozilla::UnspecifiedNaN(), + mozilla::UnspecifiedNaN(), nullptr, nullptr); + + FloatT result; + int processed; + StringToFP(converter, aString.Data(), aString.Length(), &processed, &result); + + *aErrorCode = mozilla::IsFinite(result) ? NS_OK : NS_ERROR_ILLEGAL_VALUE; + return result; +} + +template +double nsTStringRepr::ToDouble(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template +double nsTStringRepr::ToDoubleAllowTrailingChars( + nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + +template +float nsTStringRepr::ToFloat(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template +float nsTStringRepr::ToFloatAllowTrailingChars(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + } // namespace mozilla::detail template class mozilla::detail::nsTStringRepr; diff --git a/xpcom/string/nsTStringRepr.h b/xpcom/string/nsTStringRepr.h index 172509f1c9c2..7e7fa5338404 100644 --- a/xpcom/string/nsTStringRepr.h +++ b/xpcom/string/nsTStringRepr.h @@ -420,6 +420,35 @@ class nsTStringRepr { int32_t RFindCharInSet(const string_view& aSet, int32_t aOffset = -1) const; + /** + * Perform locale-independent string to double-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return double-precision float rep of string value + */ + double ToDouble(nsresult* aErrorCode) const; + + /** + * Perform locale-independent string to single-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return single-precision float rep of string value + */ + float ToFloat(nsresult* aErrorCode) const; + + /** + * Similar to above ToDouble and ToFloat but allows trailing characters that + * are not converted. + */ + double ToDoubleAllowTrailingChars(nsresult* aErrorCode) const; + float ToFloatAllowTrailingChars(nsresult* aErrorCode) const; + protected: nsTStringRepr() = delete; // Never instantiate directly diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp index bea3a62423aa..7e0f986d2902 100644 --- a/xpcom/tests/gtest/TestStrings.cpp +++ b/xpcom/tests/gtest/TestStrings.cpp @@ -46,6 +46,9 @@ using mozilla::BlackBox; using mozilla::fallible; using mozilla::IsAscii; using mozilla::IsUtf8; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; using mozilla::Span; #define TestExample1 \ @@ -1539,102 +1542,134 @@ TEST_F(Strings, huge_capacity) { } } -static void test_tofloat_helper(const nsString& aStr, float aExpected, - bool aSuccess) { +static void test_tofloat_helper(const nsString& aStr, + mozilla::Maybe aExpected) { nsresult result; - EXPECT_EQ(aStr.ToFloat(&result), aExpected); - if (aSuccess) { - EXPECT_EQ(result, NS_OK); + float value = aStr.ToFloat(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); } else { - EXPECT_NE(result, NS_OK); + EXPECT_TRUE(NS_FAILED(result)); } } TEST_F(Strings, tofloat) { - test_tofloat_helper(u"42"_ns, 42.f, true); - test_tofloat_helper(u"42.0"_ns, 42.f, true); - test_tofloat_helper(u"-42"_ns, -42.f, true); - test_tofloat_helper(u"+42"_ns, 42, true); - test_tofloat_helper(u"13.37"_ns, 13.37f, true); - test_tofloat_helper(u"1.23456789"_ns, 1.23456789f, true); - test_tofloat_helper(u"1.98765432123456"_ns, 1.98765432123456f, true); - test_tofloat_helper(u"0"_ns, 0.f, true); - test_tofloat_helper(u"1.e5"_ns, 100000, true); - test_tofloat_helper(u""_ns, 0.f, false); - test_tofloat_helper(u"42foo"_ns, 42.f, false); - test_tofloat_helper(u"foo"_ns, 0.f, false); - test_tofloat_helper(u"1.5e-"_ns, 1.5f, false); + test_tofloat_helper(u"42"_ns, Some(42.f)); + test_tofloat_helper(u"42.0"_ns, Some(42.f)); + test_tofloat_helper(u"-42"_ns, Some(-42.f)); + test_tofloat_helper(u"+42"_ns, Some(42)); + test_tofloat_helper(u"13.37"_ns, Some(13.37f)); + test_tofloat_helper(u"1.23456789"_ns, Some(1.23456789f)); + test_tofloat_helper(u"1.98765432123456"_ns, Some(1.98765432123456f)); + test_tofloat_helper(u"0"_ns, Some(0.f)); + test_tofloat_helper(u"1.e5"_ns, Some(100000)); + test_tofloat_helper(u""_ns, Nothing()); + test_tofloat_helper(u"42foo"_ns, Nothing()); + test_tofloat_helper(u"foo"_ns, Nothing()); + test_tofloat_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_tofloat_helper(u" \t5"_ns, Some(5.f)); + + // Values which are too large generate an error + test_tofloat_helper(u"3.402823e38"_ns, Some(3.402823e+38)); + test_tofloat_helper(u"1e39"_ns, Nothing()); + test_tofloat_helper(u"-3.402823e38"_ns, Some(-3.402823e+38)); + test_tofloat_helper(u"-1e39"_ns, Nothing()); + + // Values which are too small round to zero + test_tofloat_helper(u"1.4013e-45"_ns, Some(1.4013e-45f)); + test_tofloat_helper(u"1e-46"_ns, Some(0.f)); + test_tofloat_helper(u"-1.4013e-45"_ns, Some(-1.4013e-45f)); + test_tofloat_helper(u"-1e-46"_ns, Some(-0.f)); } static void test_tofloat_allow_trailing_chars_helper(const nsString& aStr, - float aExpected, - bool aSuccess) { + Maybe aExpected) { nsresult result; - EXPECT_EQ(aStr.ToFloatAllowTrailingChars(&result), aExpected); - if (aSuccess) { - EXPECT_EQ(result, NS_OK); + float value = aStr.ToFloatAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); } else { - EXPECT_NE(result, NS_OK); + EXPECT_TRUE(NS_FAILED(result)); } } TEST_F(Strings, ToFloatAllowTrailingChars) { - test_tofloat_allow_trailing_chars_helper(u""_ns, 0.f, false); - test_tofloat_allow_trailing_chars_helper(u"foo"_ns, 0.f, false); - test_tofloat_allow_trailing_chars_helper(u"42foo"_ns, 42.f, true); - test_tofloat_allow_trailing_chars_helper(u"42-5"_ns, 42.f, true); - test_tofloat_allow_trailing_chars_helper(u"13.37.8"_ns, 13.37f, true); - test_tofloat_allow_trailing_chars_helper(u"1.5e-"_ns, 1.5f, true); + test_tofloat_allow_trailing_chars_helper(u""_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"42foo"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"42-5"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37f)); + test_tofloat_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5f)); } -static void test_todouble_helper(const nsString& aStr, double aExpected, - bool aSuccess) { +static void test_todouble_helper(const nsString& aStr, + Maybe aExpected) { nsresult result; - EXPECT_EQ(aStr.ToDouble(&result), aExpected); - if (aSuccess) { - EXPECT_EQ(result, NS_OK); + double value = aStr.ToDouble(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); } else { - EXPECT_NE(result, NS_OK); + EXPECT_TRUE(NS_FAILED(result)); } } TEST_F(Strings, todouble) { - test_todouble_helper(u"42"_ns, 42, true); - test_todouble_helper(u"42.0"_ns, 42, true); - test_todouble_helper(u"-42"_ns, -42, true); - test_todouble_helper(u"+42"_ns, 42, true); - test_todouble_helper(u"13.37"_ns, 13.37, true); - test_todouble_helper(u"1.23456789"_ns, 1.23456789, true); - test_todouble_helper(u"1.98765432123456"_ns, 1.98765432123456, true); - test_todouble_helper(u"123456789.98765432123456"_ns, 123456789.98765432123456, - true); - test_todouble_helper(u"0"_ns, 0, true); - test_todouble_helper(u"1.e5"_ns, 100000, true); - test_todouble_helper(u""_ns, 0, false); - test_todouble_helper(u"42foo"_ns, 42, false); - test_todouble_helper(u"foo"_ns, 0, false); - test_todouble_helper(u"1.5e-"_ns, 1.5, false); + test_todouble_helper(u"42"_ns, Some(42)); + test_todouble_helper(u"42.0"_ns, Some(42)); + test_todouble_helper(u"-42"_ns, Some(-42)); + test_todouble_helper(u"+42"_ns, Some(42)); + test_todouble_helper(u"13.37"_ns, Some(13.37)); + test_todouble_helper(u"1.23456789"_ns, Some(1.23456789)); + test_todouble_helper(u"1.98765432123456"_ns, Some(1.98765432123456)); + test_todouble_helper(u"123456789.98765432123456"_ns, + Some(123456789.98765432123456)); + test_todouble_helper(u"0"_ns, Some(0)); + test_todouble_helper(u"1.e5"_ns, Some(100000)); + test_todouble_helper(u""_ns, Nothing()); + test_todouble_helper(u"42foo"_ns, Nothing()); + test_todouble_helper(u"foo"_ns, Nothing()); + test_todouble_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_todouble_helper(u" \t5"_ns, Some(5.)); + + // Values which are too large generate an error + test_todouble_helper(u"1.797693e+308"_ns, Some(1.797693e+308)); + test_todouble_helper(u"1e309"_ns, Nothing()); + test_todouble_helper(u"-1.797693e+308"_ns, Some(-1.797693e+308)); + test_todouble_helper(u"-1e309"_ns, Nothing()); + + // Values which are too small round to zero + test_todouble_helper(u"4.940656e-324"_ns, Some(4.940656e-324)); + test_todouble_helper(u"1e-325"_ns, Some(0.)); + test_todouble_helper(u"-4.940656e-324"_ns, Some(-4.940656e-324)); + test_todouble_helper(u"-1e-325"_ns, Some(-0.)); } static void test_todouble_allow_trailing_chars_helper(const nsString& aStr, - double aExpected, - bool aSuccess) { + Maybe aExpected) { nsresult result; - EXPECT_EQ(aStr.ToDoubleAllowTrailingChars(&result), aExpected); - if (aSuccess) { - EXPECT_EQ(result, NS_OK); + double value = aStr.ToDoubleAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); } else { - EXPECT_NE(result, NS_OK); + EXPECT_TRUE(NS_FAILED(result)); } } TEST_F(Strings, ToDoubleAllowTrailingChars) { - test_todouble_allow_trailing_chars_helper(u""_ns, 0, false); - test_todouble_allow_trailing_chars_helper(u"foo"_ns, 0, false); - test_todouble_allow_trailing_chars_helper(u"42foo"_ns, 42, true); - test_todouble_allow_trailing_chars_helper(u"42-5"_ns, 42, true); - test_todouble_allow_trailing_chars_helper(u"13.37.8"_ns, 13.37, true); - test_todouble_allow_trailing_chars_helper(u"1.5e-"_ns, 1.5, true); + test_todouble_allow_trailing_chars_helper(u""_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"42foo"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"42-5"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37)); + test_todouble_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5)); } TEST_F(Strings, Split) {