mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	The main use of this method appears to be when parsing attributes with floating point values in HTML. By changing from PR_strtod, this change does have some semantic differences around edge-cases, though I've tried to keep it compliant with the wording in the standard. Here are some of the edge-cases I investigated: 1. We appear to build nspr with INFNAN_CHECK undefined, meaning that we did not parse strings like "inf", "infinity" and "nan" both before and after this change. 2. PR_strtod already ignored locale (built with USE_LOCALE undefined), so locale behaviour shouldn't have changed. 3. Neither PR_strtod nor the spec support hex floats, so I didn't enable them in double-conversion. 4. Leading whitespace is skipped by both PR_strtod and in the spec, so is skipped after this change. 5. Numbers too large to fit in a double (e.g. 2E308) were previously returned as inf or -inf by PR_strtod. The spec specifies that these values should return an error instead, so they have been changed to produce an error with this change. This is a web-visible change (<progress value=2E308 max=10> is indeterminate in Chromium and complete in Firefox before this change, it will be indeterminate for both after the change). 6. Parsing for floats & doubles are now handled seperately due to differences in the maximum and minimum representable values. Differential Revision: https://phabricator.services.mozilla.com/D148305
		
			
				
	
	
		
			273 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "nsTStringRepr.h"
 | 
						|
 | 
						|
#include "double-conversion/string-to-double.h"
 | 
						|
#include "mozilla/FloatingPoint.h"
 | 
						|
#include "nsError.h"
 | 
						|
#include "nsString.h"
 | 
						|
 | 
						|
namespace mozilla::detail {
 | 
						|
 | 
						|
template <typename T>
 | 
						|
typename nsTStringRepr<T>::char_type nsTStringRepr<T>::First() const {
 | 
						|
  MOZ_RELEASE_ASSERT(this->mLength > 0, "|First()| called on an empty string");
 | 
						|
  return this->mData[0];
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
typename nsTStringRepr<T>::char_type nsTStringRepr<T>::Last() const {
 | 
						|
  MOZ_RELEASE_ASSERT(this->mLength > 0, "|Last()| called on an empty string");
 | 
						|
  return this->mData[this->mLength - 1];
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const self_type& aStr) const {
 | 
						|
  return this->mLength == aStr.mLength &&
 | 
						|
         char_traits::compare(this->mData, aStr.mData, this->mLength) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const self_type& aStr,
 | 
						|
                              comparator_type aComp) const {
 | 
						|
  return this->mLength == aStr.mLength &&
 | 
						|
         aComp(this->mData, aStr.mData, this->mLength, aStr.mLength) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple) const {
 | 
						|
  return Equals(substring_type(aTuple));
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple,
 | 
						|
                              comparator_type aComp) const {
 | 
						|
  return Equals(substring_type(aTuple), aComp);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const char_type* aData) const {
 | 
						|
  // unfortunately, some callers pass null :-(
 | 
						|
  if (!aData) {
 | 
						|
    MOZ_ASSERT_UNREACHABLE("null data pointer");
 | 
						|
    return this->mLength == 0;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX avoid length calculation?
 | 
						|
  size_type length = char_traits::length(aData);
 | 
						|
  return this->mLength == length &&
 | 
						|
         char_traits::compare(this->mData, aData, this->mLength) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::Equals(const char_type* aData,
 | 
						|
                              comparator_type aComp) const {
 | 
						|
  // unfortunately, some callers pass null :-(
 | 
						|
  if (!aData) {
 | 
						|
    MOZ_ASSERT_UNREACHABLE("null data pointer");
 | 
						|
    return this->mLength == 0;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX avoid length calculation?
 | 
						|
  size_type length = char_traits::length(aData);
 | 
						|
  return this->mLength == length &&
 | 
						|
         aComp(this->mData, aData, this->mLength, length) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::EqualsASCII(const char* aData, size_type aLen) const {
 | 
						|
  return this->mLength == aLen &&
 | 
						|
         char_traits::compareASCII(this->mData, aData, aLen) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::EqualsASCII(const char* aData) const {
 | 
						|
  return char_traits::compareASCIINullTerminated(this->mData, this->mLength,
 | 
						|
                                                 aData) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::EqualsLatin1(const char* aData,
 | 
						|
                                    const size_type aLength) const {
 | 
						|
  return (this->mLength == aLength) &&
 | 
						|
         char_traits::equalsLatin1(this->mData, aData, aLength);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData,
 | 
						|
                                            size_type aLen) const {
 | 
						|
  return this->mLength == aLen &&
 | 
						|
         char_traits::compareLowerCaseToASCII(this->mData, aData, aLen) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData) const {
 | 
						|
  return char_traits::compareLowerCaseToASCIINullTerminated(
 | 
						|
             this->mData, this->mLength, aData) == 0;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::Find(const string_view& aString,
 | 
						|
                               index_type aOffset) const {
 | 
						|
  auto idx = View().find(aString, aOffset);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::LowerCaseFindASCII(const std::string_view& aString,
 | 
						|
                                             index_type aOffset) const {
 | 
						|
  if (aOffset > Length()) {
 | 
						|
    return kNotFound;
 | 
						|
  }
 | 
						|
  auto begin = BeginReading();
 | 
						|
  auto end = EndReading();
 | 
						|
  auto it =
 | 
						|
      std::search(begin + aOffset, end, aString.begin(), aString.end(),
 | 
						|
                  [](char_type l, char r) {
 | 
						|
                    MOZ_ASSERT(!(r & ~0x7F), "Unexpected non-ASCII character");
 | 
						|
                    MOZ_ASSERT(char_traits::ASCIIToLower(r) == char_type(r),
 | 
						|
                               "Search string must be ASCII lowercase");
 | 
						|
                    return char_traits::ASCIIToLower(l) == char_type(r);
 | 
						|
                  });
 | 
						|
  return it == end ? kNotFound : std::distance(begin, it);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::RFind(const string_view& aString) const {
 | 
						|
  auto idx = View().rfind(aString);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
typename nsTStringRepr<T>::size_type nsTStringRepr<T>::CountChar(
 | 
						|
    char_type aChar) const {
 | 
						|
  return std::count(BeginReading(), EndReading(), aChar);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::FindChar(char_type aChar, index_type aOffset) const {
 | 
						|
  auto idx = View().find(aChar, aOffset);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::RFindChar(char_type aChar, int32_t aOffset) const {
 | 
						|
  auto idx = View().rfind(aChar, aOffset != -1 ? aOffset : string_view::npos);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::FindCharInSet(const string_view& aSet,
 | 
						|
                                        index_type aOffset) const {
 | 
						|
  auto idx = View().find_first_of(aSet, aOffset);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
int32_t nsTStringRepr<T>::RFindCharInSet(const string_view& aSet,
 | 
						|
                                         int32_t aOffset) const {
 | 
						|
  auto idx =
 | 
						|
      View().find_last_of(aSet, aOffset != -1 ? aOffset : string_view::npos);
 | 
						|
  return idx == string_view::npos ? kNotFound : idx;
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
bool nsTStringRepr<T>::EqualsIgnoreCase(const std::string_view& aString) const {
 | 
						|
  return std::equal(BeginReading(), EndReading(), aString.begin(),
 | 
						|
                    aString.end(), [](char_type l, char r) {
 | 
						|
                      return char_traits::ASCIIToLower(l) ==
 | 
						|
                             char_traits::ASCIIToLower(char_type(r));
 | 
						|
                    });
 | 
						|
}
 | 
						|
 | 
						|
// 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<const uc16*>(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<const uc16*>(aData),
 | 
						|
                                       aLength, aProcessed);
 | 
						|
}
 | 
						|
 | 
						|
template <typename FloatT, typename CharT>
 | 
						|
static FloatT ParseFloatingPoint(const nsTStringRepr<CharT>& 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<double>(),
 | 
						|
      mozilla::UnspecifiedNaN<double>(), 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 <typename T>
 | 
						|
double nsTStringRepr<T>::ToDouble(nsresult* aErrorCode) const {
 | 
						|
  return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ false,
 | 
						|
                                       aErrorCode);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
double nsTStringRepr<T>::ToDoubleAllowTrailingChars(
 | 
						|
    nsresult* aErrorCode) const {
 | 
						|
  return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ true,
 | 
						|
                                       aErrorCode);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
float nsTStringRepr<T>::ToFloat(nsresult* aErrorCode) const {
 | 
						|
  return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ false,
 | 
						|
                                      aErrorCode);
 | 
						|
}
 | 
						|
 | 
						|
template <typename T>
 | 
						|
float nsTStringRepr<T>::ToFloatAllowTrailingChars(nsresult* aErrorCode) const {
 | 
						|
  return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ true,
 | 
						|
                                      aErrorCode);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::detail
 | 
						|
 | 
						|
template class mozilla::detail::nsTStringRepr<char>;
 | 
						|
template class mozilla::detail::nsTStringRepr<char16_t>;
 |