tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ICU4CGlue.h (22091B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #ifndef intl_components_ICUUtils_h
      6 #define intl_components_ICUUtils_h
      7 
      8 #include "unicode/uenum.h"
      9 #include "unicode/utypes.h"
     10 #include "mozilla/Buffer.h"
     11 #include "mozilla/CheckedInt.h"
     12 #include "mozilla/DebugOnly.h"
     13 #include "mozilla/Likely.h"
     14 #include "mozilla/Maybe.h"
     15 #include "mozilla/Result.h"
     16 #include "mozilla/Span.h"
     17 #include "mozilla/Utf8.h"
     18 #include "mozilla/Vector.h"
     19 #include "mozilla/intl/ICUError.h"
     20 
     21 // When building standalone js shell, it will include headers from
     22 // intl/components if JS_HAS_INTL_API is true (the default value), but js shell
     23 // won't include headers from XPCOM, so don't include nsTArray.h when building
     24 // standalone js shell.
     25 #ifndef JS_STANDALONE
     26 #  include "nsTArray.h"
     27 #endif
     28 
     29 #include <cstring>
     30 #include <iterator>
     31 #include <stddef.h>
     32 #include <stdint.h>
     33 #include <string>
     34 #include <string_view>
     35 
     36 struct UFormattedValue;
     37 namespace mozilla::intl {
     38 
     39 template <typename CharType>
     40 static inline CharType* AssertNullTerminatedString(Span<CharType> aSpan) {
     41  // Intentionally check one past the last character, because we expect that the
     42  // NUL character isn't part of the string.
     43  MOZ_ASSERT(*(aSpan.data() + aSpan.size()) == '\0');
     44 
     45  // Also ensure there aren't any other NUL characters within the string.
     46  MOZ_ASSERT(std::char_traits<std::remove_const_t<CharType>>::length(
     47                 aSpan.data()) == aSpan.size());
     48 
     49  return aSpan.data();
     50 }
     51 
     52 static inline const char* AssertNullTerminatedString(std::string_view aView) {
     53  // Intentionally check one past the last character, because we expect that the
     54  // NUL character isn't part of the string.
     55  MOZ_ASSERT(*(aView.data() + aView.size()) == '\0');
     56 
     57  // Also ensure there aren't any other NUL characters within the string.
     58  MOZ_ASSERT(std::strlen(aView.data()) == aView.size());
     59 
     60  return aView.data();
     61 }
     62 
     63 /**
     64 * Map the "und" locale to an empty string, which ICU uses internally.
     65 */
     66 static inline const char* IcuLocale(const char* aLocale) {
     67  // Return the empty string if the input is exactly equal to the string "und".
     68  const char* locale = aLocale;
     69  if (!std::strcmp(locale, "und")) {
     70    locale = "";  // ICU root locale
     71  }
     72  return locale;
     73 }
     74 
     75 /**
     76 * Ensure a locale is null-terminated, and map the "und" locale to an empty
     77 * string, which ICU uses internally.
     78 */
     79 static inline const char* IcuLocale(Span<const char> aLocale) {
     80  return IcuLocale(AssertNullTerminatedString(aLocale));
     81 }
     82 
     83 /**
     84 * Ensure a locale in the buffer is null-terminated, and map the "und" locale to
     85 * an empty string, which ICU uses internally.
     86 */
     87 static inline const char* IcuLocale(const Buffer<char>& aLocale) {
     88  return IcuLocale(Span(aLocale.begin(), aLocale.Length() - 1));
     89 }
     90 
     91 using ICUResult = Result<Ok, ICUError>;
     92 
     93 /**
     94 * Convert a UErrorCode to ICUError. This will correctly apply the OutOfMemory
     95 * case.
     96 */
     97 ICUError ToICUError(UErrorCode status);
     98 
     99 /**
    100 * Convert a UErrorCode to ICUResult. This will correctly apply the OutOfMemory
    101 * case.
    102 */
    103 ICUResult ToICUResult(UErrorCode status);
    104 
    105 /**
    106 * The ICU status can complain about a string not being terminated, but this
    107 * is fine for this API, as it deals with the mozilla::Span that has a pointer
    108 * and a length.
    109 */
    110 static inline bool ICUSuccessForStringSpan(UErrorCode status) {
    111  return U_SUCCESS(status) || status == U_STRING_NOT_TERMINATED_WARNING;
    112 }
    113 
    114 /**
    115 * This class enforces that the unified mozilla::intl methods match the
    116 * const-ness of the underlying ICU4C API calls. const ICU4C APIs take a const
    117 * pointer, while mutable ones take a non-const pointer.
    118 *
    119 * For const ICU4C calls use:
    120 *   ICUPointer::GetConst().
    121 *
    122 * For non-const ICU4C calls use:
    123 *   ICUPointer::GetMut().
    124 *
    125 * This will propagate the `const` specifier from the ICU4C API call to the
    126 * unified method, and it will be enforced by the compiler. This helps ensures
    127 * a consistence and correct implementation.
    128 */
    129 template <typename T>
    130 class ICUPointer {
    131 public:
    132  explicit ICUPointer(T* aPointer) : mPointer(aPointer) {}
    133 
    134  // Only allow moves of ICUPointers, no copies.
    135  ICUPointer(ICUPointer&& other) noexcept = default;
    136  ICUPointer& operator=(ICUPointer&& other) noexcept = default;
    137 
    138  // Implicitly take ownership of a raw pointer through copy assignment.
    139  ICUPointer& operator=(T* aPointer) noexcept {
    140    mPointer = aPointer;
    141    return *this;
    142  };
    143 
    144  const T* GetConst() const { return const_cast<const T*>(mPointer); }
    145  T* GetMut() { return mPointer; }
    146 
    147  explicit operator bool() const { return !!mPointer; }
    148 
    149 private:
    150  T* mPointer;
    151 };
    152 
    153 /**
    154 * Calling into ICU with the C-API can be a bit tricky. This function wraps up
    155 * the relatively risky operations involving pointers, lengths, and buffers into
    156 * a simpler call. This function accepts a lambda that performs the ICU call,
    157 * and returns the length of characters in the buffer. When using a temporary
    158 * stack-based buffer, the calls can often be done in one trip. However, if
    159 * additional memory is needed, this function will call the C-API twice, in
    160 * order to first get the size of the result, and then second to copy the result
    161 * over to the buffer.
    162 */
    163 template <typename ICUStringFunction, typename Buffer>
    164 static ICUResult FillBufferWithICUCall(Buffer& buffer,
    165                                       const ICUStringFunction& strFn) {
    166  static_assert(std::is_same_v<typename Buffer::CharType, char16_t> ||
    167                std::is_same_v<typename Buffer::CharType, char> ||
    168                std::is_same_v<typename Buffer::CharType, uint8_t>);
    169 
    170  UErrorCode status = U_ZERO_ERROR;
    171  int32_t length = strFn(buffer.data(), buffer.capacity(), &status);
    172  if (status == U_BUFFER_OVERFLOW_ERROR) {
    173    MOZ_ASSERT(length >= 0);
    174 
    175    if (!buffer.reserve(length)) {
    176      return Err(ICUError::OutOfMemory);
    177    }
    178 
    179    status = U_ZERO_ERROR;
    180    mozilla::DebugOnly<int32_t> length2 = strFn(buffer.data(), length, &status);
    181    MOZ_ASSERT(length == length2);
    182  }
    183  if (!ICUSuccessForStringSpan(status)) {
    184    return Err(ToICUError(status));
    185  }
    186 
    187  buffer.written(length);
    188 
    189  return Ok{};
    190 }
    191 
    192 /**
    193 * Adaptor for mozilla::Vector to implement the Buffer interface.
    194 */
    195 template <typename T, size_t N>
    196 class VectorToBufferAdaptor {
    197  mozilla::Vector<T, N>& vector;
    198 
    199 public:
    200  using CharType = T;
    201 
    202  explicit VectorToBufferAdaptor(mozilla::Vector<T, N>& vector)
    203      : vector(vector) {}
    204 
    205  T* data() { return vector.begin(); }
    206 
    207  size_t capacity() const { return vector.capacity(); }
    208 
    209  bool reserve(size_t length) { return vector.reserve(length); }
    210 
    211  void written(size_t length) {
    212    mozilla::DebugOnly<bool> result = vector.resizeUninitialized(length);
    213    MOZ_ASSERT(result);
    214  }
    215 };
    216 
    217 /**
    218 * An overload of FillBufferWithICUCall that accepts a mozilla::Vector rather
    219 * than a Buffer.
    220 */
    221 template <typename ICUStringFunction, size_t InlineSize, typename CharType>
    222 static ICUResult FillBufferWithICUCall(Vector<CharType, InlineSize>& vector,
    223                                       const ICUStringFunction& strFn) {
    224  VectorToBufferAdaptor buffer(vector);
    225  return FillBufferWithICUCall(buffer, strFn);
    226 }
    227 
    228 #ifndef JS_STANDALONE
    229 /**
    230 * mozilla::intl APIs require sizeable buffers. This class abstracts over
    231 * the nsTArray.
    232 */
    233 template <typename T>
    234 class nsTArrayToBufferAdapter {
    235 public:
    236  using CharType = T;
    237 
    238  // Do not allow copy or move. Move could be added in the future if needed.
    239  nsTArrayToBufferAdapter(const nsTArrayToBufferAdapter&) = delete;
    240  nsTArrayToBufferAdapter& operator=(const nsTArrayToBufferAdapter&) = delete;
    241 
    242  explicit nsTArrayToBufferAdapter(nsTArray<CharType>& aArray)
    243      : mArray(aArray) {}
    244 
    245  /**
    246   * Ensures the buffer has enough space to accommodate |size| elements.
    247   */
    248  [[nodiscard]] bool reserve(size_t size) {
    249    // Use fallible behavior here.
    250    return mArray.SetCapacity(size, fallible);
    251  }
    252 
    253  /**
    254   * Returns the raw data inside the buffer.
    255   */
    256  CharType* data() { return mArray.Elements(); }
    257 
    258  /**
    259   * Returns the count of elements written into the buffer.
    260   */
    261  size_t length() const { return mArray.Length(); }
    262 
    263  /**
    264   * Returns the buffer's overall capacity.
    265   */
    266  size_t capacity() const { return mArray.Capacity(); }
    267 
    268  /**
    269   * Resizes the buffer to the given amount of written elements.
    270   */
    271  void written(size_t amount) {
    272    MOZ_ASSERT(amount <= mArray.Capacity());
    273    // This sets |mArray|'s internal size so that it matches how much was
    274    // written. This is necessary because the write happens across FFI
    275    // boundaries.
    276    mArray.SetLengthAndRetainStorage(amount);
    277  }
    278 
    279 private:
    280  nsTArray<CharType>& mArray;
    281 };
    282 
    283 template <typename T, size_t N>
    284 class AutoTArrayToBufferAdapter : public nsTArrayToBufferAdapter<T> {
    285  using nsTArrayToBufferAdapter<T>::nsTArrayToBufferAdapter;
    286 };
    287 
    288 /**
    289 * An overload of FillBufferWithICUCall that accepts a nsTArray.
    290 */
    291 template <typename ICUStringFunction, typename CharType>
    292 static ICUResult FillBufferWithICUCall(nsTArray<CharType>& array,
    293                                       const ICUStringFunction& strFn) {
    294  nsTArrayToBufferAdapter<CharType> buffer(array);
    295  return FillBufferWithICUCall(buffer, strFn);
    296 }
    297 
    298 template <typename ICUStringFunction, typename CharType, size_t N>
    299 static ICUResult FillBufferWithICUCall(AutoTArray<CharType, N>& array,
    300                                       const ICUStringFunction& strFn) {
    301  AutoTArrayToBufferAdapter<CharType, N> buffer(array);
    302  return FillBufferWithICUCall(buffer, strFn);
    303 }
    304 #endif
    305 
    306 /**
    307 * Fill a UTF-8 or a UTF-16 buffer with a UTF-16 span. ICU4C mostly uses UTF-16
    308 * internally, but different consumers may have different situations with their
    309 * buffers.
    310 */
    311 template <typename Buffer>
    312 [[nodiscard]] bool FillBuffer(Span<const char16_t> utf16Span,
    313                              Buffer& targetBuffer) {
    314  static_assert(std::is_same_v<typename Buffer::CharType, char> ||
    315                std::is_same_v<typename Buffer::CharType, unsigned char> ||
    316                std::is_same_v<typename Buffer::CharType, char16_t>);
    317 
    318  if constexpr (std::is_same_v<typename Buffer::CharType, char> ||
    319                std::is_same_v<typename Buffer::CharType, unsigned char>) {
    320    auto targetSize = CheckedInt<size_t>(utf16Span.Length()) * 3;
    321    if (MOZ_UNLIKELY(!targetSize.isValid() ||
    322                     !targetBuffer.reserve(targetSize.value()))) {
    323      return false;
    324    }
    325 
    326    size_t amount = ConvertUtf16toUtf8(
    327        utf16Span, Span(reinterpret_cast<char*>(targetBuffer.data()),
    328                        targetBuffer.capacity()));
    329 
    330    targetBuffer.written(amount);
    331  }
    332  if constexpr (std::is_same_v<typename Buffer::CharType, char16_t>) {
    333    size_t amount = utf16Span.Length();
    334    if (!targetBuffer.reserve(amount)) {
    335      return false;
    336    }
    337    for (size_t i = 0; i < amount; i++) {
    338      targetBuffer.data()[i] = utf16Span[i];
    339    }
    340    targetBuffer.written(amount);
    341  }
    342 
    343  return true;
    344 }
    345 
    346 /**
    347 * Fill a UTF-8 or a UTF-16 buffer with a UTF-8 span. ICU4C mostly uses UTF-16
    348 * internally, but different consumers may have different situations with their
    349 * buffers.
    350 */
    351 template <typename Buffer>
    352 [[nodiscard]] bool FillBuffer(Span<const char> utf8Span, Buffer& targetBuffer) {
    353  static_assert(std::is_same_v<typename Buffer::CharType, char> ||
    354                std::is_same_v<typename Buffer::CharType, unsigned char> ||
    355                std::is_same_v<typename Buffer::CharType, char16_t>);
    356 
    357  if constexpr (std::is_same_v<typename Buffer::CharType, char> ||
    358                std::is_same_v<typename Buffer::CharType, unsigned char>) {
    359    size_t amount = utf8Span.Length();
    360    if (!targetBuffer.reserve(amount)) {
    361      return false;
    362    }
    363    for (size_t i = 0; i < amount; i++) {
    364      targetBuffer.data()[i] =
    365          // Static cast in case of a mismatch between `unsigned char` and
    366          // `char`
    367          static_cast<typename Buffer::CharType>(utf8Span[i]);
    368    }
    369    targetBuffer.written(amount);
    370  }
    371  if constexpr (std::is_same_v<typename Buffer::CharType, char16_t>) {
    372    if (!targetBuffer.reserve(utf8Span.Length() + 1)) {
    373      return false;
    374    }
    375 
    376    size_t amount = ConvertUtf8toUtf16(
    377        utf8Span, Span(targetBuffer.data(), targetBuffer.capacity()));
    378 
    379    targetBuffer.written(amount);
    380  }
    381 
    382  return true;
    383 }
    384 
    385 /**
    386 * It is convenient for callers to be able to pass in UTF-8 strings to the API.
    387 * This function can be used to convert that to a stack-allocated UTF-16
    388 * mozilla::Vector that can then be passed into ICU calls. The string will be
    389 * null terminated.
    390 */
    391 template <size_t StackSize>
    392 [[nodiscard]] static bool FillUTF16Vector(
    393    Span<const char> utf8Span,
    394    mozilla::Vector<char16_t, StackSize>& utf16TargetVec) {
    395  // Per ConvertUtf8toUtf16: The length of aDest must be at least one greater
    396  // than the length of aSource. This additional length will be used for null
    397  // termination.
    398  if (!utf16TargetVec.reserve(utf8Span.Length() + 1)) {
    399    return false;
    400  }
    401 
    402  // ConvertUtf8toUtf16 fills the buffer with the data, but the length of the
    403  // vector is unchanged.
    404  size_t length = ConvertUtf8toUtf16(
    405      utf8Span, Span(utf16TargetVec.begin(), utf16TargetVec.capacity()));
    406 
    407  // Assert that the last element is free for writing a null terminator.
    408  MOZ_ASSERT(length < utf16TargetVec.capacity());
    409  utf16TargetVec.begin()[length] = '\0';
    410 
    411  // The call to resizeUninitialized notifies the vector of how much was written
    412  // exclusive of the null terminated character.
    413  return utf16TargetVec.resizeUninitialized(length);
    414 }
    415 
    416 /**
    417 * An iterable class that wraps calls to the ICU UEnumeration C API.
    418 *
    419 * Usage:
    420 *
    421 *  // Make sure the range expression is non-temporary, otherwise there is a
    422 *  // risk of undefined behavior:
    423 *  auto result = Calendar::GetBcp47KeywordValuesForLocale("en-US");
    424 *
    425 *  for (auto name : result.unwrap()) {
    426 *    MOZ_ASSERT(name.unwrap(), "An iterable value exists".);
    427 *  }
    428 */
    429 template <typename CharType, typename T, T(Mapper)(const CharType*, int32_t)>
    430 class Enumeration {
    431 public:
    432  class Iterator;
    433  friend class Iterator;
    434 
    435  // Transfer ownership of the UEnumeration in the move constructor.
    436  Enumeration(Enumeration&& other) noexcept
    437      : mUEnumeration(other.mUEnumeration) {
    438    other.mUEnumeration = nullptr;
    439  }
    440 
    441  // Transfer ownership of the UEnumeration in the move assignment operator.
    442  Enumeration& operator=(Enumeration&& other) noexcept {
    443    if (this == &other) {
    444      return *this;
    445    }
    446    if (mUEnumeration) {
    447      uenum_close(mUEnumeration);
    448    }
    449    mUEnumeration = other.mUEnumeration;
    450    other.mUEnumeration = nullptr;
    451    return *this;
    452  }
    453 
    454  class Iterator {
    455    Enumeration& mEnumeration;
    456    // `Nothing` signifies that no enumeration has been loaded through ICU yet.
    457    Maybe<int32_t> mIteration = Nothing{};
    458    const CharType* mNext = nullptr;
    459    int32_t mNextLength = 0;
    460 
    461   public:
    462    using value_type = const CharType*;
    463    using reference = T;
    464    using iterator_category = std::input_iterator_tag;
    465 
    466    explicit Iterator(Enumeration& aEnumeration, bool aIsBegin)
    467        : mEnumeration(aEnumeration) {
    468      if (aIsBegin) {
    469        AdvanceUEnum();
    470      }
    471    }
    472 
    473    Iterator& operator++() {
    474      AdvanceUEnum();
    475      return *this;
    476    }
    477 
    478    Iterator operator++(int) {
    479      Iterator retval = *this;
    480      ++(*this);
    481      return retval;
    482    }
    483 
    484    bool operator==(Iterator other) const {
    485      return mIteration == other.mIteration;
    486    }
    487 
    488    bool operator!=(Iterator other) const { return !(*this == other); }
    489 
    490    T operator*() const {
    491      // Map the iterated value to something new.
    492      return Mapper(mNext, mNextLength);
    493    }
    494 
    495   private:
    496    void AdvanceUEnum() {
    497      if (mIteration.isNothing()) {
    498        mIteration = Some(-1);
    499      }
    500      UErrorCode status = U_ZERO_ERROR;
    501      if constexpr (std::is_same_v<CharType, char16_t>) {
    502        mNext = uenum_unext(mEnumeration.mUEnumeration, &mNextLength, &status);
    503      } else {
    504        static_assert(std::is_same_v<CharType, char>,
    505                      "Only char16_t and char are supported by "
    506                      "mozilla::intl::Enumeration.");
    507        mNext = uenum_next(mEnumeration.mUEnumeration, &mNextLength, &status);
    508      }
    509      if (U_FAILURE(status)) {
    510        mNext = nullptr;
    511      }
    512 
    513      if (mNext) {
    514        (*mIteration)++;
    515      } else {
    516        // The iterator is complete.
    517        mIteration = Nothing{};
    518      }
    519    }
    520  };
    521 
    522  Iterator begin() { return Iterator(*this, true); }
    523  Iterator end() { return Iterator(*this, false); }
    524 
    525  explicit Enumeration(UEnumeration* aUEnumeration)
    526      : mUEnumeration(aUEnumeration) {}
    527 
    528  ~Enumeration() {
    529    if (mUEnumeration) {
    530      // Only close when the object is being destructed, not moved.
    531      uenum_close(mUEnumeration);
    532    }
    533  }
    534 
    535 private:
    536  UEnumeration* mUEnumeration = nullptr;
    537 };
    538 
    539 template <typename CharType>
    540 Result<Span<const CharType>, InternalError> SpanMapper(const CharType* string,
    541                                                       int32_t length) {
    542  // Return the raw value from this Iterator.
    543  if (string == nullptr) {
    544    return Err(InternalError{});
    545  }
    546  MOZ_ASSERT(length >= 0);
    547  return Span<const CharType>(string, static_cast<size_t>(length));
    548 }
    549 
    550 template <typename CharType>
    551 using SpanResult = Result<Span<const CharType>, InternalError>;
    552 
    553 template <typename CharType>
    554 using SpanEnumeration = Enumeration<CharType, SpanResult<CharType>, SpanMapper>;
    555 
    556 /**
    557 * An iterable class that wraps calls to ICU's available locales API.
    558 */
    559 template <int32_t(CountAvailable)(), const char*(GetAvailable)(int32_t)>
    560 class AvailableLocalesEnumeration final {
    561  // The overall count of available locales.
    562  int32_t mLocalesCount = 0;
    563 
    564 public:
    565  AvailableLocalesEnumeration() { mLocalesCount = CountAvailable(); }
    566 
    567  class Iterator {
    568   public:
    569    // std::iterator traits.
    570    using iterator_category = std::input_iterator_tag;
    571    using value_type = const char*;
    572    using difference_type = ptrdiff_t;
    573    using pointer = value_type*;
    574    using reference = value_type&;
    575 
    576   private:
    577    // The current position in the list of available locales.
    578    int32_t mLocalesPos = 0;
    579 
    580   public:
    581    explicit Iterator(int32_t aLocalesPos) : mLocalesPos(aLocalesPos) {}
    582 
    583    Iterator& operator++() {
    584      mLocalesPos++;
    585      return *this;
    586    }
    587 
    588    Iterator operator++(int) {
    589      Iterator result = *this;
    590      ++(*this);
    591      return result;
    592    }
    593 
    594    bool operator==(const Iterator& aOther) const {
    595      return mLocalesPos == aOther.mLocalesPos;
    596    }
    597 
    598    bool operator!=(const Iterator& aOther) const { return !(*this == aOther); }
    599 
    600    value_type operator*() const { return GetAvailable(mLocalesPos); }
    601  };
    602 
    603  // std::iterator begin() and end() methods.
    604 
    605  /**
    606   * Return an iterator pointing to the first available locale.
    607   */
    608  Iterator begin() const { return Iterator(0); }
    609 
    610  /**
    611   * Return an iterator pointing to one past the last available locale.
    612   */
    613  Iterator end() const { return Iterator(mLocalesCount); }
    614 };
    615 
    616 /**
    617 * A helper class to wrap calling ICU function in cpp file so we don't have to
    618 * include the ICU header here.
    619 */
    620 class FormattedResult {
    621 protected:
    622  static Result<Span<const char16_t>, ICUError> ToSpanImpl(
    623      const UFormattedValue* value);
    624 };
    625 
    626 /**
    627 * A RAII class to hold the formatted value of format result.
    628 *
    629 * The caller will need to create this AutoFormattedResult on the stack, with
    630 * the following parameters:
    631 * 1. Native ICU type.
    632 * 2. An ICU function which opens the result.
    633 * 3. An ICU function which can get the result as UFormattedValue.
    634 * 4. An ICU function which closes the result.
    635 *
    636 * After the object is created, caller needs to call IsValid() method to check
    637 * if the native object has been created properly, and then passes this
    638 * object to other format interfaces.
    639 * The format result will be stored in this object, the caller can use ToSpan()
    640 * method to get the formatted string.
    641 *
    642 * The methods GetFormatted() and Value() are private methods since they expose
    643 * native ICU types. If the caller wants to call these methods, the caller needs
    644 * to register itself as a friend class in AutoFormattedResult.
    645 *
    646 * The formatted value and the native ICU object will be released once this
    647 * class is destructed.
    648 */
    649 template <typename T, T*(Open)(UErrorCode*),
    650          const UFormattedValue*(GetValue)(const T*, UErrorCode*),
    651          void(Close)(T*)>
    652 class MOZ_RAII AutoFormattedResult : FormattedResult {
    653 public:
    654  AutoFormattedResult() {
    655    mFormatted = Open(&mError);
    656    if (U_FAILURE(mError)) {
    657      mFormatted = nullptr;
    658    }
    659  }
    660  ~AutoFormattedResult() {
    661    if (mFormatted) {
    662      Close(mFormatted);
    663    }
    664  }
    665 
    666  AutoFormattedResult(const AutoFormattedResult& other) = delete;
    667  AutoFormattedResult& operator=(const AutoFormattedResult& other) = delete;
    668 
    669  AutoFormattedResult(AutoFormattedResult&& other) = delete;
    670  AutoFormattedResult& operator=(AutoFormattedResult&& other) = delete;
    671 
    672  /**
    673   * Check if the native UFormattedDateInterval was created successfully.
    674   */
    675  bool IsValid() const { return !!mFormatted; }
    676 
    677  /**
    678   *  Get error code if IsValid() returns false.
    679   */
    680  ICUError GetError() const { return ToICUError(mError); }
    681 
    682  /**
    683   * Get the formatted result.
    684   */
    685  Result<Span<const char16_t>, ICUError> ToSpan() const {
    686    if (!IsValid()) {
    687      return Err(GetError());
    688    }
    689 
    690    const UFormattedValue* value = Value();
    691    if (!value) {
    692      return Err(ICUError::InternalError);
    693    }
    694 
    695    return ToSpanImpl(value);
    696  }
    697 
    698 private:
    699  friend class DateIntervalFormat;
    700  friend class ListFormat;
    701  T* GetFormatted() const { return mFormatted; }
    702 
    703  const UFormattedValue* Value() const {
    704    if (!IsValid()) {
    705      return nullptr;
    706    }
    707 
    708    UErrorCode status = U_ZERO_ERROR;
    709    const UFormattedValue* value = GetValue(mFormatted, &status);
    710    if (U_FAILURE(status)) {
    711      return nullptr;
    712    }
    713 
    714    return value;
    715  };
    716 
    717  T* mFormatted = nullptr;
    718  UErrorCode mError = U_ZERO_ERROR;
    719 };
    720 }  // namespace mozilla::intl
    721 
    722 #endif /* intl_components_ICUUtils_h */