Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef vm_StringBuffer_h
8 : #define vm_StringBuffer_h
9 :
10 : #include "mozilla/DebugOnly.h"
11 : #include "mozilla/MaybeOneOf.h"
12 :
13 : #include "jscntxt.h"
14 :
15 : #include "js/Vector.h"
16 :
17 : namespace js {
18 :
19 : /*
20 : * String builder that eagerly checks for over-allocation past the maximum
21 : * string length.
22 : *
23 : * Any operation which would exceed the maximum string length causes an
24 : * exception report on the context and results in a failed return value.
25 : *
26 : * Well-sized extractions (which waste no more than 1/4 of their char
27 : * buffer space) are guaranteed for strings built by this interface.
28 : * See |extractWellSized|.
29 : */
30 24771 : class StringBuffer
31 : {
32 : /*
33 : * The Vector's buffer may be either stolen or copied, so we need to use
34 : * TempAllocPolicy and account for the memory manually when stealing.
35 : */
36 : typedef Vector<Latin1Char, 64> Latin1CharBuffer;
37 : typedef Vector<char16_t, 32> TwoByteCharBuffer;
38 :
39 : JSContext* cx;
40 :
41 : /*
42 : * If Latin1 strings are enabled, cb starts out as a Latin1CharBuffer. When
43 : * a TwoByte char is appended, inflateChars() constructs a TwoByteCharBuffer
44 : * and copies the Latin1 chars.
45 : */
46 : mozilla::MaybeOneOf<Latin1CharBuffer, TwoByteCharBuffer> cb;
47 :
48 : #ifdef DEBUG
49 : /*
50 : * Make sure ensureTwoByteChars() is called before calling
51 : * infallibleAppend(char16_t).
52 : */
53 : bool hasEnsuredTwoByteChars_;
54 : #endif
55 :
56 : /* Number of reserve()'d chars, see inflateChars. */
57 : size_t reserved_;
58 :
59 : StringBuffer(const StringBuffer& other) = delete;
60 : void operator=(const StringBuffer& other) = delete;
61 :
62 102938 : MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); }
63 0 : MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); }
64 :
65 89007 : MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
66 114 : MOZ_ALWAYS_INLINE TwoByteCharBuffer& twoByteChars() { return cb.ref<TwoByteCharBuffer>(); }
67 :
68 31820 : MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const {
69 31820 : return cb.ref<Latin1CharBuffer>();
70 : }
71 34 : MOZ_ALWAYS_INLINE const TwoByteCharBuffer& twoByteChars() const {
72 34 : return cb.ref<TwoByteCharBuffer>();
73 : }
74 :
75 : MOZ_MUST_USE bool inflateChars();
76 :
77 : public:
78 24771 : explicit StringBuffer(JSContext* cx)
79 24771 : : cx(cx)
80 : #ifdef DEBUG
81 : , hasEnsuredTwoByteChars_(false)
82 : #endif
83 24771 : , reserved_(0)
84 : {
85 24771 : cb.construct<Latin1CharBuffer>(cx);
86 24771 : }
87 :
88 0 : void clear() {
89 0 : if (isLatin1())
90 0 : latin1Chars().clear();
91 : else
92 0 : twoByteChars().clear();
93 0 : }
94 563 : MOZ_MUST_USE bool reserve(size_t len) {
95 563 : if (len > reserved_)
96 563 : reserved_ = len;
97 563 : return isLatin1() ? latin1Chars().reserve(len) : twoByteChars().reserve(len);
98 : }
99 : MOZ_MUST_USE bool resize(size_t len) {
100 : return isLatin1() ? latin1Chars().resize(len) : twoByteChars().resize(len);
101 : }
102 5678 : bool empty() const {
103 5678 : return isLatin1() ? latin1Chars().empty() : twoByteChars().empty();
104 : }
105 22532 : size_t length() const {
106 22532 : return isLatin1() ? latin1Chars().length() : twoByteChars().length();
107 : }
108 3394 : char16_t getChar(size_t idx) const {
109 3394 : return isLatin1() ? latin1Chars()[idx] : twoByteChars()[idx];
110 : }
111 :
112 15 : MOZ_MUST_USE bool ensureTwoByteChars() {
113 15 : if (isLatin1() && !inflateChars())
114 0 : return false;
115 :
116 : #ifdef DEBUG
117 15 : hasEnsuredTwoByteChars_ = true;
118 : #endif
119 15 : return true;
120 : }
121 :
122 7322 : MOZ_MUST_USE bool append(const char16_t c) {
123 7322 : if (isLatin1()) {
124 7322 : if (c <= JSString::MAX_LATIN1_CHAR)
125 7322 : return latin1Chars().append(Latin1Char(c));
126 0 : if (!inflateChars())
127 0 : return false;
128 : }
129 0 : return twoByteChars().append(c);
130 : }
131 8716 : MOZ_MUST_USE bool append(Latin1Char c) {
132 8716 : return isLatin1() ? latin1Chars().append(c) : twoByteChars().append(c);
133 : }
134 8412 : MOZ_MUST_USE bool append(char c) {
135 8412 : return append(Latin1Char(c));
136 : }
137 :
138 : inline MOZ_MUST_USE bool append(const char16_t* begin, const char16_t* end);
139 :
140 282 : MOZ_MUST_USE bool append(const char16_t* chars, size_t len) {
141 282 : return append(chars, chars + len);
142 : }
143 :
144 14715 : MOZ_MUST_USE bool append(const Latin1Char* begin, const Latin1Char* end) {
145 14715 : return isLatin1() ? latin1Chars().append(begin, end) : twoByteChars().append(begin, end);
146 : }
147 14590 : MOZ_MUST_USE bool append(const Latin1Char* chars, size_t len) {
148 14590 : return append(chars, chars + len);
149 : }
150 :
151 : MOZ_MUST_USE bool append(const JS::ConstCharPtr chars, size_t len) {
152 : return append(chars.get(), chars.get() + len);
153 : }
154 3 : MOZ_MUST_USE bool appendN(Latin1Char c, size_t n) {
155 3 : return isLatin1() ? latin1Chars().appendN(c, n) : twoByteChars().appendN(c, n);
156 : }
157 :
158 : inline MOZ_MUST_USE bool append(JSString* str);
159 : inline MOZ_MUST_USE bool append(JSLinearString* str);
160 : inline MOZ_MUST_USE bool appendSubstring(JSString* base, size_t off, size_t len);
161 : inline MOZ_MUST_USE bool appendSubstring(JSLinearString* base, size_t off, size_t len);
162 :
163 14590 : MOZ_MUST_USE bool append(const char* chars, size_t len) {
164 14590 : return append(reinterpret_cast<const Latin1Char*>(chars), len);
165 : }
166 :
167 : template <size_t ArrayLength>
168 14547 : MOZ_MUST_USE bool append(const char (&array)[ArrayLength]) {
169 14547 : return append(array, ArrayLength - 1); /* No trailing '\0'. */
170 : }
171 :
172 : /* Infallible variants usable when the corresponding space is reserved. */
173 0 : void infallibleAppend(Latin1Char c) {
174 0 : if (isLatin1())
175 0 : latin1Chars().infallibleAppend(c);
176 : else
177 0 : twoByteChars().infallibleAppend(c);
178 0 : }
179 0 : void infallibleAppend(char c) {
180 0 : infallibleAppend(Latin1Char(c));
181 0 : }
182 0 : void infallibleAppend(const Latin1Char* chars, size_t len) {
183 0 : if (isLatin1())
184 0 : latin1Chars().infallibleAppend(chars, len);
185 : else
186 0 : twoByteChars().infallibleAppend(chars, len);
187 0 : }
188 : void infallibleAppend(const char* chars, size_t len) {
189 : infallibleAppend(reinterpret_cast<const Latin1Char*>(chars), len);
190 : }
191 :
192 : void infallibleAppendSubstring(JSLinearString* base, size_t off, size_t len);
193 :
194 : /*
195 : * Because inflation is fallible, these methods should only be used after
196 : * calling ensureTwoByteChars().
197 : */
198 0 : void infallibleAppend(const char16_t* chars, size_t len) {
199 0 : MOZ_ASSERT(hasEnsuredTwoByteChars_);
200 0 : twoByteChars().infallibleAppend(chars, len);
201 0 : }
202 0 : void infallibleAppend(char16_t c) {
203 0 : MOZ_ASSERT(hasEnsuredTwoByteChars_);
204 0 : twoByteChars().infallibleAppend(c);
205 0 : }
206 :
207 79 : bool isUnderlyingBufferLatin1() const { return isLatin1(); }
208 :
209 0 : char16_t* rawTwoByteBegin() { return twoByteChars().begin(); }
210 : char16_t* rawTwoByteEnd() { return twoByteChars().end(); }
211 0 : const char16_t* rawTwoByteBegin() const { return twoByteChars().begin(); }
212 0 : const char16_t* rawTwoByteEnd() const { return twoByteChars().end(); }
213 :
214 : Latin1Char* rawLatin1Begin() { return latin1Chars().begin(); }
215 : Latin1Char* rawLatin1End() { return latin1Chars().end(); }
216 125 : const Latin1Char* rawLatin1Begin() const { return latin1Chars().begin(); }
217 125 : const Latin1Char* rawLatin1End() const { return latin1Chars().end(); }
218 :
219 : /*
220 : * Creates a string from the characters in this buffer, then (regardless
221 : * whether string creation succeeded or failed) empties the buffer.
222 : */
223 : JSFlatString* finishString();
224 :
225 : /* Identical to finishString() except that an atom is created. */
226 : JSAtom* finishAtom();
227 :
228 : /*
229 : * Creates a raw string from the characters in this buffer. The string is
230 : * exactly the characters in this buffer (inflated to TwoByte), it is *not*
231 : * null-terminated unless the last appended character was '\0'.
232 : */
233 : char16_t* stealChars();
234 : };
235 :
236 : inline bool
237 282 : StringBuffer::append(const char16_t* begin, const char16_t* end)
238 : {
239 282 : MOZ_ASSERT(begin <= end);
240 282 : if (isLatin1()) {
241 : while (true) {
242 2083 : if (begin >= end)
243 271 : return true;
244 906 : if (*begin > JSString::MAX_LATIN1_CHAR)
245 0 : break;
246 906 : if (!latin1Chars().append(*begin))
247 0 : return false;
248 906 : ++begin;
249 : }
250 0 : if (!inflateChars())
251 0 : return false;
252 : }
253 11 : return twoByteChars().append(begin, end);
254 : }
255 :
256 : inline bool
257 20131 : StringBuffer::append(JSLinearString* str)
258 : {
259 40262 : JS::AutoCheckCannotGC nogc;
260 20131 : if (isLatin1()) {
261 20127 : if (str->hasLatin1Chars())
262 20123 : return latin1Chars().append(str->latin1Chars(nogc), str->length());
263 4 : if (!inflateChars())
264 0 : return false;
265 : }
266 8 : return str->hasLatin1Chars()
267 12 : ? twoByteChars().append(str->latin1Chars(nogc), str->length())
268 12 : : twoByteChars().append(str->twoByteChars(nogc), str->length());
269 : }
270 :
271 : inline void
272 0 : StringBuffer::infallibleAppendSubstring(JSLinearString* base, size_t off, size_t len)
273 : {
274 0 : MOZ_ASSERT(off + len <= base->length());
275 0 : MOZ_ASSERT_IF(base->hasTwoByteChars(), isTwoByte());
276 :
277 0 : JS::AutoCheckCannotGC nogc;
278 0 : if (base->hasLatin1Chars())
279 0 : infallibleAppend(base->latin1Chars(nogc) + off, len);
280 : else
281 0 : infallibleAppend(base->twoByteChars(nogc) + off, len);
282 0 : }
283 :
284 : inline bool
285 368 : StringBuffer::appendSubstring(JSLinearString* base, size_t off, size_t len)
286 : {
287 368 : MOZ_ASSERT(off + len <= base->length());
288 :
289 736 : JS::AutoCheckCannotGC nogc;
290 368 : if (isLatin1()) {
291 368 : if (base->hasLatin1Chars())
292 368 : return latin1Chars().append(base->latin1Chars(nogc) + off, len);
293 0 : if (!inflateChars())
294 0 : return false;
295 : }
296 0 : return base->hasLatin1Chars()
297 0 : ? twoByteChars().append(base->latin1Chars(nogc) + off, len)
298 0 : : twoByteChars().append(base->twoByteChars(nogc) + off, len);
299 : }
300 :
301 : inline bool
302 : StringBuffer::appendSubstring(JSString* base, size_t off, size_t len)
303 : {
304 : JSLinearString* linear = base->ensureLinear(cx);
305 : if (!linear)
306 : return false;
307 :
308 : return appendSubstring(linear, off, len);
309 : }
310 :
311 : inline bool
312 1665 : StringBuffer::append(JSString* str)
313 : {
314 1665 : JSLinearString* linear = str->ensureLinear(cx);
315 1665 : if (!linear)
316 0 : return false;
317 :
318 1665 : return append(linear);
319 : }
320 :
321 : /* ES5 9.8 ToString, appending the result to the string buffer. */
322 : extern bool
323 : ValueToStringBufferSlow(JSContext* cx, const Value& v, StringBuffer& sb);
324 :
325 : inline bool
326 0 : ValueToStringBuffer(JSContext* cx, const Value& v, StringBuffer& sb)
327 : {
328 0 : if (v.isString())
329 0 : return sb.append(v.toString());
330 :
331 0 : return ValueToStringBufferSlow(cx, v, sb);
332 : }
333 :
334 : /* ES5 9.8 ToString for booleans, appending the result to the string buffer. */
335 : inline bool
336 0 : BooleanToStringBuffer(bool b, StringBuffer& sb)
337 : {
338 0 : return b ? sb.append("true") : sb.append("false");
339 : }
340 :
341 : } /* namespace js */
342 :
343 : #endif /* vm_StringBuffer_h */
|