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_StringType_inl_h
8 #define vm_StringType_inl_h
9 
10 #include "vm/StringType.h"
11 
12 #include "mozilla/PodOperations.h"
13 #include "mozilla/Range.h"
14 
15 #include "gc/Allocator.h"
16 #include "gc/FreeOp.h"
17 #include "gc/Marking.h"
18 #include "vm/JSCompartment.h"
19 #include "vm/JSContext.h"
20 
21 namespace js {
22 
23 // Allocate a thin inline string if possible, and a fat inline string if not.
24 template <AllowGC allowGC, typename CharT>
AllocateInlineString(JSContext * cx,size_t len,CharT ** chars)25 static MOZ_ALWAYS_INLINE JSInlineString* AllocateInlineString(JSContext* cx,
26                                                               size_t len,
27                                                               CharT** chars) {
28   MOZ_ASSERT(JSInlineString::lengthFits<CharT>(len));
29 
30   if (JSThinInlineString::lengthFits<CharT>(len)) {
31     JSThinInlineString* str = JSThinInlineString::new_<allowGC>(cx);
32     if (!str) return nullptr;
33     *chars = str->init<CharT>(len);
34     return str;
35   }
36 
37   JSFatInlineString* str = JSFatInlineString::new_<allowGC>(cx);
38   if (!str) return nullptr;
39   *chars = str->init<CharT>(len);
40   return str;
41 }
42 
43 // Create a thin inline string if possible, and a fat inline string if not.
44 template <AllowGC allowGC, typename CharT>
NewInlineString(JSContext * cx,mozilla::Range<const CharT> chars)45 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString(
46     JSContext* cx, mozilla::Range<const CharT> chars) {
47   /*
48    * Don't bother trying to find a static atom; measurement shows that not
49    * many get here (for one, Atomize is catching them).
50    */
51 
52   size_t len = chars.length();
53   CharT* storage;
54   JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage);
55   if (!str) return nullptr;
56 
57   mozilla::PodCopy(storage, chars.begin().get(), len);
58   storage[len] = 0;
59   return str;
60 }
61 
62 // Create a thin inline string if possible, and a fat inline string if not.
63 template <typename CharT>
NewInlineString(JSContext * cx,HandleLinearString base,size_t start,size_t length)64 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString(
65     JSContext* cx, HandleLinearString base, size_t start, size_t length) {
66   MOZ_ASSERT(JSInlineString::lengthFits<CharT>(length));
67 
68   CharT* chars;
69   JSInlineString* s = AllocateInlineString<CanGC>(cx, length, &chars);
70   if (!s) return nullptr;
71 
72   JS::AutoCheckCannotGC nogc;
73   mozilla::PodCopy(chars, base->chars<CharT>(nogc) + start, length);
74   chars[length] = 0;
75   return s;
76 }
77 
StringWriteBarrierPost(JSContext * maybecx,JSString ** strp,JSString * prev,JSString * next)78 static inline void StringWriteBarrierPost(JSContext* maybecx, JSString** strp,
79                                           JSString* prev, JSString* next) {
80   js::BarrierMethods<JSString*>::postBarrier(strp, prev, next);
81 }
82 
83 } /* namespace js */
84 
validateLength(JSContext * maybecx,size_t length)85 MOZ_ALWAYS_INLINE bool JSString::validateLength(JSContext* maybecx,
86                                                 size_t length) {
87   if (MOZ_UNLIKELY(length > JSString::MAX_LENGTH)) {
88     js::ReportAllocationOverflow(maybecx);
89     return false;
90   }
91 
92   return true;
93 }
94 
init(JSContext * cx,JSString * left,JSString * right,size_t length)95 MOZ_ALWAYS_INLINE void JSRope::init(JSContext* cx, JSString* left,
96                                     JSString* right, size_t length) {
97   d.u1.length = length;
98   d.u1.flags = INIT_ROPE_FLAGS;
99   if (left->hasLatin1Chars() && right->hasLatin1Chars())
100     d.u1.flags |= LATIN1_CHARS_BIT;
101   d.s.u2.left = left;
102   d.s.u3.right = right;
103   js::BarrierMethods<JSString*>::postBarrier(&d.s.u2.left, nullptr, left);
104   js::BarrierMethods<JSString*>::postBarrier(&d.s.u3.right, nullptr, right);
105 }
106 
107 template <js::AllowGC allowGC>
new_(JSContext * cx,typename js::MaybeRooted<JSString *,allowGC>::HandleType left,typename js::MaybeRooted<JSString *,allowGC>::HandleType right,size_t length)108 MOZ_ALWAYS_INLINE JSRope* JSRope::new_(
109     JSContext* cx,
110     typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
111     typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
112     size_t length) {
113   if (!validateLength(cx, length)) return nullptr;
114   JSRope* str = js::Allocate<JSRope, allowGC>(cx, js::gc::DefaultHeap);
115   if (!str) return nullptr;
116   str->init(cx, left, right, length);
117   return str;
118 }
119 
init(JSContext * cx,JSLinearString * base,size_t start,size_t length)120 MOZ_ALWAYS_INLINE void JSDependentString::init(JSContext* cx,
121                                                JSLinearString* base,
122                                                size_t start, size_t length) {
123   MOZ_ASSERT(start + length <= base->length());
124   d.u1.length = length;
125   JS::AutoCheckCannotGC nogc;
126   if (base->hasLatin1Chars()) {
127     d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
128     d.s.u2.nonInlineCharsLatin1 = base->latin1Chars(nogc) + start;
129   } else {
130     d.u1.flags = DEPENDENT_FLAGS;
131     d.s.u2.nonInlineCharsTwoByte = base->twoByteChars(nogc) + start;
132   }
133   d.s.u3.base = base;
134   js::BarrierMethods<JSString*>::postBarrier(
135       reinterpret_cast<JSString**>(&d.s.u3.base), nullptr, base);
136 }
137 
new_(JSContext * cx,JSLinearString * baseArg,size_t start,size_t length)138 MOZ_ALWAYS_INLINE JSLinearString* JSDependentString::new_(
139     JSContext* cx, JSLinearString* baseArg, size_t start, size_t length) {
140   /*
141    * Try to avoid long chains of dependent strings. We can't avoid these
142    * entirely, however, due to how ropes are flattened.
143    */
144   if (baseArg->isDependent()) {
145     if (mozilla::Maybe<size_t> offset = baseArg->asDependent().baseOffset()) {
146       start += *offset;
147       baseArg = baseArg->asDependent().base();
148     }
149   }
150 
151   MOZ_ASSERT(start + length <= baseArg->length());
152 
153   /*
154    * Do not create a string dependent on inline chars from another string,
155    * both to avoid the awkward moving-GC hazard this introduces and because it
156    * is more efficient to immediately undepend here.
157    */
158   bool useInline = baseArg->hasTwoByteChars()
159                        ? JSInlineString::lengthFits<char16_t>(length)
160                        : JSInlineString::lengthFits<JS::Latin1Char>(length);
161   if (useInline) {
162     js::RootedLinearString base(cx, baseArg);
163     return baseArg->hasLatin1Chars()
164                ? js::NewInlineString<JS::Latin1Char>(cx, base, start, length)
165                : js::NewInlineString<char16_t>(cx, base, start, length);
166   }
167 
168   if (baseArg->isExternal() && !baseArg->ensureFlat(cx)) return nullptr;
169 
170   JSDependentString* str =
171       js::Allocate<JSDependentString, js::NoGC>(cx, js::gc::DefaultHeap);
172   if (str) {
173     str->init(cx, baseArg, start, length);
174     return str;
175   }
176 
177   js::RootedLinearString base(cx, baseArg);
178 
179   str = js::Allocate<JSDependentString>(cx, js::gc::DefaultHeap);
180   if (!str) return nullptr;
181   str->init(cx, base, start, length);
182   return str;
183 }
184 
init(const char16_t * chars,size_t length)185 MOZ_ALWAYS_INLINE void JSFlatString::init(const char16_t* chars,
186                                           size_t length) {
187   d.u1.length = length;
188   d.u1.flags = INIT_FLAT_FLAGS;
189   d.s.u2.nonInlineCharsTwoByte = chars;
190 }
191 
init(const JS::Latin1Char * chars,size_t length)192 MOZ_ALWAYS_INLINE void JSFlatString::init(const JS::Latin1Char* chars,
193                                           size_t length) {
194   d.u1.length = length;
195   d.u1.flags = INIT_FLAT_FLAGS | LATIN1_CHARS_BIT;
196   d.s.u2.nonInlineCharsLatin1 = chars;
197 }
198 
199 template <js::AllowGC allowGC, typename CharT>
new_(JSContext * cx,const CharT * chars,size_t length)200 MOZ_ALWAYS_INLINE JSFlatString* JSFlatString::new_(JSContext* cx,
201                                                    const CharT* chars,
202                                                    size_t length) {
203   MOZ_ASSERT(chars[length] == CharT(0));
204 
205   if (!validateLength(cx, length)) return nullptr;
206 
207   JSFlatString* str;
208   if (cx->compartment()->isAtomsCompartment())
209     str = js::Allocate<js::NormalAtom, allowGC>(cx);
210   else
211     str = js::Allocate<JSFlatString, allowGC>(cx, js::gc::DefaultHeap);
212   if (!str) return nullptr;
213 
214   if (!str->isTenured()) {
215     // The chars pointer is only considered to be handed over to this
216     // function on a successful return. If the following registration
217     // fails, the string is partially initialized and must be made valid,
218     // or its finalizer may attempt to free uninitialized memory.
219     void* ptr = const_cast<void*>(static_cast<const void*>(chars));
220     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(ptr)) {
221       str->init((JS::Latin1Char*)nullptr, 0);
222       if (allowGC) ReportOutOfMemory(cx);
223       return nullptr;
224     }
225   }
226 
227   str->init(chars, length);
228   return str;
229 }
230 
toPropertyName(JSContext * cx)231 inline js::PropertyName* JSFlatString::toPropertyName(JSContext* cx) {
232 #ifdef DEBUG
233   uint32_t dummy;
234   MOZ_ASSERT(!isIndex(&dummy));
235 #endif
236   if (isAtom()) return asAtom().asPropertyName();
237   JSAtom* atom = js::AtomizeString(cx, this);
238   if (!atom) return nullptr;
239   return atom->asPropertyName();
240 }
241 
242 template <js::AllowGC allowGC>
new_(JSContext * cx)243 MOZ_ALWAYS_INLINE JSThinInlineString* JSThinInlineString::new_(JSContext* cx) {
244   if (cx->compartment()->isAtomsCompartment())
245     return (JSThinInlineString*)(js::Allocate<js::NormalAtom, allowGC>(cx));
246 
247   return js::Allocate<JSThinInlineString, allowGC>(cx, js::gc::DefaultHeap);
248 }
249 
250 template <js::AllowGC allowGC>
new_(JSContext * cx)251 MOZ_ALWAYS_INLINE JSFatInlineString* JSFatInlineString::new_(JSContext* cx) {
252   if (cx->compartment()->isAtomsCompartment())
253     return (JSFatInlineString*)(js::Allocate<js::FatInlineAtom, allowGC>(cx));
254 
255   return js::Allocate<JSFatInlineString, allowGC>(cx, js::gc::DefaultHeap);
256 }
257 
258 template <>
259 MOZ_ALWAYS_INLINE JS::Latin1Char* JSThinInlineString::init<JS::Latin1Char>(
260     size_t length) {
261   MOZ_ASSERT(lengthFits<JS::Latin1Char>(length));
262   d.u1.length = length;
263   d.u1.flags = INIT_THIN_INLINE_FLAGS | LATIN1_CHARS_BIT;
264   return d.inlineStorageLatin1;
265 }
266 
267 template <>
268 MOZ_ALWAYS_INLINE char16_t* JSThinInlineString::init<char16_t>(size_t length) {
269   MOZ_ASSERT(lengthFits<char16_t>(length));
270   d.u1.length = length;
271   d.u1.flags = INIT_THIN_INLINE_FLAGS;
272   return d.inlineStorageTwoByte;
273 }
274 
275 template <>
276 MOZ_ALWAYS_INLINE JS::Latin1Char* JSFatInlineString::init<JS::Latin1Char>(
277     size_t length) {
278   MOZ_ASSERT(lengthFits<JS::Latin1Char>(length));
279   d.u1.length = length;
280   d.u1.flags = INIT_FAT_INLINE_FLAGS | LATIN1_CHARS_BIT;
281   return d.inlineStorageLatin1;
282 }
283 
284 template <>
285 MOZ_ALWAYS_INLINE char16_t* JSFatInlineString::init<char16_t>(size_t length) {
286   MOZ_ASSERT(lengthFits<char16_t>(length));
287   d.u1.length = length;
288   d.u1.flags = INIT_FAT_INLINE_FLAGS;
289   return d.inlineStorageTwoByte;
290 }
291 
init(const char16_t * chars,size_t length,const JSStringFinalizer * fin)292 MOZ_ALWAYS_INLINE void JSExternalString::init(const char16_t* chars,
293                                               size_t length,
294                                               const JSStringFinalizer* fin) {
295   MOZ_ASSERT(fin);
296   MOZ_ASSERT(fin->finalize);
297   d.u1.length = length;
298   d.u1.flags = EXTERNAL_FLAGS;
299   d.s.u2.nonInlineCharsTwoByte = chars;
300   d.s.u3.externalFinalizer = fin;
301 }
302 
new_(JSContext * cx,const char16_t * chars,size_t length,const JSStringFinalizer * fin)303 MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::new_(
304     JSContext* cx, const char16_t* chars, size_t length,
305     const JSStringFinalizer* fin) {
306   if (!validateLength(cx, length)) return nullptr;
307   JSExternalString* str = js::Allocate<JSExternalString>(cx);
308   if (!str) return nullptr;
309   str->init(chars, length, fin);
310   cx->updateMallocCounter((length + 1) * sizeof(char16_t));
311   return str;
312 }
313 
getUnitStringForElement(JSContext * cx,JSString * str,size_t index)314 inline JSLinearString* js::StaticStrings::getUnitStringForElement(
315     JSContext* cx, JSString* str, size_t index) {
316   MOZ_ASSERT(index < str->length());
317 
318   char16_t c;
319   if (!str->getChar(cx, index, &c)) return nullptr;
320   if (c < UNIT_STATIC_LIMIT) return getUnit(c);
321   return js::NewInlineString<CanGC>(cx, mozilla::Range<const char16_t>(&c, 1));
322 }
323 
finalize(js::FreeOp * fop)324 MOZ_ALWAYS_INLINE void JSString::finalize(js::FreeOp* fop) {
325   /* FatInline strings are in a different arena. */
326   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING);
327   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM);
328 
329   if (isFlat())
330     asFlat().finalize(fop);
331   else
332     MOZ_ASSERT(isDependent() || isRope());
333 }
334 
finalize(js::FreeOp * fop)335 inline void JSFlatString::finalize(js::FreeOp* fop) {
336   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING);
337   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM);
338 
339   if (!isInline()) fop->free_(nonInlineCharsRaw());
340 }
341 
finalize(js::FreeOp * fop)342 inline void JSFatInlineString::finalize(js::FreeOp* fop) {
343   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_STRING);
344   MOZ_ASSERT(isInline());
345 
346   // Nothing to do.
347 }
348 
finalize(js::FreeOp * fop)349 inline void JSAtom::finalize(js::FreeOp* fop) {
350   MOZ_ASSERT(JSString::isAtom());
351   MOZ_ASSERT(JSString::isFlat());
352   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::ATOM);
353 
354   if (!isInline()) fop->free_(nonInlineCharsRaw());
355 }
356 
finalize(js::FreeOp * fop)357 inline void js::FatInlineAtom::finalize(js::FreeOp* fop) {
358   MOZ_ASSERT(JSString::isAtom());
359   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_ATOM);
360 
361   // Nothing to do.
362 }
363 
finalize(js::FreeOp * fop)364 inline void JSExternalString::finalize(js::FreeOp* fop) {
365   if (!JSString::isExternal()) {
366     // This started out as an external string, but was turned into a
367     // non-external string by JSExternalString::ensureFlat.
368     MOZ_ASSERT(isFlat());
369     fop->free_(nonInlineCharsRaw());
370     return;
371   }
372 
373   const JSStringFinalizer* fin = externalFinalizer();
374   fin->finalize(fin, const_cast<char16_t*>(rawTwoByteChars()));
375 }
376 
377 #endif /* vm_StringType_inl_h */
378