summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/intl/FormatBuffer.h
blob: 73d450a546ca2012c375ca136ace93617f0b04c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* -*- 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/. */

#ifndef builtin_intl_FormatBuffer_h
#define builtin_intl_FormatBuffer_h

#include "mozilla/Assertions.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"

#include <stddef.h>
#include <stdint.h>

#include "gc/Allocator.h"
#include "js/AllocPolicy.h"
#include "js/CharacterEncoding.h"
#include "js/TypeDecls.h"
#include "js/UniquePtr.h"
#include "js/Vector.h"
#include "vm/StringType.h"

namespace js::intl {

/**
 * A buffer for formatting unified intl data.
 */
template <typename CharT, size_t MinInlineCapacity = 0,
          class AllocPolicy = TempAllocPolicy>
class FormatBuffer {
 public:
  using CharType = CharT;

  // Allow move constructors, but not copy constructors, as this class owns a
  // js::Vector.
  FormatBuffer(FormatBuffer&& other) noexcept = default;
  FormatBuffer& operator=(FormatBuffer&& other) noexcept = default;

  explicit FormatBuffer(AllocPolicy aP = AllocPolicy())
      : buffer_(std::move(aP)) {
    // The initial capacity matches the requested minimum inline capacity, as
    // long as it doesn't exceed |Vector::kMaxInlineBytes / sizeof(CharT)|. If
    // this assertion should ever fail, either reduce |MinInlineCapacity| or
    // make the FormatBuffer initialization fallible.
    MOZ_ASSERT(buffer_.capacity() == MinInlineCapacity);
    if constexpr (MinInlineCapacity > 0) {
      // Ensure the full capacity is marked as reserved.
      //
      // Reserving the minimum inline capacity can never fail, even when
      // simulating OOM.
      MOZ_ALWAYS_TRUE(buffer_.reserve(MinInlineCapacity));
    }
  }

  // Implicitly convert to a Span.
  operator mozilla::Span<CharType>() { return buffer_; }
  operator mozilla::Span<const CharType>() const { return buffer_; }

  /**
   * Ensures the buffer has enough space to accommodate |size| elements.
   */
  [[nodiscard]] bool reserve(size_t size) {
    // Call |reserve| a second time to ensure its full capacity is marked as
    // reserved.
    return buffer_.reserve(size) && buffer_.reserve(buffer_.capacity());
  }

  /**
   * Returns the raw data inside the buffer.
   */
  CharType* data() { return buffer_.begin(); }

  /**
   * Returns the count of elements written into the buffer.
   */
  size_t length() const { return buffer_.length(); }

  /**
   * Returns the buffer's overall capacity.
   */
  size_t capacity() const { return buffer_.capacity(); }

  /**
   * Resizes the buffer to the given amount of written elements.
   */
  void written(size_t amount) {
    MOZ_ASSERT(amount <= buffer_.capacity());
    // This sets |buffer_|'s internal size so that it matches how much was
    // written. This is necessary because the write happens across FFI
    // boundaries.
    size_t curLength = length();
    if (amount > curLength) {
      buffer_.infallibleGrowByUninitialized(amount - curLength);
    } else {
      buffer_.shrinkBy(curLength - amount);
    }
  }

  /**
   * Copies the buffer's data to a JSString.
   *
   * TODO(#1715842) - This should be more explicit on needing to handle OOM
   * errors. In this case it returns a nullptr that must be checked, but it may
   * not be obvious.
   */
  JSLinearString* toString(JSContext* cx) const {
    if constexpr (std::is_same_v<CharT, uint8_t> ||
                  std::is_same_v<CharT, unsigned char> ||
                  std::is_same_v<CharT, char>) {
      // Handle the UTF-8 encoding case.
      return NewStringCopyUTF8N(
          cx, JS::UTF8Chars(buffer_.begin(), buffer_.length()));
    } else {
      // Handle the UTF-16 encoding case.
      static_assert(std::is_same_v<CharT, char16_t>);
      return NewStringCopyN<CanGC>(cx, buffer_.begin(), buffer_.length());
    }
  }

  /**
   * Copies the buffer's data to a JSString. The buffer must contain only
   * ASCII characters.
   */
  JSLinearString* toAsciiString(JSContext* cx) const {
    static_assert(std::is_same_v<CharT, char>);

    MOZ_ASSERT(mozilla::IsAscii(buffer_));
    return NewStringCopyN<CanGC>(cx, buffer_.begin(), buffer_.length());
  }

  /**
   * Extract this buffer's content as a null-terminated string.
   */
  UniquePtr<CharType[], JS::FreePolicy> extractStringZ() {
    // Adding the NUL character on an already null-terminated string is likely
    // an error. If there's ever a valid use case which triggers this assertion,
    // we should change the below code to only conditionally add '\0'.
    MOZ_ASSERT_IF(!buffer_.empty(), buffer_.end()[-1] != '\0');

    if (!buffer_.append('\0')) {
      return nullptr;
    }
    return UniquePtr<CharType[], JS::FreePolicy>(
        buffer_.extractOrCopyRawBuffer());
  }

 private:
  js::Vector<CharT, MinInlineCapacity, AllocPolicy> buffer_;
};

}  // namespace js::intl

#endif /* builtin_intl_FormatBuffer_h */