| |
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 #include "vm/String-inl.h" |
| |
8 |
| |
9 #include "mozilla/MathAlgorithms.h" |
| |
10 #include "mozilla/MemoryReporting.h" |
| |
11 #include "mozilla/PodOperations.h" |
| |
12 #include "mozilla/RangedPtr.h" |
| |
13 |
| |
14 #include "gc/Marking.h" |
| |
15 |
| |
16 #include "jscntxtinlines.h" |
| |
17 #include "jscompartmentinlines.h" |
| |
18 |
| |
19 using namespace js; |
| |
20 |
| |
21 using mozilla::PodCopy; |
| |
22 using mozilla::RangedPtr; |
| |
23 using mozilla::RoundUpPow2; |
| |
24 |
| |
25 bool |
| |
26 JSString::isFatInline() const |
| |
27 { |
| |
28 // It's possible for fat-inline strings to be converted to flat strings; |
| |
29 // as a result, checking just for the arena isn't enough to determine if a |
| |
30 // string is fat-inline. Hence the isInline() check. |
| |
31 bool is_FatInline = (getAllocKind() == gc::FINALIZE_FAT_INLINE_STRING) && isInline(); |
| |
32 JS_ASSERT_IF(is_FatInline, isFlat()); |
| |
33 return is_FatInline; |
| |
34 } |
| |
35 |
| |
36 bool |
| |
37 JSString::isExternal() const |
| |
38 { |
| |
39 bool is_external = (getAllocKind() == gc::FINALIZE_EXTERNAL_STRING); |
| |
40 JS_ASSERT_IF(is_external, isFlat()); |
| |
41 return is_external; |
| |
42 } |
| |
43 |
| |
44 size_t |
| |
45 JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) |
| |
46 { |
| |
47 // JSRope: do nothing, we'll count all children chars when we hit the leaf strings. |
| |
48 if (isRope()) |
| |
49 return 0; |
| |
50 |
| |
51 JS_ASSERT(isLinear()); |
| |
52 |
| |
53 // JSDependentString: do nothing, we'll count the chars when we hit the base string. |
| |
54 if (isDependent()) |
| |
55 return 0; |
| |
56 |
| |
57 JS_ASSERT(isFlat()); |
| |
58 |
| |
59 // JSExtensibleString: count the full capacity, not just the used space. |
| |
60 if (isExtensible()) { |
| |
61 JSExtensibleString &extensible = asExtensible(); |
| |
62 return mallocSizeOf(extensible.chars()); |
| |
63 } |
| |
64 |
| |
65 // JSExternalString: don't count, the chars could be stored anywhere. |
| |
66 if (isExternal()) |
| |
67 return 0; |
| |
68 |
| |
69 // JSInlineString, JSFatInlineString [JSInlineAtom, JSFatInlineAtom]: the chars are inline. |
| |
70 if (isInline()) |
| |
71 return 0; |
| |
72 |
| |
73 // JSAtom, JSUndependedString: measure the space for the chars. For |
| |
74 // JSUndependedString, there is no need to count the base string, for the |
| |
75 // same reason as JSDependentString above. |
| |
76 JSFlatString &flat = asFlat(); |
| |
77 return mallocSizeOf(flat.chars()); |
| |
78 } |
| |
79 |
| |
80 #ifdef DEBUG |
| |
81 |
| |
82 void |
| |
83 JSString::dumpChars(const jschar *s, size_t n) |
| |
84 { |
| |
85 if (n == SIZE_MAX) { |
| |
86 n = 0; |
| |
87 while (s[n]) |
| |
88 n++; |
| |
89 } |
| |
90 |
| |
91 fputc('"', stderr); |
| |
92 for (size_t i = 0; i < n; i++) { |
| |
93 if (s[i] == '\n') |
| |
94 fprintf(stderr, "\\n"); |
| |
95 else if (s[i] == '\t') |
| |
96 fprintf(stderr, "\\t"); |
| |
97 else if (s[i] >= 32 && s[i] < 127) |
| |
98 fputc(s[i], stderr); |
| |
99 else if (s[i] <= 255) |
| |
100 fprintf(stderr, "\\x%02x", (unsigned int) s[i]); |
| |
101 else |
| |
102 fprintf(stderr, "\\u%04x", (unsigned int) s[i]); |
| |
103 } |
| |
104 fputc('"', stderr); |
| |
105 } |
| |
106 |
| |
107 void |
| |
108 JSString::dump() |
| |
109 { |
| |
110 if (const jschar *chars = getChars(nullptr)) { |
| |
111 fprintf(stderr, "JSString* (%p) = jschar * (%p) = ", |
| |
112 (void *) this, (void *) chars); |
| |
113 dumpChars(chars, length()); |
| |
114 } else { |
| |
115 fprintf(stderr, "(oom in JSString::dump)"); |
| |
116 } |
| |
117 fputc('\n', stderr); |
| |
118 } |
| |
119 |
| |
120 bool |
| |
121 JSString::equals(const char *s) |
| |
122 { |
| |
123 const jschar *c = getChars(nullptr); |
| |
124 if (!c) { |
| |
125 fprintf(stderr, "OOM in JSString::equals!\n"); |
| |
126 return false; |
| |
127 } |
| |
128 while (*c && *s) { |
| |
129 if (*c != *s) |
| |
130 return false; |
| |
131 c++; |
| |
132 s++; |
| |
133 } |
| |
134 return *c == *s; |
| |
135 } |
| |
136 #endif /* DEBUG */ |
| |
137 |
| |
138 static MOZ_ALWAYS_INLINE bool |
| |
139 AllocChars(ThreadSafeContext *maybecx, size_t length, jschar **chars, size_t *capacity) |
| |
140 { |
| |
141 /* |
| |
142 * String length doesn't include the null char, so include it here before |
| |
143 * doubling. Adding the null char after doubling would interact poorly with |
| |
144 * round-up malloc schemes. |
| |
145 */ |
| |
146 size_t numChars = length + 1; |
| |
147 |
| |
148 /* |
| |
149 * Grow by 12.5% if the buffer is very large. Otherwise, round up to the |
| |
150 * next power of 2. This is similar to what we do with arrays; see |
| |
151 * JSObject::ensureDenseArrayElements. |
| |
152 */ |
| |
153 static const size_t DOUBLING_MAX = 1024 * 1024; |
| |
154 numChars = numChars > DOUBLING_MAX ? numChars + (numChars / 8) : RoundUpPow2(numChars); |
| |
155 |
| |
156 /* Like length, capacity does not include the null char, so take it out. */ |
| |
157 *capacity = numChars - 1; |
| |
158 |
| |
159 JS_STATIC_ASSERT(JSString::MAX_LENGTH * sizeof(jschar) < UINT32_MAX); |
| |
160 size_t bytes = numChars * sizeof(jschar); |
| |
161 *chars = (jschar *)(maybecx ? maybecx->malloc_(bytes) : js_malloc(bytes)); |
| |
162 return *chars != nullptr; |
| |
163 } |
| |
164 |
| |
165 bool |
| |
166 JSRope::copyNonPureChars(ThreadSafeContext *cx, ScopedJSFreePtr<jschar> &out) const |
| |
167 { |
| |
168 return copyNonPureCharsInternal(cx, out, false); |
| |
169 } |
| |
170 |
| |
171 bool |
| |
172 JSRope::copyNonPureCharsZ(ThreadSafeContext *cx, ScopedJSFreePtr<jschar> &out) const |
| |
173 { |
| |
174 return copyNonPureCharsInternal(cx, out, true); |
| |
175 } |
| |
176 |
| |
177 bool |
| |
178 JSRope::copyNonPureCharsInternal(ThreadSafeContext *cx, ScopedJSFreePtr<jschar> &out, |
| |
179 bool nullTerminate) const |
| |
180 { |
| |
181 /* |
| |
182 * Perform non-destructive post-order traversal of the rope, splatting |
| |
183 * each node's characters into a contiguous buffer. |
| |
184 */ |
| |
185 |
| |
186 size_t n = length(); |
| |
187 if (cx) |
| |
188 out.reset(cx->pod_malloc<jschar>(n + 1)); |
| |
189 else |
| |
190 out.reset(js_pod_malloc<jschar>(n + 1)); |
| |
191 |
| |
192 if (!out) |
| |
193 return false; |
| |
194 |
| |
195 Vector<const JSString *, 8, SystemAllocPolicy> nodeStack; |
| |
196 const JSString *str = this; |
| |
197 jschar *pos = out; |
| |
198 while (true) { |
| |
199 if (str->isRope()) { |
| |
200 if (!nodeStack.append(str->asRope().rightChild())) |
| |
201 return false; |
| |
202 str = str->asRope().leftChild(); |
| |
203 } else { |
| |
204 size_t len = str->length(); |
| |
205 PodCopy(pos, str->asLinear().chars(), len); |
| |
206 pos += len; |
| |
207 if (nodeStack.empty()) |
| |
208 break; |
| |
209 str = nodeStack.popCopy(); |
| |
210 } |
| |
211 } |
| |
212 |
| |
213 JS_ASSERT(pos == out + n); |
| |
214 |
| |
215 if (nullTerminate) |
| |
216 out[n] = 0; |
| |
217 |
| |
218 return true; |
| |
219 } |
| |
220 |
| |
221 template<JSRope::UsingBarrier b> |
| |
222 JSFlatString * |
| |
223 JSRope::flattenInternal(ExclusiveContext *maybecx) |
| |
224 { |
| |
225 /* |
| |
226 * Perform a depth-first dag traversal, splatting each node's characters |
| |
227 * into a contiguous buffer. Visit each rope node three times: |
| |
228 * 1. record position in the buffer and recurse into left child; |
| |
229 * 2. recurse into the right child; |
| |
230 * 3. transform the node into a dependent string. |
| |
231 * To avoid maintaining a stack, tree nodes are mutated to indicate how many |
| |
232 * times they have been visited. Since ropes can be dags, a node may be |
| |
233 * encountered multiple times during traversal. However, step 3 above leaves |
| |
234 * a valid dependent string, so everything works out. This algorithm is |
| |
235 * homomorphic to marking code. |
| |
236 * |
| |
237 * While ropes avoid all sorts of quadratic cases with string |
| |
238 * concatenation, they can't help when ropes are immediately flattened. |
| |
239 * One idiomatic case that we'd like to keep linear (and has traditionally |
| |
240 * been linear in SM and other JS engines) is: |
| |
241 * |
| |
242 * while (...) { |
| |
243 * s += ... |
| |
244 * s.flatten |
| |
245 * } |
| |
246 * |
| |
247 * To do this, when the buffer for a to-be-flattened rope is allocated, the |
| |
248 * allocation size is rounded up. Then, if the resulting flat string is the |
| |
249 * left-hand side of a new rope that gets flattened and there is enough |
| |
250 * capacity, the rope is flattened into the same buffer, thereby avoiding |
| |
251 * copying the left-hand side. Clearing the 'extensible' bit turns off this |
| |
252 * optimization. This is necessary, e.g., when the JSAPI hands out the raw |
| |
253 * null-terminated char array of a flat string. |
| |
254 * |
| |
255 * N.B. This optimization can create chains of dependent strings. |
| |
256 */ |
| |
257 const size_t wholeLength = length(); |
| |
258 size_t wholeCapacity; |
| |
259 jschar *wholeChars; |
| |
260 JSString *str = this; |
| |
261 jschar *pos; |
| |
262 |
| |
263 /* Find the left most string, containing the first string. */ |
| |
264 JSRope *leftMostRope = this; |
| |
265 while (leftMostRope->leftChild()->isRope()) |
| |
266 leftMostRope = &leftMostRope->leftChild()->asRope(); |
| |
267 |
| |
268 if (leftMostRope->leftChild()->isExtensible()) { |
| |
269 JSExtensibleString &left = leftMostRope->leftChild()->asExtensible(); |
| |
270 size_t capacity = left.capacity(); |
| |
271 if (capacity >= wholeLength) { |
| |
272 /* |
| |
273 * Simulate a left-most traversal from the root to leftMost->leftChild() |
| |
274 * via first_visit_node |
| |
275 */ |
| |
276 while (str != leftMostRope) { |
| |
277 JS_ASSERT(str->isRope()); |
| |
278 if (b == WithIncrementalBarrier) { |
| |
279 JSString::writeBarrierPre(str->d.u1.left); |
| |
280 JSString::writeBarrierPre(str->d.s.u2.right); |
| |
281 } |
| |
282 JSString *child = str->d.u1.left; |
| |
283 str->d.u1.chars = left.chars(); |
| |
284 child->d.s.u3.parent = str; |
| |
285 child->d.lengthAndFlags = 0x200; |
| |
286 str = child; |
| |
287 } |
| |
288 if (b == WithIncrementalBarrier) { |
| |
289 JSString::writeBarrierPre(str->d.u1.left); |
| |
290 JSString::writeBarrierPre(str->d.s.u2.right); |
| |
291 } |
| |
292 str->d.u1.chars = left.chars(); |
| |
293 wholeCapacity = capacity; |
| |
294 wholeChars = const_cast<jschar *>(left.chars()); |
| |
295 size_t bits = left.d.lengthAndFlags; |
| |
296 pos = wholeChars + (bits >> LENGTH_SHIFT); |
| |
297 JS_STATIC_ASSERT(!(EXTENSIBLE_FLAGS & DEPENDENT_FLAGS)); |
| |
298 left.d.lengthAndFlags = bits ^ (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS); |
| |
299 left.d.s.u2.base = (JSLinearString *)this; /* will be true on exit */ |
| |
300 StringWriteBarrierPostRemove(maybecx, &left.d.u1.left); |
| |
301 StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u2.base); |
| |
302 goto visit_right_child; |
| |
303 } |
| |
304 } |
| |
305 |
| |
306 if (!AllocChars(maybecx, wholeLength, &wholeChars, &wholeCapacity)) |
| |
307 return nullptr; |
| |
308 |
| |
309 pos = wholeChars; |
| |
310 first_visit_node: { |
| |
311 if (b == WithIncrementalBarrier) { |
| |
312 JSString::writeBarrierPre(str->d.u1.left); |
| |
313 JSString::writeBarrierPre(str->d.s.u2.right); |
| |
314 } |
| |
315 |
| |
316 JSString &left = *str->d.u1.left; |
| |
317 str->d.u1.chars = pos; |
| |
318 StringWriteBarrierPostRemove(maybecx, &str->d.u1.left); |
| |
319 if (left.isRope()) { |
| |
320 left.d.s.u3.parent = str; /* Return to this when 'left' done, */ |
| |
321 left.d.lengthAndFlags = 0x200; /* but goto visit_right_child. */ |
| |
322 str = &left; |
| |
323 goto first_visit_node; |
| |
324 } |
| |
325 size_t len = left.length(); |
| |
326 PodCopy(pos, left.d.u1.chars, len); |
| |
327 pos += len; |
| |
328 } |
| |
329 visit_right_child: { |
| |
330 JSString &right = *str->d.s.u2.right; |
| |
331 if (right.isRope()) { |
| |
332 right.d.s.u3.parent = str; /* Return to this node when 'right' done, */ |
| |
333 right.d.lengthAndFlags = 0x300; /* but goto finish_node. */ |
| |
334 str = &right; |
| |
335 goto first_visit_node; |
| |
336 } |
| |
337 size_t len = right.length(); |
| |
338 PodCopy(pos, right.d.u1.chars, len); |
| |
339 pos += len; |
| |
340 } |
| |
341 finish_node: { |
| |
342 if (str == this) { |
| |
343 JS_ASSERT(pos == wholeChars + wholeLength); |
| |
344 *pos = '\0'; |
| |
345 str->d.lengthAndFlags = buildLengthAndFlags(wholeLength, EXTENSIBLE_FLAGS); |
| |
346 str->d.u1.chars = wholeChars; |
| |
347 str->d.s.u2.capacity = wholeCapacity; |
| |
348 StringWriteBarrierPostRemove(maybecx, &str->d.u1.left); |
| |
349 StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.right); |
| |
350 return &this->asFlat(); |
| |
351 } |
| |
352 size_t progress = str->d.lengthAndFlags; |
| |
353 str->d.lengthAndFlags = buildLengthAndFlags(pos - str->d.u1.chars, DEPENDENT_FLAGS); |
| |
354 str->d.s.u2.base = (JSLinearString *)this; /* will be true on exit */ |
| |
355 StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u2.base); |
| |
356 str = str->d.s.u3.parent; |
| |
357 if (progress == 0x200) |
| |
358 goto visit_right_child; |
| |
359 JS_ASSERT(progress == 0x300); |
| |
360 goto finish_node; |
| |
361 } |
| |
362 } |
| |
363 |
| |
364 JSFlatString * |
| |
365 JSRope::flatten(ExclusiveContext *maybecx) |
| |
366 { |
| |
367 #if JSGC_INCREMENTAL |
| |
368 if (zone()->needsBarrier()) |
| |
369 return flattenInternal<WithIncrementalBarrier>(maybecx); |
| |
370 else |
| |
371 return flattenInternal<NoBarrier>(maybecx); |
| |
372 #else |
| |
373 return flattenInternal<NoBarrier>(maybecx); |
| |
374 #endif |
| |
375 } |
| |
376 |
| |
377 template <AllowGC allowGC> |
| |
378 JSString * |
| |
379 js::ConcatStrings(ThreadSafeContext *cx, |
| |
380 typename MaybeRooted<JSString*, allowGC>::HandleType left, |
| |
381 typename MaybeRooted<JSString*, allowGC>::HandleType right) |
| |
382 { |
| |
383 JS_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left)); |
| |
384 JS_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right)); |
| |
385 |
| |
386 size_t leftLen = left->length(); |
| |
387 if (leftLen == 0) |
| |
388 return right; |
| |
389 |
| |
390 size_t rightLen = right->length(); |
| |
391 if (rightLen == 0) |
| |
392 return left; |
| |
393 |
| |
394 size_t wholeLength = leftLen + rightLen; |
| |
395 if (!JSString::validateLength(cx, wholeLength)) |
| |
396 return nullptr; |
| |
397 |
| |
398 if (JSFatInlineString::lengthFits(wholeLength) && cx->isJSContext()) { |
| |
399 JSFatInlineString *str = js_NewGCFatInlineString<allowGC>(cx); |
| |
400 if (!str) |
| |
401 return nullptr; |
| |
402 |
| |
403 ScopedThreadSafeStringInspector leftInspector(left); |
| |
404 ScopedThreadSafeStringInspector rightInspector(right); |
| |
405 if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx)) |
| |
406 return nullptr; |
| |
407 |
| |
408 jschar *buf = str->init(wholeLength); |
| |
409 PodCopy(buf, leftInspector.chars(), leftLen); |
| |
410 PodCopy(buf + leftLen, rightInspector.chars(), rightLen); |
| |
411 |
| |
412 buf[wholeLength] = 0; |
| |
413 return str; |
| |
414 } |
| |
415 |
| |
416 return JSRope::new_<allowGC>(cx, left, right, wholeLength); |
| |
417 } |
| |
418 |
| |
419 template JSString * |
| |
420 js::ConcatStrings<CanGC>(ThreadSafeContext *cx, HandleString left, HandleString right); |
| |
421 |
| |
422 template JSString * |
| |
423 js::ConcatStrings<NoGC>(ThreadSafeContext *cx, JSString *left, JSString *right); |
| |
424 |
| |
425 bool |
| |
426 JSDependentString::copyNonPureCharsZ(ThreadSafeContext *cx, ScopedJSFreePtr<jschar> &out) const |
| |
427 { |
| |
428 JS_ASSERT(JSString::isDependent()); |
| |
429 |
| |
430 size_t n = length(); |
| |
431 jschar *s = cx->pod_malloc<jschar>(n + 1); |
| |
432 if (!s) |
| |
433 return false; |
| |
434 |
| |
435 PodCopy(s, chars(), n); |
| |
436 s[n] = 0; |
| |
437 |
| |
438 out.reset(s); |
| |
439 return true; |
| |
440 } |
| |
441 |
| |
442 JSFlatString * |
| |
443 JSDependentString::undepend(ExclusiveContext *cx) |
| |
444 { |
| |
445 JS_ASSERT(JSString::isDependent()); |
| |
446 |
| |
447 /* |
| |
448 * We destroy the base() pointer in undepend, so we need a pre-barrier. We |
| |
449 * don't need a post-barrier because there aren't any outgoing pointers |
| |
450 * afterwards. |
| |
451 */ |
| |
452 JSString::writeBarrierPre(base()); |
| |
453 |
| |
454 size_t n = length(); |
| |
455 size_t size = (n + 1) * sizeof(jschar); |
| |
456 jschar *s = (jschar *) cx->malloc_(size); |
| |
457 if (!s) |
| |
458 return nullptr; |
| |
459 |
| |
460 PodCopy(s, chars(), n); |
| |
461 s[n] = 0; |
| |
462 d.u1.chars = s; |
| |
463 |
| |
464 /* |
| |
465 * Transform *this into an undepended string so 'base' will remain rooted |
| |
466 * for the benefit of any other dependent string that depends on *this. |
| |
467 */ |
| |
468 d.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS); |
| |
469 |
| |
470 return &this->asFlat(); |
| |
471 } |
| |
472 |
| |
473 bool |
| |
474 JSFlatString::isIndexSlow(uint32_t *indexp) const |
| |
475 { |
| |
476 const jschar *s = charsZ(); |
| |
477 jschar ch = *s; |
| |
478 |
| |
479 if (!JS7_ISDEC(ch)) |
| |
480 return false; |
| |
481 |
| |
482 size_t n = length(); |
| |
483 if (n > UINT32_CHAR_BUFFER_LENGTH) |
| |
484 return false; |
| |
485 |
| |
486 /* |
| |
487 * Make sure to account for the '\0' at the end of characters, dereferenced |
| |
488 * in the loop below. |
| |
489 */ |
| |
490 RangedPtr<const jschar> cp(s, n + 1); |
| |
491 const RangedPtr<const jschar> end(s + n, s, n + 1); |
| |
492 |
| |
493 uint32_t index = JS7_UNDEC(*cp++); |
| |
494 uint32_t oldIndex = 0; |
| |
495 uint32_t c = 0; |
| |
496 |
| |
497 if (index != 0) { |
| |
498 while (JS7_ISDEC(*cp)) { |
| |
499 oldIndex = index; |
| |
500 c = JS7_UNDEC(*cp); |
| |
501 index = 10 * index + c; |
| |
502 cp++; |
| |
503 } |
| |
504 } |
| |
505 |
| |
506 /* It's not an element if there are characters after the number. */ |
| |
507 if (cp != end) |
| |
508 return false; |
| |
509 |
| |
510 /* |
| |
511 * Look out for "4294967296" and larger-number strings that fit in |
| |
512 * UINT32_CHAR_BUFFER_LENGTH: only unsigned 32-bit integers shall pass. |
| |
513 */ |
| |
514 if (oldIndex < UINT32_MAX / 10 || (oldIndex == UINT32_MAX / 10 && c <= (UINT32_MAX % 10))) { |
| |
515 *indexp = index; |
| |
516 return true; |
| |
517 } |
| |
518 |
| |
519 return false; |
| |
520 } |
| |
521 |
| |
522 bool |
| |
523 ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx) |
| |
524 { |
| |
525 if (chars_) |
| |
526 return true; |
| |
527 |
| |
528 if (cx->isExclusiveContext()) { |
| |
529 JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); |
| |
530 if (!linear) |
| |
531 return false; |
| |
532 chars_ = linear->chars(); |
| |
533 } else { |
| |
534 if (str_->hasPureChars()) { |
| |
535 chars_ = str_->pureChars(); |
| |
536 } else { |
| |
537 if (!str_->copyNonPureChars(cx, scopedChars_)) |
| |
538 return false; |
| |
539 chars_ = scopedChars_; |
| |
540 } |
| |
541 } |
| |
542 |
| |
543 JS_ASSERT(chars_); |
| |
544 return true; |
| |
545 } |
| |
546 |
| |
547 /* |
| |
548 * Set up some tools to make it easier to generate large tables. After constant |
| |
549 * folding, for each n, Rn(0) is the comma-separated list R(0), R(1), ..., R(2^n-1). |
| |
550 * Similary, Rn(k) (for any k and n) generates the list R(k), R(k+1), ..., R(k+2^n-1). |
| |
551 * To use this, define R appropriately, then use Rn(0) (for some value of n), then |
| |
552 * undefine R. |
| |
553 */ |
| |
554 #define R2(n) R(n), R((n) + (1 << 0)), R((n) + (2 << 0)), R((n) + (3 << 0)) |
| |
555 #define R4(n) R2(n), R2((n) + (1 << 2)), R2((n) + (2 << 2)), R2((n) + (3 << 2)) |
| |
556 #define R6(n) R4(n), R4((n) + (1 << 4)), R4((n) + (2 << 4)), R4((n) + (3 << 4)) |
| |
557 #define R7(n) R6(n), R6((n) + (1 << 6)) |
| |
558 |
| |
559 /* |
| |
560 * This is used when we generate our table of short strings, so the compiler is |
| |
561 * happier if we use |c| as few times as possible. |
| |
562 */ |
| |
563 #define FROM_SMALL_CHAR(c) jschar((c) + ((c) < 10 ? '0' : \ |
| |
564 (c) < 36 ? 'a' - 10 : \ |
| |
565 'A' - 36)) |
| |
566 |
| |
567 /* |
| |
568 * Declare length-2 strings. We only store strings where both characters are |
| |
569 * alphanumeric. The lower 10 short chars are the numerals, the next 26 are |
| |
570 * the lowercase letters, and the next 26 are the uppercase letters. |
| |
571 */ |
| |
572 #define TO_SMALL_CHAR(c) ((c) >= '0' && (c) <= '9' ? (c) - '0' : \ |
| |
573 (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 10 : \ |
| |
574 (c) >= 'A' && (c) <= 'Z' ? (c) - 'A' + 36 : \ |
| |
575 StaticStrings::INVALID_SMALL_CHAR) |
| |
576 |
| |
577 #define R TO_SMALL_CHAR |
| |
578 const StaticStrings::SmallChar StaticStrings::toSmallChar[] = { R7(0) }; |
| |
579 #undef R |
| |
580 |
| |
581 bool |
| |
582 StaticStrings::init(JSContext *cx) |
| |
583 { |
| |
584 AutoLockForExclusiveAccess lock(cx); |
| |
585 AutoCompartment ac(cx, cx->runtime()->atomsCompartment()); |
| |
586 |
| |
587 for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) { |
| |
588 jschar buffer[] = { jschar(i), '\0' }; |
| |
589 JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 1); |
| |
590 if (!s) |
| |
591 return false; |
| |
592 unitStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(); |
| |
593 } |
| |
594 |
| |
595 for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) { |
| |
596 jschar buffer[] = { FROM_SMALL_CHAR(i >> 6), FROM_SMALL_CHAR(i & 0x3F), '\0' }; |
| |
597 JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 2); |
| |
598 if (!s) |
| |
599 return false; |
| |
600 length2StaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(); |
| |
601 } |
| |
602 |
| |
603 for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) { |
| |
604 if (i < 10) { |
| |
605 intStaticTable[i] = unitStaticTable[i + '0']; |
| |
606 } else if (i < 100) { |
| |
607 size_t index = ((size_t)TO_SMALL_CHAR((i / 10) + '0') << 6) + |
| |
608 TO_SMALL_CHAR((i % 10) + '0'); |
| |
609 intStaticTable[i] = length2StaticTable[index]; |
| |
610 } else { |
| |
611 jschar buffer[] = { jschar('0' + (i / 100)), |
| |
612 jschar('0' + ((i / 10) % 10)), |
| |
613 jschar('0' + (i % 10)), |
| |
614 '\0' }; |
| |
615 JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 3); |
| |
616 if (!s) |
| |
617 return false; |
| |
618 intStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(); |
| |
619 } |
| |
620 } |
| |
621 |
| |
622 return true; |
| |
623 } |
| |
624 |
| |
625 void |
| |
626 StaticStrings::trace(JSTracer *trc) |
| |
627 { |
| |
628 /* These strings never change, so barriers are not needed. */ |
| |
629 |
| |
630 for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) |
| |
631 MarkPermanentAtom(trc, unitStaticTable[i], "unit-static-string"); |
| |
632 |
| |
633 for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) |
| |
634 MarkPermanentAtom(trc, length2StaticTable[i], "length2-static-string"); |
| |
635 |
| |
636 /* This may mark some strings more than once, but so be it. */ |
| |
637 for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) |
| |
638 MarkPermanentAtom(trc, intStaticTable[i], "int-static-string"); |
| |
639 } |
| |
640 |
| |
641 bool |
| |
642 StaticStrings::isStatic(JSAtom *atom) |
| |
643 { |
| |
644 const jschar *chars = atom->chars(); |
| |
645 switch (atom->length()) { |
| |
646 case 1: |
| |
647 return chars[0] < UNIT_STATIC_LIMIT; |
| |
648 case 2: |
| |
649 return fitsInSmallChar(chars[0]) && fitsInSmallChar(chars[1]); |
| |
650 case 3: |
| |
651 if ('1' <= chars[0] && chars[0] <= '9' && |
| |
652 '0' <= chars[1] && chars[1] <= '9' && |
| |
653 '0' <= chars[2] && chars[2] <= '9') { |
| |
654 int i = (chars[0] - '0') * 100 + |
| |
655 (chars[1] - '0') * 10 + |
| |
656 (chars[2] - '0'); |
| |
657 |
| |
658 return unsigned(i) < INT_STATIC_LIMIT; |
| |
659 } |
| |
660 return false; |
| |
661 default: |
| |
662 return false; |
| |
663 } |
| |
664 } |
| |
665 |
| |
666 #ifdef DEBUG |
| |
667 void |
| |
668 JSAtom::dump() |
| |
669 { |
| |
670 fprintf(stderr, "JSAtom* (%p) = ", (void *) this); |
| |
671 this->JSString::dump(); |
| |
672 } |
| |
673 #endif /* DEBUG */ |