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 : #include "jsstr.h"
8 :
9 : #include "mozilla/Attributes.h"
10 : #include "mozilla/Casting.h"
11 : #include "mozilla/CheckedInt.h"
12 : #include "mozilla/FloatingPoint.h"
13 : #include "mozilla/PodOperations.h"
14 : #include "mozilla/Range.h"
15 : #include "mozilla/TypeTraits.h"
16 : #include "mozilla/Unused.h"
17 :
18 : #include <ctype.h>
19 : #include <limits>
20 : #include <string.h>
21 :
22 : #include "jsapi.h"
23 : #include "jsarray.h"
24 : #include "jsatom.h"
25 : #include "jsbool.h"
26 : #include "jscntxt.h"
27 : #include "jsgc.h"
28 : #include "jsnum.h"
29 : #include "jsobj.h"
30 : #include "jsopcode.h"
31 : #include "jstypes.h"
32 : #include "jsutil.h"
33 :
34 : #include "builtin/RegExp.h"
35 : #include "jit/InlinableNatives.h"
36 : #include "js/Conversions.h"
37 : #include "js/UniquePtr.h"
38 : #if ENABLE_INTL_API
39 : #include "unicode/uchar.h"
40 : #include "unicode/unorm2.h"
41 : #endif
42 : #include "vm/GlobalObject.h"
43 : #include "vm/Interpreter.h"
44 : #include "vm/Opcodes.h"
45 : #include "vm/Printer.h"
46 : #include "vm/RegExpObject.h"
47 : #include "vm/RegExpStatics.h"
48 : #include "vm/StringBuffer.h"
49 : #include "vm/Unicode.h"
50 :
51 : #include "vm/Interpreter-inl.h"
52 : #include "vm/String-inl.h"
53 : #include "vm/StringObject-inl.h"
54 : #include "vm/TypeInference-inl.h"
55 :
56 : using namespace js;
57 : using namespace js::gc;
58 :
59 : using JS::Symbol;
60 : using JS::SymbolCode;
61 : using JS::ToInt32;
62 : using JS::ToUint32;
63 :
64 : using mozilla::AssertedCast;
65 : using mozilla::CheckedInt;
66 : using mozilla::IsNaN;
67 : using mozilla::IsNegativeZero;
68 : using mozilla::IsSame;
69 : using mozilla::Move;
70 : using mozilla::PodCopy;
71 : using mozilla::PodEqual;
72 : using mozilla::RangedPtr;
73 :
74 : using JS::AutoCheckCannotGC;
75 :
76 : static JSLinearString*
77 1374 : ArgToRootedString(JSContext* cx, const CallArgs& args, unsigned argno)
78 : {
79 1374 : if (argno >= args.length())
80 0 : return cx->names().undefined;
81 :
82 1374 : JSString* str = ToString<CanGC>(cx, args[argno]);
83 1374 : if (!str)
84 0 : return nullptr;
85 :
86 1374 : args[argno].setString(str);
87 1374 : return str->ensureLinear(cx);
88 : }
89 :
90 : /*
91 : * Forward declarations for URI encode/decode and helper routines
92 : */
93 : static bool
94 : str_decodeURI(JSContext* cx, unsigned argc, Value* vp);
95 :
96 : static bool
97 : str_decodeURI_Component(JSContext* cx, unsigned argc, Value* vp);
98 :
99 : static bool
100 : str_encodeURI(JSContext* cx, unsigned argc, Value* vp);
101 :
102 : static bool
103 : str_encodeURI_Component(JSContext* cx, unsigned argc, Value* vp);
104 :
105 : /*
106 : * Global string methods
107 : */
108 :
109 :
110 : /* ES5 B.2.1 */
111 : template <typename CharT>
112 : static Latin1Char*
113 0 : Escape(JSContext* cx, const CharT* chars, uint32_t length, uint32_t* newLengthOut)
114 : {
115 : static const uint8_t shouldPassThrough[128] = {
116 : 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
117 : 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
118 : 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1, /* !"#$%&'()*+,-./ */
119 : 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */
120 : 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */
121 : 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */
122 : 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */
123 : 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, /* pqrstuvwxyz{\}~ DEL */
124 : };
125 :
126 : /* Take a first pass and see how big the result string will need to be. */
127 0 : uint32_t newLength = length;
128 0 : for (size_t i = 0; i < length; i++) {
129 0 : char16_t ch = chars[i];
130 0 : if (ch < 128 && shouldPassThrough[ch])
131 0 : continue;
132 :
133 : /* The character will be encoded as %XX or %uXXXX. */
134 0 : newLength += (ch < 256) ? 2 : 5;
135 :
136 : /*
137 : * newlength is incremented by at most 5 on each iteration, so worst
138 : * case newlength == length * 6. This can't overflow.
139 : */
140 : static_assert(JSString::MAX_LENGTH < UINT32_MAX / 6,
141 : "newlength must not overflow");
142 : }
143 :
144 0 : Latin1Char* newChars = cx->pod_malloc<Latin1Char>(newLength + 1);
145 0 : if (!newChars)
146 0 : return nullptr;
147 :
148 : static const char digits[] = "0123456789ABCDEF";
149 :
150 : size_t i, ni;
151 0 : for (i = 0, ni = 0; i < length; i++) {
152 0 : char16_t ch = chars[i];
153 0 : if (ch < 128 && shouldPassThrough[ch]) {
154 0 : newChars[ni++] = ch;
155 0 : } else if (ch < 256) {
156 0 : newChars[ni++] = '%';
157 0 : newChars[ni++] = digits[ch >> 4];
158 0 : newChars[ni++] = digits[ch & 0xF];
159 : } else {
160 0 : newChars[ni++] = '%';
161 0 : newChars[ni++] = 'u';
162 0 : newChars[ni++] = digits[ch >> 12];
163 0 : newChars[ni++] = digits[(ch & 0xF00) >> 8];
164 0 : newChars[ni++] = digits[(ch & 0xF0) >> 4];
165 0 : newChars[ni++] = digits[ch & 0xF];
166 : }
167 : }
168 0 : MOZ_ASSERT(ni == newLength);
169 0 : newChars[newLength] = 0;
170 :
171 0 : *newLengthOut = newLength;
172 0 : return newChars;
173 : }
174 :
175 : static bool
176 0 : str_escape(JSContext* cx, unsigned argc, Value* vp)
177 : {
178 0 : CallArgs args = CallArgsFromVp(argc, vp);
179 :
180 0 : JSLinearString* str = ArgToRootedString(cx, args, 0);
181 0 : if (!str)
182 0 : return false;
183 :
184 0 : ScopedJSFreePtr<Latin1Char> newChars;
185 0 : uint32_t newLength = 0; // initialize to silence GCC warning
186 0 : if (str->hasLatin1Chars()) {
187 0 : AutoCheckCannotGC nogc;
188 0 : newChars = Escape(cx, str->latin1Chars(nogc), str->length(), &newLength);
189 : } else {
190 0 : AutoCheckCannotGC nogc;
191 0 : newChars = Escape(cx, str->twoByteChars(nogc), str->length(), &newLength);
192 : }
193 :
194 0 : if (!newChars)
195 0 : return false;
196 :
197 0 : JSString* res = NewString<CanGC>(cx, newChars.get(), newLength);
198 0 : if (!res)
199 0 : return false;
200 :
201 0 : newChars.forget();
202 0 : args.rval().setString(res);
203 0 : return true;
204 : }
205 :
206 : template <typename CharT>
207 : static inline bool
208 0 : Unhex4(const RangedPtr<const CharT> chars, char16_t* result)
209 : {
210 0 : char16_t a = chars[0],
211 0 : b = chars[1],
212 0 : c = chars[2],
213 0 : d = chars[3];
214 :
215 0 : if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d)))
216 0 : return false;
217 :
218 0 : *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d);
219 0 : return true;
220 : }
221 :
222 : template <typename CharT>
223 : static inline bool
224 0 : Unhex2(const RangedPtr<const CharT> chars, char16_t* result)
225 : {
226 0 : char16_t a = chars[0],
227 0 : b = chars[1];
228 :
229 0 : if (!(JS7_ISHEX(a) && JS7_ISHEX(b)))
230 0 : return false;
231 :
232 0 : *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b);
233 0 : return true;
234 : }
235 :
236 : template <typename CharT>
237 : static bool
238 0 : Unescape(StringBuffer& sb, const mozilla::Range<const CharT> chars)
239 : {
240 : /*
241 : * NB: use signed integers for length/index to allow simple length
242 : * comparisons without unsigned-underflow hazards.
243 : */
244 : static_assert(JSString::MAX_LENGTH <= INT_MAX, "String length must fit in a signed integer");
245 0 : int length = AssertedCast<int>(chars.length());
246 :
247 : /*
248 : * Note that the spec algorithm has been optimized to avoid building
249 : * a string in the case where no escapes are present.
250 : */
251 :
252 : /* Step 4. */
253 0 : int k = 0;
254 0 : bool building = false;
255 :
256 : /* Step 5. */
257 0 : while (k < length) {
258 : /* Step 6. */
259 0 : char16_t c = chars[k];
260 :
261 : /* Step 7. */
262 0 : if (c != '%')
263 0 : goto step_18;
264 :
265 : /* Step 8. */
266 0 : if (k > length - 6)
267 0 : goto step_14;
268 :
269 : /* Step 9. */
270 0 : if (chars[k + 1] != 'u')
271 0 : goto step_14;
272 :
273 : #define ENSURE_BUILDING \
274 : do { \
275 : if (!building) { \
276 : building = true; \
277 : if (!sb.reserve(length)) \
278 : return false; \
279 : sb.infallibleAppend(chars.begin().get(), k); \
280 : } \
281 : } while(false);
282 :
283 : /* Step 10-13. */
284 0 : if (Unhex4(chars.begin() + k + 2, &c)) {
285 0 : ENSURE_BUILDING;
286 0 : k += 5;
287 0 : goto step_18;
288 : }
289 :
290 : step_14:
291 : /* Step 14. */
292 0 : if (k > length - 3)
293 0 : goto step_18;
294 :
295 : /* Step 15-17. */
296 0 : if (Unhex2(chars.begin() + k + 1, &c)) {
297 0 : ENSURE_BUILDING;
298 0 : k += 2;
299 : }
300 :
301 : step_18:
302 0 : if (building && !sb.append(c))
303 0 : return false;
304 :
305 : /* Step 19. */
306 0 : k += 1;
307 : }
308 :
309 0 : return true;
310 : #undef ENSURE_BUILDING
311 : }
312 :
313 : /* ES5 B.2.2 */
314 : static bool
315 0 : str_unescape(JSContext* cx, unsigned argc, Value* vp)
316 : {
317 0 : CallArgs args = CallArgsFromVp(argc, vp);
318 :
319 : /* Step 1. */
320 0 : RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
321 0 : if (!str)
322 0 : return false;
323 :
324 : /* Step 3. */
325 0 : StringBuffer sb(cx);
326 0 : if (str->hasTwoByteChars() && !sb.ensureTwoByteChars())
327 0 : return false;
328 :
329 0 : if (str->hasLatin1Chars()) {
330 0 : AutoCheckCannotGC nogc;
331 0 : if (!Unescape(sb, str->latin1Range(nogc)))
332 0 : return false;
333 : } else {
334 0 : AutoCheckCannotGC nogc;
335 0 : if (!Unescape(sb, str->twoByteRange(nogc)))
336 0 : return false;
337 : }
338 :
339 : JSLinearString* result;
340 0 : if (!sb.empty()) {
341 0 : result = sb.finishString();
342 0 : if (!result)
343 0 : return false;
344 : } else {
345 0 : result = str;
346 : }
347 :
348 0 : args.rval().setString(result);
349 0 : return true;
350 : }
351 :
352 : #if JS_HAS_UNEVAL
353 : static bool
354 0 : str_uneval(JSContext* cx, unsigned argc, Value* vp)
355 : {
356 0 : CallArgs args = CallArgsFromVp(argc, vp);
357 0 : JSString* str = ValueToSource(cx, args.get(0));
358 0 : if (!str)
359 0 : return false;
360 :
361 0 : args.rval().setString(str);
362 0 : return true;
363 : }
364 : #endif
365 :
366 : static const JSFunctionSpec string_functions[] = {
367 : JS_FN(js_escape_str, str_escape, 1, JSPROP_RESOLVING),
368 : JS_FN(js_unescape_str, str_unescape, 1, JSPROP_RESOLVING),
369 : #if JS_HAS_UNEVAL
370 : JS_FN(js_uneval_str, str_uneval, 1, JSPROP_RESOLVING),
371 : #endif
372 : JS_FN(js_decodeURI_str, str_decodeURI, 1, JSPROP_RESOLVING),
373 : JS_FN(js_encodeURI_str, str_encodeURI, 1, JSPROP_RESOLVING),
374 : JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1, JSPROP_RESOLVING),
375 : JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1, JSPROP_RESOLVING),
376 :
377 : JS_FS_END
378 : };
379 :
380 : static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
381 :
382 : static bool
383 1 : str_enumerate(JSContext* cx, HandleObject obj)
384 : {
385 2 : RootedString str(cx, obj->as<StringObject>().unbox());
386 2 : RootedValue value(cx);
387 1 : for (size_t i = 0, length = str->length(); i < length; i++) {
388 0 : JSString* str1 = NewDependentString(cx, str, i, 1);
389 0 : if (!str1)
390 0 : return false;
391 0 : value.setString(str1);
392 0 : if (!DefineElement(cx, obj, i, value, nullptr, nullptr,
393 : STRING_ELEMENT_ATTRS | JSPROP_RESOLVING))
394 : {
395 0 : return false;
396 : }
397 : }
398 :
399 1 : return true;
400 : }
401 :
402 : static bool
403 15 : str_mayResolve(const JSAtomState&, jsid id, JSObject*)
404 : {
405 : // str_resolve ignores non-integer ids.
406 15 : return JSID_IS_INT(id);
407 : }
408 :
409 : static bool
410 2291 : str_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
411 : {
412 2291 : if (!JSID_IS_INT(id))
413 2291 : return true;
414 :
415 0 : RootedString str(cx, obj->as<StringObject>().unbox());
416 :
417 0 : int32_t slot = JSID_TO_INT(id);
418 0 : if ((size_t)slot < str->length()) {
419 0 : JSString* str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot));
420 0 : if (!str1)
421 0 : return false;
422 0 : RootedValue value(cx, StringValue(str1));
423 0 : if (!DefineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr,
424 : STRING_ELEMENT_ATTRS | JSPROP_RESOLVING))
425 : {
426 0 : return false;
427 : }
428 0 : *resolvedp = true;
429 : }
430 0 : return true;
431 : }
432 :
433 : static const ClassOps StringObjectClassOps = {
434 : nullptr, /* addProperty */
435 : nullptr, /* delProperty */
436 : nullptr, /* getProperty */
437 : nullptr, /* setProperty */
438 : str_enumerate,
439 : nullptr, /* newEnumerate */
440 : str_resolve,
441 : str_mayResolve
442 : };
443 :
444 : const Class StringObject::class_ = {
445 : js_String_str,
446 : JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
447 : JSCLASS_HAS_CACHED_PROTO(JSProto_String),
448 : &StringObjectClassOps
449 : };
450 :
451 : /*
452 : * Perform the initial |RequireObjectCoercible(thisv)| and |ToString(thisv)|
453 : * from nearly all String.prototype.* functions.
454 : */
455 : static MOZ_ALWAYS_INLINE JSString*
456 954 : ToStringForStringFunction(JSContext* cx, HandleValue thisv)
457 : {
458 954 : if (!CheckRecursionLimit(cx))
459 0 : return nullptr;
460 :
461 954 : if (thisv.isString())
462 953 : return thisv.toString();
463 :
464 1 : if (thisv.isObject()) {
465 1 : RootedObject obj(cx, &thisv.toObject());
466 1 : if (obj->is<StringObject>()) {
467 1 : StringObject* nobj = &obj->as<StringObject>();
468 : // We have to make sure that the ToPrimitive call from ToString
469 : // would be unobservable.
470 2 : if (HasNoToPrimitiveMethodPure(nobj, cx) &&
471 1 : HasNativeMethodPure(nobj, cx->names().toString, str_toString, cx))
472 : {
473 1 : return nobj->unbox();
474 : }
475 : }
476 0 : } else if (thisv.isNullOrUndefined()) {
477 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
478 0 : thisv.isNull() ? "null" : "undefined", "object");
479 0 : return nullptr;
480 : }
481 :
482 0 : return ToStringSlow<CanGC>(cx, thisv);
483 : }
484 :
485 : MOZ_ALWAYS_INLINE bool
486 0 : IsString(HandleValue v)
487 : {
488 0 : return v.isString() || (v.isObject() && v.toObject().is<StringObject>());
489 : }
490 :
491 : #if JS_HAS_TOSOURCE
492 :
493 : MOZ_ALWAYS_INLINE bool
494 0 : str_toSource_impl(JSContext* cx, const CallArgs& args)
495 : {
496 0 : MOZ_ASSERT(IsString(args.thisv()));
497 :
498 0 : Rooted<JSString*> str(cx, ToString<CanGC>(cx, args.thisv()));
499 0 : if (!str)
500 0 : return false;
501 :
502 0 : str = QuoteString(cx, str, '"');
503 0 : if (!str)
504 0 : return false;
505 :
506 0 : StringBuffer sb(cx);
507 0 : if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
508 0 : return false;
509 :
510 0 : str = sb.finishString();
511 0 : if (!str)
512 0 : return false;
513 0 : args.rval().setString(str);
514 0 : return true;
515 : }
516 :
517 : static bool
518 0 : str_toSource(JSContext* cx, unsigned argc, Value* vp)
519 : {
520 0 : CallArgs args = CallArgsFromVp(argc, vp);
521 0 : return CallNonGenericMethod<IsString, str_toSource_impl>(cx, args);
522 : }
523 :
524 : #endif /* JS_HAS_TOSOURCE */
525 :
526 : MOZ_ALWAYS_INLINE bool
527 0 : str_toString_impl(JSContext* cx, const CallArgs& args)
528 : {
529 0 : MOZ_ASSERT(IsString(args.thisv()));
530 :
531 0 : args.rval().setString(args.thisv().isString()
532 0 : ? args.thisv().toString()
533 0 : : args.thisv().toObject().as<StringObject>().unbox());
534 0 : return true;
535 : }
536 :
537 : bool
538 0 : js::str_toString(JSContext* cx, unsigned argc, Value* vp)
539 : {
540 0 : CallArgs args = CallArgsFromVp(argc, vp);
541 0 : return CallNonGenericMethod<IsString, str_toString_impl>(cx, args);
542 : }
543 :
544 : /*
545 : * Java-like string native methods.
546 : */
547 :
548 : JSString*
549 177 : js::SubstringKernel(JSContext* cx, HandleString str, int32_t beginInt, int32_t lengthInt)
550 : {
551 177 : MOZ_ASSERT(0 <= beginInt);
552 177 : MOZ_ASSERT(0 <= lengthInt);
553 177 : MOZ_ASSERT(uint32_t(beginInt) <= str->length());
554 177 : MOZ_ASSERT(uint32_t(lengthInt) <= str->length() - beginInt);
555 :
556 177 : uint32_t begin = beginInt;
557 177 : uint32_t len = lengthInt;
558 :
559 : /*
560 : * Optimization for one level deep ropes.
561 : * This is common for the following pattern:
562 : *
563 : * while() {
564 : * text = text.substr(0, x) + "bla" + text.substr(x)
565 : * test.charCodeAt(x + 1)
566 : * }
567 : */
568 177 : if (str->isRope()) {
569 0 : JSRope* rope = &str->asRope();
570 :
571 : /* Substring is totally in leftChild of rope. */
572 0 : if (begin + len <= rope->leftChild()->length())
573 0 : return NewDependentString(cx, rope->leftChild(), begin, len);
574 :
575 : /* Substring is totally in rightChild of rope. */
576 0 : if (begin >= rope->leftChild()->length()) {
577 0 : begin -= rope->leftChild()->length();
578 0 : return NewDependentString(cx, rope->rightChild(), begin, len);
579 : }
580 :
581 : /*
582 : * Requested substring is partly in the left and partly in right child.
583 : * Create a rope of substrings for both childs.
584 : */
585 0 : MOZ_ASSERT(begin < rope->leftChild()->length() &&
586 : begin + len > rope->leftChild()->length());
587 :
588 0 : size_t lhsLength = rope->leftChild()->length() - begin;
589 0 : size_t rhsLength = begin + len - rope->leftChild()->length();
590 :
591 0 : Rooted<JSRope*> ropeRoot(cx, rope);
592 0 : RootedString lhs(cx, NewDependentString(cx, ropeRoot->leftChild(), begin, lhsLength));
593 0 : if (!lhs)
594 0 : return nullptr;
595 :
596 0 : RootedString rhs(cx, NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength));
597 0 : if (!rhs)
598 0 : return nullptr;
599 :
600 0 : return JSRope::new_<CanGC>(cx, lhs, rhs, len);
601 : }
602 :
603 177 : return NewDependentString(cx, str, begin, len);
604 : }
605 :
606 : template <typename CharT> struct MaximumInlineLength;
607 :
608 : template<> struct MaximumInlineLength<Latin1Char> {
609 : static constexpr size_t value = JSFatInlineString::MAX_LENGTH_LATIN1;
610 : };
611 :
612 : template<> struct MaximumInlineLength<char16_t> {
613 : static constexpr size_t value = JSFatInlineString::MAX_LENGTH_TWO_BYTE;
614 : };
615 :
616 : // Character buffer class used for ToLowerCase and ToUpperCase operations.
617 : //
618 : // Case conversion operations normally return a string with the same length as
619 : // the input string. To avoid over-allocation, we optimistically allocate an
620 : // array with same size as the input string and only when we detect special
621 : // casing characters, which can change the output string length, we reallocate
622 : // the output buffer to the final string length.
623 : //
624 : // As a further mean to improve runtime performance, the character buffer
625 : // contains an inline storage, so we don't need to heap-allocate an array when
626 : // a JSInlineString will be used for the output string.
627 : //
628 : // Why not use mozilla::Vector instead? mozilla::Vector doesn't provide enough
629 : // fine-grained control to avoid over-allocation when (re)allocating for exact
630 : // buffer sizes. This led to visible performance regressions in ยต-benchmarks.
631 : template <typename CharT>
632 84 : class MOZ_NON_PARAM InlineCharBuffer
633 : {
634 : using CharPtr = UniquePtr<CharT[], JS::FreePolicy>;
635 : static constexpr size_t InlineCapacity = MaximumInlineLength<CharT>::value;
636 :
637 : CharT inlineStorage[InlineCapacity];
638 : CharPtr heapStorage;
639 :
640 : #ifdef DEBUG
641 : // In debug mode, we keep track of the requested string lengths to ensure
642 : // all character buffer methods are called in the correct order and with
643 : // the expected argument values.
644 : size_t lastRequestedLength = 0;
645 :
646 32 : void assertValidRequest(size_t expectedLastLength, size_t length) {
647 32 : MOZ_ASSERT(length > expectedLastLength, "cannot shrink requested length");
648 32 : MOZ_ASSERT(lastRequestedLength == expectedLastLength);
649 32 : lastRequestedLength = length;
650 32 : }
651 : #else
652 : void assertValidRequest(size_t expectedLastLength, size_t length) {}
653 : #endif
654 :
655 : public:
656 64 : CharT* get()
657 : {
658 64 : return heapStorage ? heapStorage.get() : inlineStorage;
659 : }
660 :
661 32 : bool maybeAlloc(JSContext* cx, size_t length)
662 : {
663 32 : assertValidRequest(0, length);
664 :
665 32 : if (length <= InlineCapacity)
666 30 : return true;
667 :
668 2 : MOZ_ASSERT(!heapStorage, "heap storage already allocated");
669 2 : heapStorage = cx->make_pod_array<CharT>(length + 1);
670 2 : return !!heapStorage;
671 : }
672 :
673 0 : bool maybeRealloc(JSContext* cx, size_t oldLength, size_t newLength)
674 : {
675 0 : assertValidRequest(oldLength, newLength);
676 :
677 0 : if (newLength <= InlineCapacity)
678 0 : return true;
679 :
680 0 : if (!heapStorage) {
681 0 : heapStorage = cx->make_pod_array<CharT>(newLength + 1);
682 0 : if (!heapStorage)
683 0 : return false;
684 :
685 0 : MOZ_ASSERT(oldLength <= InlineCapacity);
686 0 : PodCopy(heapStorage.get(), inlineStorage, oldLength);
687 0 : return true;
688 : }
689 :
690 0 : CharT* oldChars = heapStorage.release();
691 0 : CharT* newChars = cx->pod_realloc(oldChars, oldLength + 1, newLength + 1);
692 0 : if (!newChars) {
693 0 : js_free(oldChars);
694 0 : return false;
695 : }
696 :
697 0 : heapStorage.reset(newChars);
698 0 : return true;
699 : }
700 :
701 32 : JSString* toString(JSContext* cx, size_t length)
702 : {
703 32 : MOZ_ASSERT(length == lastRequestedLength);
704 :
705 32 : if (JSInlineString::lengthFits<CharT>(length)) {
706 30 : MOZ_ASSERT(!heapStorage,
707 : "expected only inline storage when length fits in inline string");
708 :
709 30 : mozilla::Range<const CharT> range(inlineStorage, length);
710 30 : return NewInlineString<CanGC>(cx, range);
711 : }
712 :
713 2 : MOZ_ASSERT(heapStorage, "heap storage was not allocated for non-inline string");
714 :
715 2 : heapStorage.get()[length] = '\0'; // Null-terminate
716 2 : JSString* res = NewStringDontDeflate<CanGC>(cx, heapStorage.get(), length);
717 2 : if (!res)
718 0 : return nullptr;
719 :
720 2 : mozilla::Unused << heapStorage.release();
721 2 : return res;
722 : }
723 : };
724 :
725 : /**
726 : * U+03A3 GREEK CAPITAL LETTER SIGMA has two different lower case mappings
727 : * depending on its context:
728 : * When it's preceded by a cased character and not followed by another cased
729 : * character, its lower case form is U+03C2 GREEK SMALL LETTER FINAL SIGMA.
730 : * Otherwise its lower case mapping is U+03C3 GREEK SMALL LETTER SIGMA.
731 : *
732 : * Unicode 9.0, ยง3.13 Default Case Algorithms
733 : */
734 : static char16_t
735 0 : Final_Sigma(const char16_t* chars, size_t length, size_t index)
736 : {
737 0 : MOZ_ASSERT(index < length);
738 0 : MOZ_ASSERT(chars[index] == unicode::GREEK_CAPITAL_LETTER_SIGMA);
739 0 : MOZ_ASSERT(unicode::ToLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA) ==
740 : unicode::GREEK_SMALL_LETTER_SIGMA);
741 :
742 : #if ENABLE_INTL_API
743 : // Tell the analysis the BinaryProperty.contains function pointer called by
744 : // u_hasBinaryProperty cannot GC.
745 0 : JS::AutoSuppressGCAnalysis nogc;
746 :
747 0 : bool precededByCased = false;
748 0 : for (size_t i = index; i > 0; ) {
749 0 : char16_t c = chars[--i];
750 0 : uint32_t codePoint = c;
751 0 : if (unicode::IsTrailSurrogate(c) && i > 0) {
752 0 : char16_t lead = chars[i - 1];
753 0 : if (unicode::IsLeadSurrogate(lead)) {
754 0 : codePoint = unicode::UTF16Decode(lead, c);
755 0 : i--;
756 : }
757 : }
758 :
759 : // Ignore any characters with the property Case_Ignorable.
760 : // NB: We need to skip over all Case_Ignorable characters, even when
761 : // they also have the Cased binary property.
762 0 : if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
763 0 : continue;
764 :
765 0 : precededByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
766 0 : break;
767 : }
768 0 : if (!precededByCased)
769 0 : return unicode::GREEK_SMALL_LETTER_SIGMA;
770 :
771 0 : bool followedByCased = false;
772 0 : for (size_t i = index + 1; i < length; ) {
773 0 : char16_t c = chars[i++];
774 0 : uint32_t codePoint = c;
775 0 : if (unicode::IsLeadSurrogate(c) && i < length) {
776 0 : char16_t trail = chars[i];
777 0 : if (unicode::IsTrailSurrogate(trail)) {
778 0 : codePoint = unicode::UTF16Decode(c, trail);
779 0 : i++;
780 : }
781 : }
782 :
783 : // Ignore any characters with the property Case_Ignorable.
784 : // NB: We need to skip over all Case_Ignorable characters, even when
785 : // they also have the Cased binary property.
786 0 : if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
787 0 : continue;
788 :
789 0 : followedByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
790 0 : break;
791 : }
792 0 : if (!followedByCased)
793 0 : return unicode::GREEK_SMALL_LETTER_FINAL_SIGMA;
794 : #endif
795 :
796 0 : return unicode::GREEK_SMALL_LETTER_SIGMA;
797 : }
798 :
799 : static Latin1Char
800 0 : Final_Sigma(const Latin1Char* chars, size_t length, size_t index)
801 : {
802 0 : MOZ_ASSERT_UNREACHABLE("U+03A3 is not a Latin-1 character");
803 : return 0;
804 : }
805 :
806 : // If |srcLength == destLength| is true, the destination buffer was allocated
807 : // with the same size as the source buffer. When we append characters which
808 : // have special casing mappings, we test |srcLength == destLength| to decide
809 : // if we need to back out and reallocate a sufficiently large destination
810 : // buffer. Otherwise the destination buffer was allocated with the correct
811 : // size to hold all lower case mapped characters, i.e.
812 : // |destLength == ToLowerCaseLength(srcChars, 0, srcLength)| is true.
813 : template <typename CharT>
814 : static size_t
815 27 : ToLowerCaseImpl(CharT* destChars, const CharT* srcChars, size_t startIndex, size_t srcLength,
816 : size_t destLength)
817 : {
818 27 : MOZ_ASSERT(startIndex < srcLength);
819 27 : MOZ_ASSERT(srcLength <= destLength);
820 26 : MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), srcLength == destLength);
821 :
822 27 : size_t j = startIndex;
823 245 : for (size_t i = startIndex; i < srcLength; i++) {
824 218 : char16_t c = srcChars[i];
825 : if (!IsSame<CharT, Latin1Char>::value) {
826 7 : if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
827 0 : char16_t trail = srcChars[i + 1];
828 0 : if (unicode::IsTrailSurrogate(trail)) {
829 0 : trail = unicode::ToLowerCaseNonBMPTrail(c, trail);
830 0 : destChars[j++] = c;
831 0 : destChars[j++] = trail;
832 0 : i++;
833 0 : continue;
834 : }
835 : }
836 :
837 : // Special case: U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
838 : // lowercases to <U+0069 U+0307>.
839 7 : if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
840 : // Return if the output buffer is too small.
841 0 : if (srcLength == destLength)
842 0 : return i;
843 :
844 0 : destChars[j++] = CharT('i');
845 0 : destChars[j++] = CharT(unicode::COMBINING_DOT_ABOVE);
846 0 : continue;
847 : }
848 :
849 : // Special case: U+03A3 GREEK CAPITAL LETTER SIGMA lowercases to
850 : // one of two codepoints depending on context.
851 7 : if (c == unicode::GREEK_CAPITAL_LETTER_SIGMA) {
852 0 : destChars[j++] = Final_Sigma(srcChars, srcLength, i);
853 0 : continue;
854 : }
855 : }
856 :
857 218 : c = unicode::ToLowerCase(c);
858 211 : MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
859 218 : destChars[j++] = c;
860 : }
861 :
862 27 : MOZ_ASSERT(j == destLength);
863 27 : return srcLength;
864 : }
865 :
866 : static size_t
867 0 : ToLowerCaseLength(const char16_t* chars, size_t startIndex, size_t length)
868 : {
869 0 : size_t lowerLength = length;
870 0 : for (size_t i = startIndex; i < length; i++) {
871 0 : char16_t c = chars[i];
872 :
873 : // U+0130 is lowercased to the two-element sequence <U+0069 U+0307>.
874 0 : if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE)
875 0 : lowerLength += 1;
876 : }
877 0 : return lowerLength;
878 : }
879 :
880 : static size_t
881 0 : ToLowerCaseLength(const Latin1Char* chars, size_t startIndex, size_t length)
882 : {
883 0 : MOZ_ASSERT_UNREACHABLE("never called for Latin-1 strings");
884 : return 0;
885 : }
886 :
887 : template <typename CharT>
888 : static JSString*
889 37 : ToLowerCase(JSContext* cx, JSLinearString* str)
890 : {
891 : // Unlike toUpperCase, toLowerCase has the nice invariant that if the
892 : // input is a Latin-1 string, the output is also a Latin-1 string.
893 :
894 74 : InlineCharBuffer<CharT> newChars;
895 :
896 37 : const size_t length = str->length();
897 : size_t resultLength;
898 : {
899 64 : AutoCheckCannotGC nogc;
900 37 : const CharT* chars = str->chars<CharT>(nogc);
901 :
902 : // We don't need extra special casing checks in the loop below,
903 : // because U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+03A3
904 : // GREEK CAPITAL LETTER SIGMA already have simple lower case mappings.
905 37 : MOZ_ASSERT(unicode::CanLowerCase(unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE),
906 : "U+0130 has a simple lower case mapping");
907 37 : MOZ_ASSERT(unicode::CanLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA),
908 : "U+03A3 has a simple lower case mapping");
909 :
910 : // One element Latin-1 strings can be directly retrieved from the
911 : // static strings cache.
912 : if (IsSame<CharT, Latin1Char>::value) {
913 28 : if (length == 1) {
914 0 : char16_t lower = unicode::ToLowerCase(chars[0]);
915 0 : MOZ_ASSERT(lower <= JSString::MAX_LATIN1_CHAR);
916 0 : MOZ_ASSERT(StaticStrings::hasUnit(lower));
917 :
918 0 : return cx->staticStrings().getUnit(lower);
919 : }
920 : }
921 :
922 : // Look for the first character that changes when lowercased.
923 37 : size_t i = 0;
924 161 : for (; i < length; i++) {
925 89 : char16_t c = chars[i];
926 : if (!IsSame<CharT, Latin1Char>::value) {
927 47 : if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
928 0 : char16_t trail = chars[i + 1];
929 0 : if (unicode::IsTrailSurrogate(trail)) {
930 0 : if (unicode::CanLowerCaseNonBMP(c, trail))
931 0 : break;
932 :
933 0 : i++;
934 0 : continue;
935 : }
936 : }
937 : }
938 89 : if (unicode::CanLowerCase(c))
939 27 : break;
940 : }
941 :
942 : // If no character needs to change, return the input string.
943 37 : if (i == length)
944 10 : return str;
945 :
946 27 : resultLength = length;
947 27 : if (!newChars.maybeAlloc(cx, resultLength))
948 0 : return nullptr;
949 :
950 27 : PodCopy(newChars.get(), chars, i);
951 :
952 27 : size_t readChars = ToLowerCaseImpl(newChars.get(), chars, i, length, resultLength);
953 27 : if (readChars < length) {
954 0 : MOZ_ASSERT((!IsSame<CharT, Latin1Char>::value),
955 : "Latin-1 strings don't have special lower case mappings");
956 0 : resultLength = ToLowerCaseLength(chars, readChars, length);
957 :
958 0 : if (!newChars.maybeRealloc(cx, length, resultLength))
959 0 : return nullptr;
960 :
961 0 : MOZ_ALWAYS_TRUE(length ==
962 : ToLowerCaseImpl(newChars.get(), chars, readChars, length, resultLength));
963 : }
964 : }
965 :
966 27 : return newChars.toString(cx, resultLength);
967 : }
968 :
969 : JSString*
970 37 : js::StringToLowerCase(JSContext* cx, HandleLinearString string)
971 : {
972 37 : if (string->hasLatin1Chars())
973 28 : return ToLowerCase<Latin1Char>(cx, string);
974 9 : return ToLowerCase<char16_t>(cx, string);
975 : }
976 :
977 : bool
978 37 : js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
979 : {
980 37 : CallArgs args = CallArgsFromVp(argc, vp);
981 :
982 74 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
983 37 : if (!str)
984 0 : return false;
985 :
986 74 : RootedLinearString linear(cx, str->ensureLinear(cx));
987 37 : if (!linear)
988 0 : return false;
989 :
990 37 : JSString* result = StringToLowerCase(cx, linear);
991 37 : if (!result)
992 0 : return false;
993 :
994 37 : args.rval().setString(result);
995 37 : return true;
996 : }
997 :
998 : #if !EXPOSE_INTL_API
999 : bool
1000 : js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
1001 : {
1002 : CallArgs args = CallArgsFromVp(argc, vp);
1003 :
1004 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
1005 : if (!str)
1006 : return false;
1007 :
1008 : /*
1009 : * Forcefully ignore the first (or any) argument and return toLowerCase(),
1010 : * ECMA has reserved that argument, presumably for defining the locale.
1011 : */
1012 : if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) {
1013 : RootedValue result(cx);
1014 : if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result))
1015 : return false;
1016 :
1017 : args.rval().set(result);
1018 : return true;
1019 : }
1020 :
1021 : RootedLinearString linear(cx, str->ensureLinear(cx));
1022 : if (!linear)
1023 : return false;
1024 :
1025 : JSString* result = StringToLowerCase(cx, linear);
1026 : if (!result)
1027 : return false;
1028 :
1029 : args.rval().setString(result);
1030 : return true;
1031 : }
1032 : #endif /* !EXPOSE_INTL_API */
1033 :
1034 : static inline bool
1035 0 : CanUpperCaseSpecialCasing(Latin1Char charCode)
1036 : {
1037 : // Handle U+00DF LATIN SMALL LETTER SHARP S inline, all other Latin-1
1038 : // characters don't have special casing rules.
1039 0 : MOZ_ASSERT_IF(charCode != unicode::LATIN_SMALL_LETTER_SHARP_S,
1040 : !unicode::CanUpperCaseSpecialCasing(charCode));
1041 :
1042 0 : return charCode == unicode::LATIN_SMALL_LETTER_SHARP_S;
1043 : }
1044 :
1045 : static inline bool
1046 0 : CanUpperCaseSpecialCasing(char16_t charCode)
1047 : {
1048 0 : return unicode::CanUpperCaseSpecialCasing(charCode);
1049 : }
1050 :
1051 : static inline size_t
1052 0 : LengthUpperCaseSpecialCasing(Latin1Char charCode)
1053 : {
1054 : // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
1055 0 : MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
1056 :
1057 0 : return 2;
1058 : }
1059 :
1060 : static inline size_t
1061 0 : LengthUpperCaseSpecialCasing(char16_t charCode)
1062 : {
1063 0 : MOZ_ASSERT(CanUpperCaseSpecialCasing(charCode));
1064 :
1065 0 : return unicode::LengthUpperCaseSpecialCasing(charCode);
1066 : }
1067 :
1068 : static inline void
1069 0 : AppendUpperCaseSpecialCasing(char16_t charCode, Latin1Char* elements, size_t* index)
1070 : {
1071 : // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
1072 0 : MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
1073 : static_assert('S' <= JSString::MAX_LATIN1_CHAR, "'S' is a Latin-1 character");
1074 :
1075 0 : elements[(*index)++] = 'S';
1076 0 : elements[(*index)++] = 'S';
1077 0 : }
1078 :
1079 : static inline void
1080 0 : AppendUpperCaseSpecialCasing(char16_t charCode, char16_t* elements, size_t* index)
1081 : {
1082 0 : unicode::AppendUpperCaseSpecialCasing(charCode, elements, index);
1083 0 : }
1084 :
1085 : // See ToLowerCaseImpl for an explanation of the parameters.
1086 : template <typename DestChar, typename SrcChar>
1087 : static size_t
1088 5 : ToUpperCaseImpl(DestChar* destChars, const SrcChar* srcChars, size_t startIndex, size_t srcLength,
1089 : size_t destLength)
1090 : {
1091 : static_assert(IsSame<SrcChar, Latin1Char>::value || !IsSame<DestChar, Latin1Char>::value,
1092 : "cannot write non-Latin-1 characters into Latin-1 string");
1093 5 : MOZ_ASSERT(startIndex < srcLength);
1094 5 : MOZ_ASSERT(srcLength <= destLength);
1095 :
1096 5 : size_t j = startIndex;
1097 63 : for (size_t i = startIndex; i < srcLength; i++) {
1098 58 : char16_t c = srcChars[i];
1099 : if (!IsSame<DestChar, Latin1Char>::value) {
1100 0 : if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
1101 0 : char16_t trail = srcChars[i + 1];
1102 0 : if (unicode::IsTrailSurrogate(trail)) {
1103 0 : trail = unicode::ToUpperCaseNonBMPTrail(c, trail);
1104 0 : destChars[j++] = c;
1105 0 : destChars[j++] = trail;
1106 0 : i++;
1107 0 : continue;
1108 : }
1109 : }
1110 : }
1111 :
1112 58 : if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<SrcChar>(c)))) {
1113 : // Return if the output buffer is too small.
1114 0 : if (srcLength == destLength)
1115 0 : return i;
1116 :
1117 0 : AppendUpperCaseSpecialCasing(c, destChars, &j);
1118 0 : continue;
1119 : }
1120 :
1121 58 : c = unicode::ToUpperCase(c);
1122 58 : MOZ_ASSERT_IF((IsSame<DestChar, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
1123 58 : destChars[j++] = c;
1124 : }
1125 :
1126 5 : MOZ_ASSERT(j == destLength);
1127 5 : return srcLength;
1128 : }
1129 :
1130 : // Explicit instantiation so we don't hit the static_assert from above.
1131 : static bool
1132 0 : ToUpperCaseImpl(Latin1Char* destChars, const char16_t* srcChars, size_t startIndex,
1133 : size_t srcLength, size_t destLength)
1134 : {
1135 0 : MOZ_ASSERT_UNREACHABLE("cannot write non-Latin-1 characters into Latin-1 string");
1136 : return false;
1137 : }
1138 :
1139 : template <typename CharT>
1140 : static size_t
1141 0 : ToUpperCaseLength(const CharT* chars, size_t startIndex, size_t length)
1142 : {
1143 0 : size_t upperLength = length;
1144 0 : for (size_t i = startIndex; i < length; i++) {
1145 0 : char16_t c = chars[i];
1146 :
1147 0 : if (c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c)))
1148 0 : upperLength += LengthUpperCaseSpecialCasing(static_cast<CharT>(c)) - 1;
1149 : }
1150 0 : return upperLength;
1151 : }
1152 :
1153 : template <typename DestChar, typename SrcChar>
1154 : static inline void
1155 0 : CopyChars(DestChar* destChars, const SrcChar* srcChars, size_t length)
1156 : {
1157 : static_assert(!IsSame<DestChar, SrcChar>::value, "PodCopy is used for the same type case");
1158 0 : for (size_t i = 0; i < length; i++)
1159 0 : destChars[i] = srcChars[i];
1160 0 : }
1161 :
1162 : template <typename CharT>
1163 : static inline void
1164 5 : CopyChars(CharT* destChars, const CharT* srcChars, size_t length)
1165 : {
1166 5 : PodCopy(destChars, srcChars, length);
1167 5 : }
1168 :
1169 : template <typename DestChar, typename SrcChar>
1170 : static inline bool
1171 5 : ToUpperCase(JSContext* cx, InlineCharBuffer<DestChar>& newChars, const SrcChar* chars,
1172 : size_t startIndex, size_t length, size_t* resultLength)
1173 : {
1174 5 : MOZ_ASSERT(startIndex < length);
1175 :
1176 5 : *resultLength = length;
1177 5 : if (!newChars.maybeAlloc(cx, length))
1178 0 : return false;
1179 :
1180 5 : CopyChars(newChars.get(), chars, startIndex);
1181 :
1182 5 : size_t readChars = ToUpperCaseImpl(newChars.get(), chars, startIndex, length, length);
1183 5 : if (readChars < length) {
1184 0 : size_t actualLength = ToUpperCaseLength(chars, readChars, length);
1185 :
1186 0 : *resultLength = actualLength;
1187 0 : if (!newChars.maybeRealloc(cx, length, actualLength))
1188 0 : return false;
1189 :
1190 0 : MOZ_ALWAYS_TRUE(length ==
1191 : ToUpperCaseImpl(newChars.get(), chars, readChars, length, actualLength));
1192 : }
1193 :
1194 5 : return true;
1195 : }
1196 :
1197 : template <typename CharT>
1198 : static JSString*
1199 6 : ToUpperCase(JSContext* cx, JSLinearString* str)
1200 : {
1201 : using Latin1Buffer = InlineCharBuffer<Latin1Char>;
1202 : using TwoByteBuffer = InlineCharBuffer<char16_t>;
1203 :
1204 12 : mozilla::MaybeOneOf<Latin1Buffer, TwoByteBuffer> newChars;
1205 6 : const size_t length = str->length();
1206 : size_t resultLength;
1207 : {
1208 11 : AutoCheckCannotGC nogc;
1209 6 : const CharT* chars = str->chars<CharT>(nogc);
1210 :
1211 : // Most one element Latin-1 strings can be directly retrieved from the
1212 : // static strings cache.
1213 : if (IsSame<CharT, Latin1Char>::value) {
1214 6 : if (length == 1) {
1215 1 : Latin1Char c = chars[0];
1216 1 : if (c != unicode::MICRO_SIGN &&
1217 1 : c != unicode::LATIN_SMALL_LETTER_Y_WITH_DIAERESIS &&
1218 : c != unicode::LATIN_SMALL_LETTER_SHARP_S)
1219 : {
1220 1 : char16_t upper = unicode::ToUpperCase(c);
1221 1 : MOZ_ASSERT(upper <= JSString::MAX_LATIN1_CHAR);
1222 1 : MOZ_ASSERT(StaticStrings::hasUnit(upper));
1223 :
1224 1 : return cx->staticStrings().getUnit(upper);
1225 : }
1226 :
1227 0 : MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR ||
1228 : CanUpperCaseSpecialCasing(c));
1229 : }
1230 : }
1231 :
1232 : // Look for the first character that changes when uppercased.
1233 5 : size_t i = 0;
1234 13 : for (; i < length; i++) {
1235 9 : char16_t c = chars[i];
1236 : if (!IsSame<CharT, Latin1Char>::value) {
1237 0 : if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
1238 0 : char16_t trail = chars[i + 1];
1239 0 : if (unicode::IsTrailSurrogate(trail)) {
1240 0 : if (unicode::CanUpperCaseNonBMP(c, trail))
1241 0 : break;
1242 :
1243 0 : i++;
1244 0 : continue;
1245 : }
1246 : }
1247 : }
1248 9 : if (unicode::CanUpperCase(c))
1249 5 : break;
1250 4 : if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c))))
1251 0 : break;
1252 : }
1253 :
1254 : // If no character needs to change, return the input string.
1255 5 : if (i == length)
1256 0 : return str;
1257 :
1258 : // The string changes when uppercased, so we must create a new string.
1259 : // Can it be Latin-1?
1260 : //
1261 : // If the original string is Latin-1, it can -- unless the string
1262 : // contains U+00B5 MICRO SIGN or U+00FF SMALL LETTER Y WITH DIAERESIS,
1263 : // the only Latin-1 codepoints that don't uppercase within Latin-1.
1264 : // Search for those codepoints to decide whether the new string can be
1265 : // Latin-1.
1266 : // If the original string is a two-byte string, its uppercase form is
1267 : // so rarely Latin-1 that we don't even consider creating a new
1268 : // Latin-1 string.
1269 : bool resultIsLatin1;
1270 : if (IsSame<CharT, Latin1Char>::value) {
1271 5 : resultIsLatin1 = true;
1272 63 : for (size_t j = i; j < length; j++) {
1273 58 : Latin1Char c = chars[j];
1274 58 : if (c == unicode::MICRO_SIGN ||
1275 : c == unicode::LATIN_SMALL_LETTER_Y_WITH_DIAERESIS)
1276 : {
1277 0 : MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR);
1278 0 : resultIsLatin1 = false;
1279 0 : break;
1280 : } else {
1281 58 : MOZ_ASSERT(unicode::ToUpperCase(c) <= JSString::MAX_LATIN1_CHAR);
1282 : }
1283 : }
1284 : } else {
1285 0 : resultIsLatin1 = false;
1286 : }
1287 :
1288 5 : if (resultIsLatin1) {
1289 5 : newChars.construct<Latin1Buffer>();
1290 :
1291 5 : if (!ToUpperCase(cx, newChars.ref<Latin1Buffer>(), chars, i, length, &resultLength))
1292 0 : return nullptr;
1293 : } else {
1294 0 : newChars.construct<TwoByteBuffer>();
1295 :
1296 0 : if (!ToUpperCase(cx, newChars.ref<TwoByteBuffer>(), chars, i, length, &resultLength))
1297 0 : return nullptr;
1298 : }
1299 : }
1300 :
1301 5 : return newChars.constructed<Latin1Buffer>()
1302 5 : ? newChars.ref<Latin1Buffer>().toString(cx, resultLength)
1303 10 : : newChars.ref<TwoByteBuffer>().toString(cx, resultLength);
1304 : }
1305 :
1306 : JSString*
1307 6 : js::StringToUpperCase(JSContext* cx, HandleLinearString string)
1308 : {
1309 6 : if (string->hasLatin1Chars())
1310 6 : return ToUpperCase<Latin1Char>(cx, string);
1311 0 : return ToUpperCase<char16_t>(cx, string);
1312 : }
1313 :
1314 : bool
1315 6 : js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp)
1316 : {
1317 6 : CallArgs args = CallArgsFromVp(argc, vp);
1318 :
1319 12 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
1320 6 : if (!str)
1321 0 : return false;
1322 :
1323 12 : RootedLinearString linear(cx, str->ensureLinear(cx));
1324 6 : if (!linear)
1325 0 : return false;
1326 :
1327 6 : JSString* result = StringToUpperCase(cx, linear);
1328 6 : if (!result)
1329 0 : return false;
1330 :
1331 6 : args.rval().setString(result);
1332 6 : return true;
1333 : }
1334 :
1335 : #if !EXPOSE_INTL_API
1336 : bool
1337 : js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
1338 : {
1339 : CallArgs args = CallArgsFromVp(argc, vp);
1340 :
1341 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
1342 : if (!str)
1343 : return false;
1344 :
1345 : /*
1346 : * Forcefully ignore the first (or any) argument and return toUpperCase(),
1347 : * ECMA has reserved that argument, presumably for defining the locale.
1348 : */
1349 : if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) {
1350 : RootedValue result(cx);
1351 : if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result))
1352 : return false;
1353 :
1354 : args.rval().set(result);
1355 : return true;
1356 : }
1357 :
1358 : RootedLinearString linear(cx, str->ensureLinear(cx));
1359 : if (!linear)
1360 : return false;
1361 :
1362 : JSString* result = StringToUpperCase(cx, linear);
1363 : if (!result)
1364 : return false;
1365 :
1366 : args.rval().setString(result);
1367 : return true;
1368 : }
1369 : #endif /* !EXPOSE_INTL_API */
1370 :
1371 : #if !EXPOSE_INTL_API
1372 : bool
1373 : js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
1374 : {
1375 : CallArgs args = CallArgsFromVp(argc, vp);
1376 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
1377 : if (!str)
1378 : return false;
1379 :
1380 : RootedString thatStr(cx, ToString<CanGC>(cx, args.get(0)));
1381 : if (!thatStr)
1382 : return false;
1383 :
1384 : if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) {
1385 : RootedValue result(cx);
1386 : if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result))
1387 : return false;
1388 :
1389 : args.rval().set(result);
1390 : return true;
1391 : }
1392 :
1393 : int32_t result;
1394 : if (!CompareStrings(cx, str, thatStr, &result))
1395 : return false;
1396 :
1397 : args.rval().setInt32(result);
1398 : return true;
1399 : }
1400 : #endif
1401 :
1402 : #if EXPOSE_INTL_API
1403 : // ES2017 draft rev 45e890512fd77add72cc0ee742785f9f6f6482de
1404 : // 21.1.3.12 String.prototype.normalize ( [ form ] )
1405 : bool
1406 0 : js::str_normalize(JSContext* cx, unsigned argc, Value* vp)
1407 : {
1408 0 : CallArgs args = CallArgsFromVp(argc, vp);
1409 :
1410 : // Steps 1-2.
1411 0 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
1412 0 : if (!str)
1413 0 : return false;
1414 :
1415 : enum NormalizationForm {
1416 : NFC, NFD, NFKC, NFKD
1417 : };
1418 :
1419 : NormalizationForm form;
1420 0 : if (!args.hasDefined(0)) {
1421 : // Step 3.
1422 0 : form = NFC;
1423 : } else {
1424 : // Step 4.
1425 0 : RootedLinearString formStr(cx, ArgToRootedString(cx, args, 0));
1426 0 : if (!formStr)
1427 0 : return false;
1428 :
1429 : // Step 5.
1430 0 : if (EqualStrings(formStr, cx->names().NFC)) {
1431 0 : form = NFC;
1432 0 : } else if (EqualStrings(formStr, cx->names().NFD)) {
1433 0 : form = NFD;
1434 0 : } else if (EqualStrings(formStr, cx->names().NFKC)) {
1435 0 : form = NFKC;
1436 0 : } else if (EqualStrings(formStr, cx->names().NFKD)) {
1437 0 : form = NFKD;
1438 : } else {
1439 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_NORMALIZE_FORM);
1440 0 : return false;
1441 : }
1442 : }
1443 :
1444 : // Latin-1 strings are already in Normalization Form C.
1445 0 : if (form == NFC && str->hasLatin1Chars()) {
1446 : // Step 7.
1447 0 : args.rval().setString(str);
1448 0 : return true;
1449 : }
1450 :
1451 : // Step 6.
1452 0 : AutoStableStringChars stableChars(cx);
1453 0 : if (!stableChars.initTwoByte(cx, str))
1454 0 : return false;
1455 :
1456 0 : mozilla::Range<const char16_t> srcChars = stableChars.twoByteRange();
1457 :
1458 : // The unorm2_getXXXInstance() methods return a shared instance which must
1459 : // not be deleted.
1460 0 : UErrorCode status = U_ZERO_ERROR;
1461 : const UNormalizer2* normalizer;
1462 0 : if (form == NFC) {
1463 0 : normalizer = unorm2_getNFCInstance(&status);
1464 0 : } else if (form == NFD) {
1465 0 : normalizer = unorm2_getNFDInstance(&status);
1466 0 : } else if (form == NFKC) {
1467 0 : normalizer = unorm2_getNFKCInstance(&status);
1468 : } else {
1469 0 : MOZ_ASSERT(form == NFKD);
1470 0 : normalizer = unorm2_getNFKDInstance(&status);
1471 : }
1472 0 : if (U_FAILURE(status)) {
1473 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1474 0 : return false;
1475 : }
1476 :
1477 0 : int32_t spanLength = unorm2_spanQuickCheckYes(normalizer,
1478 0 : srcChars.begin().get(), srcChars.length(),
1479 0 : &status);
1480 0 : if (U_FAILURE(status)) {
1481 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1482 0 : return false;
1483 : }
1484 0 : MOZ_ASSERT(0 <= spanLength && size_t(spanLength) <= srcChars.length());
1485 :
1486 : // Return if the input string is already normalized.
1487 0 : if (size_t(spanLength) == srcChars.length()) {
1488 : // Step 7.
1489 0 : args.rval().setString(str);
1490 0 : return true;
1491 : }
1492 :
1493 : static const size_t INLINE_CAPACITY = 32;
1494 :
1495 0 : Vector<char16_t, INLINE_CAPACITY> chars(cx);
1496 0 : if (!chars.resize(Max(INLINE_CAPACITY, srcChars.length())))
1497 0 : return false;
1498 :
1499 : // Copy the already normalized prefix.
1500 0 : if (spanLength > 0)
1501 0 : PodCopy(chars.begin(), srcChars.begin().get(), size_t(spanLength));
1502 :
1503 0 : mozilla::RangedPtr<const char16_t> remainingStart = srcChars.begin() + spanLength;
1504 0 : size_t remainingLength = srcChars.length() - size_t(spanLength);
1505 :
1506 0 : int32_t size = unorm2_normalizeSecondAndAppend(normalizer,
1507 0 : chars.begin(), spanLength, chars.length(),
1508 0 : remainingStart.get(), remainingLength, &status);
1509 0 : if (status == U_BUFFER_OVERFLOW_ERROR) {
1510 0 : MOZ_ASSERT(size >= 0);
1511 0 : if (!chars.resize(size))
1512 0 : return false;
1513 0 : status = U_ZERO_ERROR;
1514 : #ifdef DEBUG
1515 : int32_t finalSize =
1516 : #endif
1517 0 : unorm2_normalizeSecondAndAppend(normalizer,
1518 0 : chars.begin(), spanLength, chars.length(),
1519 0 : remainingStart.get(), remainingLength, &status);
1520 0 : MOZ_ASSERT_IF(!U_FAILURE(status), size == finalSize);
1521 : }
1522 0 : if (U_FAILURE(status)) {
1523 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
1524 0 : return false;
1525 : }
1526 :
1527 0 : MOZ_ASSERT(size >= 0);
1528 0 : JSString* ns = NewStringCopyN<CanGC>(cx, chars.begin(), size);
1529 0 : if (!ns)
1530 0 : return false;
1531 :
1532 : // Step 7.
1533 0 : args.rval().setString(ns);
1534 0 : return true;
1535 : }
1536 : #endif
1537 :
1538 : bool
1539 0 : js::str_charAt(JSContext* cx, unsigned argc, Value* vp)
1540 : {
1541 0 : CallArgs args = CallArgsFromVp(argc, vp);
1542 :
1543 0 : RootedString str(cx);
1544 : size_t i;
1545 0 : if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
1546 0 : str = args.thisv().toString();
1547 0 : i = size_t(args[0].toInt32());
1548 0 : if (i >= str->length())
1549 0 : goto out_of_range;
1550 : } else {
1551 0 : str = ToStringForStringFunction(cx, args.thisv());
1552 0 : if (!str)
1553 0 : return false;
1554 :
1555 0 : double d = 0.0;
1556 0 : if (args.length() > 0 && !ToInteger(cx, args[0], &d))
1557 0 : return false;
1558 :
1559 0 : if (d < 0 || str->length() <= d)
1560 0 : goto out_of_range;
1561 0 : i = size_t(d);
1562 : }
1563 :
1564 0 : str = cx->staticStrings().getUnitStringForElement(cx, str, i);
1565 0 : if (!str)
1566 0 : return false;
1567 0 : args.rval().setString(str);
1568 0 : return true;
1569 :
1570 : out_of_range:
1571 0 : args.rval().setString(cx->runtime()->emptyString);
1572 0 : return true;
1573 : }
1574 :
1575 : bool
1576 14324 : js::str_charCodeAt_impl(JSContext* cx, HandleString string, HandleValue index, MutableHandleValue res)
1577 : {
1578 28648 : RootedString str(cx);
1579 : size_t i;
1580 14324 : if (index.isInt32()) {
1581 14324 : i = index.toInt32();
1582 14324 : if (i >= string->length())
1583 0 : goto out_of_range;
1584 : } else {
1585 0 : double d = 0.0;
1586 0 : if (!ToInteger(cx, index, &d))
1587 0 : return false;
1588 : // check whether d is negative as size_t is unsigned
1589 0 : if (d < 0 || string->length() <= d )
1590 0 : goto out_of_range;
1591 0 : i = size_t(d);
1592 : }
1593 : char16_t c;
1594 14324 : if (!string->getChar(cx, i , &c))
1595 0 : return false;
1596 14324 : res.setInt32(c);
1597 14324 : return true;
1598 :
1599 : out_of_range:
1600 0 : res.setNaN();
1601 0 : return true;
1602 : }
1603 :
1604 : bool
1605 14324 : js::str_charCodeAt(JSContext* cx, unsigned argc, Value* vp)
1606 : {
1607 14324 : CallArgs args = CallArgsFromVp(argc, vp);
1608 28648 : RootedString str(cx);
1609 28648 : RootedValue index(cx);
1610 14324 : if (args.thisv().isString()) {
1611 14324 : str = args.thisv().toString();
1612 : } else {
1613 0 : str = ToStringForStringFunction(cx, args.thisv());
1614 0 : if (!str)
1615 0 : return false;
1616 : }
1617 14324 : if (args.length() != 0)
1618 14324 : index = args[0];
1619 : else
1620 0 : index.setInt32(0);
1621 :
1622 14324 : return js::str_charCodeAt_impl(cx, str, index, args.rval());
1623 : }
1624 :
1625 : /*
1626 : * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen.
1627 : * The patlen argument must be positive and no greater than sBMHPatLenMax.
1628 : *
1629 : * Return the index of pat in text, or -1 if not found.
1630 : */
1631 : static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */
1632 : static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */
1633 : static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */
1634 :
1635 : template <typename TextChar, typename PatChar>
1636 : static int
1637 0 : BoyerMooreHorspool(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t patLen)
1638 : {
1639 0 : MOZ_ASSERT(0 < patLen && patLen <= sBMHPatLenMax);
1640 :
1641 : uint8_t skip[sBMHCharSetSize];
1642 0 : for (uint32_t i = 0; i < sBMHCharSetSize; i++)
1643 0 : skip[i] = uint8_t(patLen);
1644 :
1645 0 : uint32_t patLast = patLen - 1;
1646 0 : for (uint32_t i = 0; i < patLast; i++) {
1647 0 : char16_t c = pat[i];
1648 0 : if (c >= sBMHCharSetSize)
1649 0 : return sBMHBadPattern;
1650 0 : skip[c] = uint8_t(patLast - i);
1651 : }
1652 :
1653 0 : for (uint32_t k = patLast; k < textLen; ) {
1654 0 : for (uint32_t i = k, j = patLast; ; i--, j--) {
1655 0 : if (text[i] != pat[j])
1656 0 : break;
1657 0 : if (j == 0)
1658 0 : return static_cast<int>(i); /* safe: max string size */
1659 : }
1660 :
1661 0 : char16_t c = text[k];
1662 0 : k += (c >= sBMHCharSetSize) ? patLen : skip[c];
1663 : }
1664 0 : return -1;
1665 : }
1666 :
1667 : template <typename TextChar, typename PatChar>
1668 : struct MemCmp {
1669 : typedef uint32_t Extent;
1670 : static MOZ_ALWAYS_INLINE Extent computeExtent(const PatChar*, uint32_t patLen) {
1671 : return (patLen - 1) * sizeof(PatChar);
1672 : }
1673 : static MOZ_ALWAYS_INLINE bool match(const PatChar* p, const TextChar* t, Extent extent) {
1674 : MOZ_ASSERT(sizeof(TextChar) == sizeof(PatChar));
1675 : return memcmp(p, t, extent) == 0;
1676 : }
1677 : };
1678 :
1679 : template <typename TextChar, typename PatChar>
1680 : struct ManualCmp {
1681 : typedef const PatChar* Extent;
1682 174 : static MOZ_ALWAYS_INLINE Extent computeExtent(const PatChar* pat, uint32_t patLen) {
1683 174 : return pat + patLen;
1684 : }
1685 291 : static MOZ_ALWAYS_INLINE bool match(const PatChar* p, const TextChar* t, Extent extent) {
1686 513 : for (; p != extent; ++p, ++t) {
1687 257 : if (*p != *t)
1688 35 : return false;
1689 : }
1690 34 : return true;
1691 : }
1692 : };
1693 :
1694 : template <typename TextChar, typename PatChar>
1695 : static const TextChar*
1696 50 : FirstCharMatcherUnrolled(const TextChar* text, uint32_t n, const PatChar pat)
1697 : {
1698 50 : const TextChar* textend = text + n;
1699 50 : const TextChar* t = text;
1700 :
1701 50 : switch ((textend - t) & 7) {
1702 4 : case 0: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1703 8 : case 7: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1704 14 : case 6: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1705 22 : case 5: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1706 27 : case 4: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1707 31 : case 3: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1708 34 : case 2: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
1709 43 : case 1: if (*t++ == pat) return t - 1;
1710 : }
1711 230 : while (textend != t) {
1712 111 : if (t[0] == pat) return t;
1713 111 : if (t[1] == pat) return t + 1;
1714 108 : if (t[2] == pat) return t + 2;
1715 106 : if (t[3] == pat) return t + 3;
1716 104 : if (t[4] == pat) return t + 4;
1717 104 : if (t[5] == pat) return t + 5;
1718 103 : if (t[6] == pat) return t + 6;
1719 97 : if (t[7] == pat) return t + 7;
1720 95 : t += 8;
1721 : }
1722 24 : return nullptr;
1723 : }
1724 :
1725 : static const char*
1726 157 : FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
1727 : {
1728 157 : return reinterpret_cast<const char*>(memchr(text, pat, n));
1729 : }
1730 :
1731 : template <class InnerMatch, typename TextChar, typename PatChar>
1732 : static int
1733 174 : Matcher(const TextChar* text, uint32_t textlen, const PatChar* pat, uint32_t patlen)
1734 : {
1735 174 : MOZ_ASSERT(patlen > 0);
1736 :
1737 0 : if (sizeof(TextChar) == 1 && sizeof(PatChar) > 1 && pat[0] > 0xff)
1738 0 : return -1;
1739 :
1740 174 : const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
1741 :
1742 174 : uint32_t i = 0;
1743 174 : uint32_t n = textlen - patlen + 1;
1744 244 : while (i < n) {
1745 : const TextChar* pos;
1746 :
1747 : if (sizeof(TextChar) == 1) {
1748 157 : MOZ_ASSERT(pat[0] <= 0xff);
1749 157 : pos = (TextChar*) FirstCharMatcher8bit((char*) text + i, n - i, pat[0]);
1750 : } else {
1751 50 : pos = FirstCharMatcherUnrolled(text + i, n - i, char16_t(pat[0]));
1752 : }
1753 :
1754 207 : if (pos == nullptr)
1755 138 : return -1;
1756 :
1757 69 : i = static_cast<uint32_t>(pos - text);
1758 69 : if (InnerMatch::match(pat + 1, text + i + 1, extent))
1759 34 : return i;
1760 :
1761 35 : i += 1;
1762 : }
1763 2 : return -1;
1764 : }
1765 :
1766 :
1767 : template <typename TextChar, typename PatChar>
1768 : static MOZ_ALWAYS_INLINE int
1769 350 : StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t patLen)
1770 : {
1771 350 : if (patLen == 0)
1772 0 : return 0;
1773 350 : if (textLen < patLen)
1774 176 : return -1;
1775 :
1776 : #if defined(__i386__) || defined(_M_IX86) || defined(__i386)
1777 : /*
1778 : * Given enough registers, the unrolled loop below is faster than the
1779 : * following loop. 32-bit x86 does not have enough registers.
1780 : */
1781 : if (patLen == 1) {
1782 : const PatChar p0 = *pat;
1783 : const TextChar* end = text + textLen;
1784 : for (const TextChar* c = text; c != end; ++c) {
1785 : if (*c == p0)
1786 : return c - text;
1787 : }
1788 : return -1;
1789 : }
1790 : #endif
1791 :
1792 : /*
1793 : * If the text or pattern string is short, BMH will be more expensive than
1794 : * the basic linear scan due to initialization cost and a more complex loop
1795 : * body. While the correct threshold is input-dependent, we can make a few
1796 : * conservative observations:
1797 : * - When |textLen| is "big enough", the initialization time will be
1798 : * proportionally small, so the worst-case slowdown is minimized.
1799 : * - When |patLen| is "too small", even the best case for BMH will be
1800 : * slower than a simple scan for large |textLen| due to the more complex
1801 : * loop body of BMH.
1802 : * From this, the values for "big enough" and "too small" are determined
1803 : * empirically. See bug 526348.
1804 : */
1805 174 : if (textLen >= 512 && patLen >= 11 && patLen <= sBMHPatLenMax) {
1806 0 : int index = BoyerMooreHorspool(text, textLen, pat, patLen);
1807 0 : if (index != sBMHBadPattern)
1808 0 : return index;
1809 : }
1810 :
1811 : /*
1812 : * For big patterns with large potential overlap we want the SIMD-optimized
1813 : * speed of memcmp. For small patterns, a simple loop is faster. We also can't
1814 : * use memcmp if one of the strings is TwoByte and the other is Latin-1.
1815 : *
1816 : * FIXME: Linux memcmp performance is sad and the manual loop is faster.
1817 : */
1818 : return
1819 : #if !defined(__linux__)
1820 : (patLen > 128 && IsSame<TextChar, PatChar>::value)
1821 : ? Matcher<MemCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen)
1822 : :
1823 : #endif
1824 174 : Matcher<ManualCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen);
1825 : }
1826 :
1827 : static int32_t
1828 350 : StringMatch(JSLinearString* text, JSLinearString* pat, uint32_t start = 0)
1829 : {
1830 350 : MOZ_ASSERT(start <= text->length());
1831 350 : uint32_t textLen = text->length() - start;
1832 350 : uint32_t patLen = pat->length();
1833 :
1834 : int match;
1835 700 : AutoCheckCannotGC nogc;
1836 350 : if (text->hasLatin1Chars()) {
1837 322 : const Latin1Char* textChars = text->latin1Chars(nogc) + start;
1838 322 : if (pat->hasLatin1Chars())
1839 322 : match = StringMatch(textChars, textLen, pat->latin1Chars(nogc), patLen);
1840 : else
1841 0 : match = StringMatch(textChars, textLen, pat->twoByteChars(nogc), patLen);
1842 : } else {
1843 28 : const char16_t* textChars = text->twoByteChars(nogc) + start;
1844 28 : if (pat->hasLatin1Chars())
1845 28 : match = StringMatch(textChars, textLen, pat->latin1Chars(nogc), patLen);
1846 : else
1847 0 : match = StringMatch(textChars, textLen, pat->twoByteChars(nogc), patLen);
1848 : }
1849 :
1850 700 : return (match == -1) ? -1 : start + match;
1851 : }
1852 :
1853 : static const size_t sRopeMatchThresholdRatioLog2 = 4;
1854 :
1855 : bool
1856 0 : js::StringHasPattern(JSLinearString* text, const char16_t* pat, uint32_t patLen)
1857 : {
1858 0 : AutoCheckCannotGC nogc;
1859 0 : return text->hasLatin1Chars()
1860 0 : ? StringMatch(text->latin1Chars(nogc), text->length(), pat, patLen) != -1
1861 0 : : StringMatch(text->twoByteChars(nogc), text->length(), pat, patLen) != -1;
1862 : }
1863 :
1864 : int
1865 19 : js::StringFindPattern(JSLinearString* text, JSLinearString* pat, size_t start)
1866 : {
1867 19 : return StringMatch(text, pat, start);
1868 : }
1869 :
1870 : // When an algorithm does not need a string represented as a single linear
1871 : // array of characters, this range utility may be used to traverse the string a
1872 : // sequence of linear arrays of characters. This avoids flattening ropes.
1873 0 : class StringSegmentRange
1874 : {
1875 : // If malloc() shows up in any profiles from this vector, we can add a new
1876 : // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
1877 : using StackVector = JS::GCVector<JSString*, 16>;
1878 : Rooted<StackVector> stack;
1879 : RootedLinearString cur;
1880 :
1881 0 : bool settle(JSString* str) {
1882 0 : while (str->isRope()) {
1883 0 : JSRope& rope = str->asRope();
1884 0 : if (!stack.append(rope.rightChild()))
1885 0 : return false;
1886 0 : str = rope.leftChild();
1887 : }
1888 0 : cur = &str->asLinear();
1889 0 : return true;
1890 : }
1891 :
1892 : public:
1893 0 : explicit StringSegmentRange(JSContext* cx)
1894 0 : : stack(cx, StackVector(cx)), cur(cx)
1895 0 : {}
1896 :
1897 0 : MOZ_MUST_USE bool init(JSString* str) {
1898 0 : MOZ_ASSERT(stack.empty());
1899 0 : return settle(str);
1900 : }
1901 :
1902 0 : bool empty() const {
1903 0 : return cur == nullptr;
1904 : }
1905 :
1906 0 : JSLinearString* front() const {
1907 0 : MOZ_ASSERT(!cur->isRope());
1908 0 : return cur;
1909 : }
1910 :
1911 0 : MOZ_MUST_USE bool popFront() {
1912 0 : MOZ_ASSERT(!empty());
1913 0 : if (stack.empty()) {
1914 0 : cur = nullptr;
1915 0 : return true;
1916 : }
1917 0 : return settle(stack.popCopy());
1918 : }
1919 : };
1920 :
1921 : typedef Vector<JSLinearString*, 16, SystemAllocPolicy> LinearStringVector;
1922 :
1923 : template <typename TextChar, typename PatChar>
1924 : static int
1925 0 : RopeMatchImpl(const AutoCheckCannotGC& nogc, LinearStringVector& strings,
1926 : const PatChar* pat, size_t patLen)
1927 : {
1928 : /* Absolute offset from the beginning of the logical text string. */
1929 0 : int pos = 0;
1930 :
1931 0 : for (JSLinearString** outerp = strings.begin(); outerp != strings.end(); ++outerp) {
1932 : /* Try to find a match within 'outer'. */
1933 0 : JSLinearString* outer = *outerp;
1934 0 : const TextChar* chars = outer->chars<TextChar>(nogc);
1935 0 : size_t len = outer->length();
1936 0 : int matchResult = StringMatch(chars, len, pat, patLen);
1937 0 : if (matchResult != -1) {
1938 : /* Matched! */
1939 0 : return pos + matchResult;
1940 : }
1941 :
1942 : /* Try to find a match starting in 'outer' and running into other nodes. */
1943 0 : const TextChar* const text = chars + (patLen > len ? 0 : len - patLen + 1);
1944 0 : const TextChar* const textend = chars + len;
1945 0 : const PatChar p0 = *pat;
1946 0 : const PatChar* const p1 = pat + 1;
1947 0 : const PatChar* const patend = pat + patLen;
1948 0 : for (const TextChar* t = text; t != textend; ) {
1949 0 : if (*t++ != p0)
1950 0 : continue;
1951 :
1952 0 : JSLinearString** innerp = outerp;
1953 0 : const TextChar* ttend = textend;
1954 0 : const TextChar* tt = t;
1955 0 : for (const PatChar* pp = p1; pp != patend; ++pp, ++tt) {
1956 0 : while (tt == ttend) {
1957 0 : if (++innerp == strings.end())
1958 0 : return -1;
1959 :
1960 0 : JSLinearString* inner = *innerp;
1961 0 : tt = inner->chars<TextChar>(nogc);
1962 0 : ttend = tt + inner->length();
1963 : }
1964 0 : if (*pp != *tt)
1965 0 : goto break_continue;
1966 : }
1967 :
1968 : /* Matched! */
1969 0 : return pos + (t - chars) - 1; /* -1 because of *t++ above */
1970 :
1971 : break_continue:;
1972 : }
1973 :
1974 0 : pos += len;
1975 : }
1976 :
1977 0 : return -1;
1978 : }
1979 :
1980 : /*
1981 : * RopeMatch takes the text to search and the pattern to search for in the text.
1982 : * RopeMatch returns false on OOM and otherwise returns the match index through
1983 : * the 'match' outparam (-1 for not found).
1984 : */
1985 : static bool
1986 0 : RopeMatch(JSContext* cx, JSRope* text, JSLinearString* pat, int* match)
1987 : {
1988 0 : uint32_t patLen = pat->length();
1989 0 : if (patLen == 0) {
1990 0 : *match = 0;
1991 0 : return true;
1992 : }
1993 0 : if (text->length() < patLen) {
1994 0 : *match = -1;
1995 0 : return true;
1996 : }
1997 :
1998 : /*
1999 : * List of leaf nodes in the rope. If we run out of memory when trying to
2000 : * append to this list, we can still fall back to StringMatch, so use the
2001 : * system allocator so we don't report OOM in that case.
2002 : */
2003 0 : LinearStringVector strings;
2004 :
2005 : /*
2006 : * We don't want to do rope matching if there is a poor node-to-char ratio,
2007 : * since this means spending a lot of time in the match loop below. We also
2008 : * need to build the list of leaf nodes. Do both here: iterate over the
2009 : * nodes so long as there are not too many.
2010 : *
2011 : * We also don't use rope matching if the rope contains both Latin-1 and
2012 : * TwoByte nodes, to simplify the match algorithm.
2013 : */
2014 : {
2015 0 : size_t threshold = text->length() >> sRopeMatchThresholdRatioLog2;
2016 0 : StringSegmentRange r(cx);
2017 0 : if (!r.init(text))
2018 0 : return false;
2019 :
2020 0 : bool textIsLatin1 = text->hasLatin1Chars();
2021 0 : while (!r.empty()) {
2022 0 : if (threshold-- == 0 ||
2023 0 : r.front()->hasLatin1Chars() != textIsLatin1 ||
2024 0 : !strings.append(r.front()))
2025 : {
2026 0 : JSLinearString* linear = text->ensureLinear(cx);
2027 0 : if (!linear)
2028 0 : return false;
2029 :
2030 0 : *match = StringMatch(linear, pat);
2031 0 : return true;
2032 : }
2033 0 : if (!r.popFront())
2034 0 : return false;
2035 : }
2036 : }
2037 :
2038 0 : AutoCheckCannotGC nogc;
2039 0 : if (text->hasLatin1Chars()) {
2040 0 : if (pat->hasLatin1Chars())
2041 0 : *match = RopeMatchImpl<Latin1Char>(nogc, strings, pat->latin1Chars(nogc), patLen);
2042 : else
2043 0 : *match = RopeMatchImpl<Latin1Char>(nogc, strings, pat->twoByteChars(nogc), patLen);
2044 : } else {
2045 0 : if (pat->hasLatin1Chars())
2046 0 : *match = RopeMatchImpl<char16_t>(nogc, strings, pat->latin1Chars(nogc), patLen);
2047 : else
2048 0 : *match = RopeMatchImpl<char16_t>(nogc, strings, pat->twoByteChars(nogc), patLen);
2049 : }
2050 :
2051 0 : return true;
2052 : }
2053 :
2054 : /* ES6 draft rc4 21.1.3.7. */
2055 : bool
2056 11 : js::str_includes(JSContext* cx, unsigned argc, Value* vp)
2057 : {
2058 11 : CallArgs args = CallArgsFromVp(argc, vp);
2059 :
2060 : // Steps 1, 2, and 3
2061 22 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2062 11 : if (!str)
2063 0 : return false;
2064 :
2065 : // Steps 4 and 5
2066 : bool isRegExp;
2067 11 : if (!IsRegExp(cx, args.get(0), &isRegExp))
2068 0 : return false;
2069 :
2070 : // Step 6
2071 11 : if (isRegExp) {
2072 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
2073 0 : "first", "", "Regular Expression");
2074 0 : return false;
2075 : }
2076 :
2077 : // Steps 7 and 8
2078 22 : RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
2079 11 : if (!searchStr)
2080 0 : return false;
2081 :
2082 : // Steps 9 and 10
2083 11 : uint32_t pos = 0;
2084 11 : if (args.hasDefined(1)) {
2085 0 : if (args[1].isInt32()) {
2086 0 : int i = args[1].toInt32();
2087 0 : pos = (i < 0) ? 0U : uint32_t(i);
2088 : } else {
2089 : double d;
2090 0 : if (!ToInteger(cx, args[1], &d))
2091 0 : return false;
2092 0 : pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
2093 : }
2094 : }
2095 :
2096 : // Step 11
2097 11 : uint32_t textLen = str->length();
2098 :
2099 : // Step 12
2100 11 : uint32_t start = Min(Max(pos, 0U), textLen);
2101 :
2102 : // Steps 13 and 14
2103 11 : JSLinearString* text = str->ensureLinear(cx);
2104 11 : if (!text)
2105 0 : return false;
2106 :
2107 11 : args.rval().setBoolean(StringMatch(text, searchStr, start) != -1);
2108 11 : return true;
2109 : }
2110 :
2111 : /* ES6 20120927 draft 15.5.4.7. */
2112 : bool
2113 282 : js::str_indexOf(JSContext* cx, unsigned argc, Value* vp)
2114 : {
2115 282 : CallArgs args = CallArgsFromVp(argc, vp);
2116 :
2117 : // Steps 1, 2, and 3
2118 564 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2119 282 : if (!str)
2120 0 : return false;
2121 :
2122 : // Steps 4 and 5
2123 564 : RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
2124 282 : if (!searchStr)
2125 0 : return false;
2126 :
2127 : // Steps 6 and 7
2128 282 : uint32_t pos = 0;
2129 282 : if (args.hasDefined(1)) {
2130 0 : if (args[1].isInt32()) {
2131 0 : int i = args[1].toInt32();
2132 0 : pos = (i < 0) ? 0U : uint32_t(i);
2133 : } else {
2134 : double d;
2135 0 : if (!ToInteger(cx, args[1], &d))
2136 0 : return false;
2137 0 : pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
2138 : }
2139 : }
2140 :
2141 : // Step 8
2142 282 : uint32_t textLen = str->length();
2143 :
2144 : // Step 9
2145 282 : uint32_t start = Min(Max(pos, 0U), textLen);
2146 :
2147 : // Steps 10 and 11
2148 282 : JSLinearString* text = str->ensureLinear(cx);
2149 282 : if (!text)
2150 0 : return false;
2151 :
2152 282 : args.rval().setInt32(StringMatch(text, searchStr, start));
2153 282 : return true;
2154 : }
2155 :
2156 : template <typename TextChar, typename PatChar>
2157 : static int32_t
2158 13 : LastIndexOfImpl(const TextChar* text, size_t textLen, const PatChar* pat, size_t patLen,
2159 : size_t start)
2160 : {
2161 13 : MOZ_ASSERT(patLen > 0);
2162 13 : MOZ_ASSERT(patLen <= textLen);
2163 13 : MOZ_ASSERT(start <= textLen - patLen);
2164 :
2165 13 : const PatChar p0 = *pat;
2166 13 : const PatChar* patNext = pat + 1;
2167 13 : const PatChar* patEnd = pat + patLen;
2168 :
2169 175 : for (const TextChar* t = text + start; t >= text; --t) {
2170 175 : if (*t == p0) {
2171 13 : const TextChar* t1 = t + 1;
2172 13 : for (const PatChar* p1 = patNext; p1 < patEnd; ++p1, ++t1) {
2173 0 : if (*t1 != *p1)
2174 0 : goto break_continue;
2175 : }
2176 :
2177 13 : return static_cast<int32_t>(t - text);
2178 : }
2179 : break_continue:;
2180 : }
2181 :
2182 0 : return -1;
2183 : }
2184 :
2185 : // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
2186 : // 21.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] )
2187 : bool
2188 13 : js::str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp)
2189 : {
2190 13 : CallArgs args = CallArgsFromVp(argc, vp);
2191 :
2192 : // Steps 1-2.
2193 26 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2194 13 : if (!str)
2195 0 : return false;
2196 :
2197 : // Step 3.
2198 26 : RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
2199 13 : if (!searchStr)
2200 0 : return false;
2201 :
2202 : // Step 6.
2203 13 : size_t len = str->length();
2204 :
2205 : // Step 8.
2206 13 : size_t searchLen = searchStr->length();
2207 :
2208 : // Steps 4-5, 7.
2209 13 : int start = len - searchLen; // Start searching here
2210 13 : if (args.hasDefined(1)) {
2211 0 : if (args[1].isInt32()) {
2212 0 : int i = args[1].toInt32();
2213 0 : if (i <= 0)
2214 0 : start = 0;
2215 0 : else if (i < start)
2216 0 : start = i;
2217 : } else {
2218 : double d;
2219 0 : if (!ToNumber(cx, args[1], &d))
2220 0 : return false;
2221 0 : if (!IsNaN(d)) {
2222 0 : d = JS::ToInteger(d);
2223 0 : if (d <= 0)
2224 0 : start = 0;
2225 0 : else if (d < start)
2226 0 : start = int(d);
2227 : }
2228 : }
2229 : }
2230 :
2231 13 : if (searchLen > len) {
2232 0 : args.rval().setInt32(-1);
2233 0 : return true;
2234 : }
2235 :
2236 13 : if (searchLen == 0) {
2237 0 : args.rval().setInt32(start);
2238 0 : return true;
2239 : }
2240 13 : MOZ_ASSERT(0 <= start && size_t(start) < len);
2241 :
2242 13 : JSLinearString* text = str->ensureLinear(cx);
2243 13 : if (!text)
2244 0 : return false;
2245 :
2246 : // Step 9.
2247 : int32_t res;
2248 26 : AutoCheckCannotGC nogc;
2249 13 : if (text->hasLatin1Chars()) {
2250 12 : const Latin1Char* textChars = text->latin1Chars(nogc);
2251 12 : if (searchStr->hasLatin1Chars())
2252 12 : res = LastIndexOfImpl(textChars, len, searchStr->latin1Chars(nogc), searchLen, start);
2253 : else
2254 0 : res = LastIndexOfImpl(textChars, len, searchStr->twoByteChars(nogc), searchLen, start);
2255 : } else {
2256 1 : const char16_t* textChars = text->twoByteChars(nogc);
2257 1 : if (searchStr->hasLatin1Chars())
2258 1 : res = LastIndexOfImpl(textChars, len, searchStr->latin1Chars(nogc), searchLen, start);
2259 : else
2260 0 : res = LastIndexOfImpl(textChars, len, searchStr->twoByteChars(nogc), searchLen, start);
2261 : }
2262 :
2263 13 : args.rval().setInt32(res);
2264 13 : return true;
2265 : }
2266 :
2267 : bool
2268 481 : js::HasSubstringAt(JSLinearString* text, JSLinearString* pat, size_t start)
2269 : {
2270 481 : MOZ_ASSERT(start + pat->length() <= text->length());
2271 :
2272 481 : size_t patLen = pat->length();
2273 :
2274 962 : AutoCheckCannotGC nogc;
2275 481 : if (text->hasLatin1Chars()) {
2276 478 : const Latin1Char* textChars = text->latin1Chars(nogc) + start;
2277 478 : if (pat->hasLatin1Chars())
2278 478 : return PodEqual(textChars, pat->latin1Chars(nogc), patLen);
2279 :
2280 0 : return EqualChars(textChars, pat->twoByteChars(nogc), patLen);
2281 : }
2282 :
2283 3 : const char16_t* textChars = text->twoByteChars(nogc) + start;
2284 3 : if (pat->hasTwoByteChars())
2285 0 : return PodEqual(textChars, pat->twoByteChars(nogc), patLen);
2286 :
2287 3 : return EqualChars(pat->latin1Chars(nogc), textChars, patLen);
2288 : }
2289 :
2290 : /* ES6 draft rc3 21.1.3.18. */
2291 : bool
2292 378 : js::str_startsWith(JSContext* cx, unsigned argc, Value* vp)
2293 : {
2294 378 : CallArgs args = CallArgsFromVp(argc, vp);
2295 :
2296 : // Steps 1, 2, and 3
2297 756 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2298 378 : if (!str)
2299 0 : return false;
2300 :
2301 : // Steps 4 and 5
2302 : bool isRegExp;
2303 378 : if (!IsRegExp(cx, args.get(0), &isRegExp))
2304 0 : return false;
2305 :
2306 : // Step 6
2307 378 : if (isRegExp) {
2308 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
2309 0 : "first", "", "Regular Expression");
2310 0 : return false;
2311 : }
2312 :
2313 : // Steps 7 and 8
2314 756 : RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
2315 378 : if (!searchStr)
2316 0 : return false;
2317 :
2318 : // Steps 9 and 10
2319 378 : uint32_t pos = 0;
2320 378 : if (args.hasDefined(1)) {
2321 0 : if (args[1].isInt32()) {
2322 0 : int i = args[1].toInt32();
2323 0 : pos = (i < 0) ? 0U : uint32_t(i);
2324 : } else {
2325 : double d;
2326 0 : if (!ToInteger(cx, args[1], &d))
2327 0 : return false;
2328 0 : pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
2329 : }
2330 : }
2331 :
2332 : // Step 11
2333 378 : uint32_t textLen = str->length();
2334 :
2335 : // Step 12
2336 378 : uint32_t start = Min(Max(pos, 0U), textLen);
2337 :
2338 : // Step 13
2339 378 : uint32_t searchLen = searchStr->length();
2340 :
2341 : // Step 14
2342 378 : if (searchLen + start < searchLen || searchLen + start > textLen) {
2343 68 : args.rval().setBoolean(false);
2344 68 : return true;
2345 : }
2346 :
2347 : // Steps 15 and 16
2348 310 : JSLinearString* text = str->ensureLinear(cx);
2349 310 : if (!text)
2350 0 : return false;
2351 :
2352 310 : args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
2353 310 : return true;
2354 : }
2355 :
2356 : /* ES6 draft rc3 21.1.3.6. */
2357 : bool
2358 179 : js::str_endsWith(JSContext* cx, unsigned argc, Value* vp)
2359 : {
2360 179 : CallArgs args = CallArgsFromVp(argc, vp);
2361 :
2362 : // Steps 1, 2, and 3
2363 358 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2364 179 : if (!str)
2365 0 : return false;
2366 :
2367 : // Steps 4 and 5
2368 : bool isRegExp;
2369 179 : if (!IsRegExp(cx, args.get(0), &isRegExp))
2370 0 : return false;
2371 :
2372 : // Step 6
2373 179 : if (isRegExp) {
2374 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
2375 0 : "first", "", "Regular Expression");
2376 0 : return false;
2377 : }
2378 :
2379 : // Steps 7 and 8
2380 358 : RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
2381 179 : if (!searchStr)
2382 0 : return false;
2383 :
2384 : // Step 9
2385 179 : uint32_t textLen = str->length();
2386 :
2387 : // Steps 10 and 11
2388 179 : uint32_t pos = textLen;
2389 179 : if (args.hasDefined(1)) {
2390 0 : if (args[1].isInt32()) {
2391 0 : int i = args[1].toInt32();
2392 0 : pos = (i < 0) ? 0U : uint32_t(i);
2393 : } else {
2394 : double d;
2395 0 : if (!ToInteger(cx, args[1], &d))
2396 0 : return false;
2397 0 : pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
2398 : }
2399 : }
2400 :
2401 : // Step 12
2402 179 : uint32_t end = Min(Max(pos, 0U), textLen);
2403 :
2404 : // Step 13
2405 179 : uint32_t searchLen = searchStr->length();
2406 :
2407 : // Step 15 (reordered)
2408 179 : if (searchLen > end) {
2409 8 : args.rval().setBoolean(false);
2410 8 : return true;
2411 : }
2412 :
2413 : // Step 14
2414 171 : uint32_t start = end - searchLen;
2415 :
2416 : // Steps 16 and 17
2417 171 : JSLinearString* text = str->ensureLinear(cx);
2418 171 : if (!text)
2419 0 : return false;
2420 :
2421 171 : args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
2422 171 : return true;
2423 : }
2424 :
2425 : template <typename CharT>
2426 : static void
2427 48 : TrimString(const CharT* chars, bool trimLeft, bool trimRight, size_t length,
2428 : size_t* pBegin, size_t* pEnd)
2429 : {
2430 48 : size_t begin = 0, end = length;
2431 :
2432 48 : if (trimLeft) {
2433 48 : while (begin < length && unicode::IsSpace(chars[begin]))
2434 0 : ++begin;
2435 : }
2436 :
2437 48 : if (trimRight) {
2438 48 : while (end > begin && unicode::IsSpace(chars[end - 1]))
2439 0 : --end;
2440 : }
2441 :
2442 48 : *pBegin = begin;
2443 48 : *pEnd = end;
2444 48 : }
2445 :
2446 : static bool
2447 48 : TrimString(JSContext* cx, const CallArgs& args, bool trimLeft, bool trimRight)
2448 : {
2449 96 : RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
2450 48 : if (!str)
2451 0 : return false;
2452 :
2453 48 : JSLinearString* linear = str->ensureLinear(cx);
2454 48 : if (!linear)
2455 0 : return false;
2456 :
2457 48 : size_t length = linear->length();
2458 : size_t begin, end;
2459 48 : if (linear->hasLatin1Chars()) {
2460 90 : AutoCheckCannotGC nogc;
2461 45 : TrimString(linear->latin1Chars(nogc), trimLeft, trimRight, length, &begin, &end);
2462 : } else {
2463 6 : AutoCheckCannotGC nogc;
2464 3 : TrimString(linear->twoByteChars(nogc), trimLeft, trimRight, length, &begin, &end);
2465 : }
2466 :
2467 48 : str = NewDependentString(cx, str, begin, end - begin);
2468 48 : if (!str)
2469 0 : return false;
2470 :
2471 48 : args.rval().setString(str);
2472 48 : return true;
2473 : }
2474 :
2475 : bool
2476 48 : js::str_trim(JSContext* cx, unsigned argc, Value* vp)
2477 : {
2478 48 : CallArgs args = CallArgsFromVp(argc, vp);
2479 48 : return TrimString(cx, args, true, true);
2480 : }
2481 :
2482 : bool
2483 0 : js::str_trimLeft(JSContext* cx, unsigned argc, Value* vp)
2484 : {
2485 0 : CallArgs args = CallArgsFromVp(argc, vp);
2486 0 : return TrimString(cx, args, true, false);
2487 : }
2488 :
2489 : bool
2490 0 : js::str_trimRight(JSContext* cx, unsigned argc, Value* vp)
2491 : {
2492 0 : CallArgs args = CallArgsFromVp(argc, vp);
2493 0 : return TrimString(cx, args, false, true);
2494 : }
2495 :
2496 : // Utility for building a rope (lazy concatenation) of strings.
2497 14 : class RopeBuilder {
2498 : JSContext* cx;
2499 : RootedString res;
2500 :
2501 : RopeBuilder(const RopeBuilder& other) = delete;
2502 : void operator=(const RopeBuilder& other) = delete;
2503 :
2504 : public:
2505 14 : explicit RopeBuilder(JSContext* cx)
2506 14 : : cx(cx), res(cx, cx->runtime()->emptyString)
2507 14 : {}
2508 :
2509 42 : inline bool append(HandleString str) {
2510 42 : res = ConcatStrings<CanGC>(cx, res, str);
2511 42 : return !!res;
2512 : }
2513 :
2514 14 : inline JSString* result() {
2515 14 : return res;
2516 : }
2517 : };
2518 :
2519 : namespace {
2520 :
2521 : template <typename CharT>
2522 : static uint32_t
2523 28 : FindDollarIndex(const CharT* chars, size_t length)
2524 : {
2525 28 : if (const CharT* p = js_strchr_limit(chars, '$', chars + length)) {
2526 0 : uint32_t dollarIndex = p - chars;
2527 0 : MOZ_ASSERT(dollarIndex < length);
2528 0 : return dollarIndex;
2529 : }
2530 28 : return UINT32_MAX;
2531 : }
2532 :
2533 : } /* anonymous namespace */
2534 :
2535 : static JSString*
2536 14 : BuildFlatReplacement(JSContext* cx, HandleString textstr, HandleString repstr,
2537 : size_t match, size_t patternLength)
2538 : {
2539 28 : RopeBuilder builder(cx);
2540 14 : size_t matchEnd = match + patternLength;
2541 :
2542 14 : if (textstr->isRope()) {
2543 : /*
2544 : * If we are replacing over a rope, avoid flattening it by iterating
2545 : * through it, building a new rope.
2546 : */
2547 0 : StringSegmentRange r(cx);
2548 0 : if (!r.init(textstr))
2549 0 : return nullptr;
2550 :
2551 0 : size_t pos = 0;
2552 0 : while (!r.empty()) {
2553 0 : RootedString str(cx, r.front());
2554 0 : size_t len = str->length();
2555 0 : size_t strEnd = pos + len;
2556 0 : if (pos < matchEnd && strEnd > match) {
2557 : /*
2558 : * We need to special-case any part of the rope that overlaps
2559 : * with the replacement string.
2560 : */
2561 0 : if (match >= pos) {
2562 : /*
2563 : * If this part of the rope overlaps with the left side of
2564 : * the pattern, then it must be the only one to overlap with
2565 : * the first character in the pattern, so we include the
2566 : * replacement string here.
2567 : */
2568 0 : RootedString leftSide(cx, NewDependentString(cx, str, 0, match - pos));
2569 0 : if (!leftSide ||
2570 0 : !builder.append(leftSide) ||
2571 0 : !builder.append(repstr))
2572 : {
2573 0 : return nullptr;
2574 : }
2575 : }
2576 :
2577 : /*
2578 : * If str runs off the end of the matched string, append the
2579 : * last part of str.
2580 : */
2581 0 : if (strEnd > matchEnd) {
2582 0 : RootedString rightSide(cx, NewDependentString(cx, str, matchEnd - pos,
2583 0 : strEnd - matchEnd));
2584 0 : if (!rightSide || !builder.append(rightSide))
2585 0 : return nullptr;
2586 0 : }
2587 : } else {
2588 0 : if (!builder.append(str))
2589 0 : return nullptr;
2590 : }
2591 0 : pos += str->length();
2592 0 : if (!r.popFront())
2593 0 : return nullptr;
2594 : }
2595 : } else {
2596 28 : RootedString leftSide(cx, NewDependentString(cx, textstr, 0, match));
2597 14 : if (!leftSide)
2598 0 : return nullptr;
2599 28 : RootedString rightSide(cx);
2600 42 : rightSide = NewDependentString(cx, textstr, match + patternLength,
2601 42 : textstr->length() - match - patternLength);
2602 70 : if (!rightSide ||
2603 70 : !builder.append(leftSide) ||
2604 70 : !builder.append(repstr) ||
2605 42 : !builder.append(rightSide))
2606 : {
2607 0 : return nullptr;
2608 : }
2609 : }
2610 :
2611 14 : return builder.result();
2612 : }
2613 :
2614 : template <typename CharT>
2615 : static bool
2616 0 : AppendDollarReplacement(StringBuffer& newReplaceChars, size_t firstDollarIndex,
2617 : size_t matchStart, size_t matchLimit, JSLinearString* text,
2618 : const CharT* repChars, size_t repLength)
2619 : {
2620 0 : MOZ_ASSERT(firstDollarIndex < repLength);
2621 :
2622 : /* Move the pre-dollar chunk in bulk. */
2623 0 : newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
2624 :
2625 : /* Move the rest char-by-char, interpreting dollars as we encounter them. */
2626 0 : const CharT* repLimit = repChars + repLength;
2627 0 : for (const CharT* it = repChars + firstDollarIndex; it < repLimit; ++it) {
2628 0 : if (*it != '$' || it == repLimit - 1) {
2629 0 : if (!newReplaceChars.append(*it))
2630 0 : return false;
2631 0 : continue;
2632 : }
2633 :
2634 0 : switch (*(it + 1)) {
2635 : case '$': /* Eat one of the dollars. */
2636 0 : if (!newReplaceChars.append(*it))
2637 0 : return false;
2638 0 : break;
2639 : case '&':
2640 0 : if (!newReplaceChars.appendSubstring(text, matchStart, matchLimit - matchStart))
2641 0 : return false;
2642 0 : break;
2643 : case '`':
2644 0 : if (!newReplaceChars.appendSubstring(text, 0, matchStart))
2645 0 : return false;
2646 0 : break;
2647 : case '\'':
2648 0 : if (!newReplaceChars.appendSubstring(text, matchLimit, text->length() - matchLimit))
2649 0 : return false;
2650 0 : break;
2651 : default: /* The dollar we saw was not special (no matter what its mother told it). */
2652 0 : if (!newReplaceChars.append(*it))
2653 0 : return false;
2654 0 : continue;
2655 : }
2656 0 : ++it; /* We always eat an extra char in the above switch. */
2657 : }
2658 :
2659 0 : return true;
2660 : }
2661 :
2662 : /*
2663 : * Perform a linear-scan dollar substitution on the replacement text,
2664 : * constructing a result string that looks like:
2665 : *
2666 : * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
2667 : */
2668 : static JSString*
2669 0 : BuildDollarReplacement(JSContext* cx, JSString* textstrArg, JSLinearString* repstr,
2670 : uint32_t firstDollarIndex, size_t matchStart, size_t patternLength)
2671 : {
2672 0 : RootedLinearString textstr(cx, textstrArg->ensureLinear(cx));
2673 0 : if (!textstr)
2674 0 : return nullptr;
2675 :
2676 0 : size_t matchLimit = matchStart + patternLength;
2677 :
2678 : /*
2679 : * Most probably:
2680 : *
2681 : * len(newstr) >= len(orig) - len(match) + len(replacement)
2682 : *
2683 : * Note that dollar vars _could_ make the resulting text smaller than this.
2684 : */
2685 0 : StringBuffer newReplaceChars(cx);
2686 0 : if (repstr->hasTwoByteChars() && !newReplaceChars.ensureTwoByteChars())
2687 0 : return nullptr;
2688 :
2689 0 : if (!newReplaceChars.reserve(textstr->length() - patternLength + repstr->length()))
2690 0 : return nullptr;
2691 :
2692 : bool res;
2693 0 : if (repstr->hasLatin1Chars()) {
2694 0 : AutoCheckCannotGC nogc;
2695 0 : res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, matchStart, matchLimit,
2696 0 : textstr, repstr->latin1Chars(nogc), repstr->length());
2697 : } else {
2698 0 : AutoCheckCannotGC nogc;
2699 0 : res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, matchStart, matchLimit,
2700 0 : textstr, repstr->twoByteChars(nogc), repstr->length());
2701 : }
2702 0 : if (!res)
2703 0 : return nullptr;
2704 :
2705 0 : RootedString leftSide(cx, NewDependentString(cx, textstr, 0, matchStart));
2706 0 : if (!leftSide)
2707 0 : return nullptr;
2708 :
2709 0 : RootedString newReplace(cx, newReplaceChars.finishString());
2710 0 : if (!newReplace)
2711 0 : return nullptr;
2712 :
2713 0 : MOZ_ASSERT(textstr->length() >= matchLimit);
2714 0 : RootedString rightSide(cx, NewDependentString(cx, textstr, matchLimit,
2715 0 : textstr->length() - matchLimit));
2716 0 : if (!rightSide)
2717 0 : return nullptr;
2718 :
2719 0 : RopeBuilder builder(cx);
2720 0 : if (!builder.append(leftSide) || !builder.append(newReplace) || !builder.append(rightSide))
2721 0 : return nullptr;
2722 :
2723 0 : return builder.result();
2724 : }
2725 :
2726 : template <typename StrChar, typename RepChar>
2727 : static bool
2728 0 : StrFlatReplaceGlobal(JSContext *cx, JSLinearString *str, JSLinearString *pat, JSLinearString *rep,
2729 : StringBuffer &sb)
2730 : {
2731 0 : MOZ_ASSERT(str->length() > 0);
2732 :
2733 0 : AutoCheckCannotGC nogc;
2734 0 : const StrChar *strChars = str->chars<StrChar>(nogc);
2735 0 : const RepChar *repChars = rep->chars<RepChar>(nogc);
2736 :
2737 : // The pattern is empty, so we interleave the replacement string in-between
2738 : // each character.
2739 0 : if (!pat->length()) {
2740 0 : CheckedInt<uint32_t> strLength(str->length());
2741 0 : CheckedInt<uint32_t> repLength(rep->length());
2742 0 : CheckedInt<uint32_t> length = repLength * (strLength - 1) + strLength;
2743 0 : if (!length.isValid()) {
2744 0 : ReportAllocationOverflow(cx);
2745 0 : return false;
2746 : }
2747 :
2748 0 : if (!sb.reserve(length.value()))
2749 0 : return false;
2750 :
2751 0 : for (unsigned i = 0; i < str->length() - 1; ++i, ++strChars) {
2752 0 : sb.infallibleAppend(*strChars);
2753 0 : sb.infallibleAppend(repChars, rep->length());
2754 : }
2755 0 : sb.infallibleAppend(*strChars);
2756 0 : return true;
2757 : }
2758 :
2759 : // If it's true, we are sure that the result's length is, at least, the same
2760 : // length as |str->length()|.
2761 0 : if (rep->length() >= pat->length()) {
2762 0 : if (!sb.reserve(str->length()))
2763 0 : return false;
2764 : }
2765 :
2766 0 : uint32_t start = 0;
2767 0 : for (;;) {
2768 0 : int match = StringMatch(str, pat, start);
2769 0 : if (match < 0)
2770 0 : break;
2771 0 : if (!sb.append(strChars + start, match - start))
2772 0 : return false;
2773 0 : if (!sb.append(repChars, rep->length()))
2774 0 : return false;
2775 0 : start = match + pat->length();
2776 : }
2777 :
2778 0 : if (!sb.append(strChars + start, str->length() - start))
2779 0 : return false;
2780 :
2781 0 : return true;
2782 : }
2783 :
2784 : // This is identical to "str.split(pattern).join(replacement)" except that we
2785 : // do some deforestation optimization in Ion.
2786 : JSString *
2787 0 : js::str_flat_replace_string(JSContext *cx, HandleString string, HandleString pattern,
2788 : HandleString replacement)
2789 : {
2790 0 : MOZ_ASSERT(string);
2791 0 : MOZ_ASSERT(pattern);
2792 0 : MOZ_ASSERT(replacement);
2793 :
2794 0 : if (!string->length())
2795 0 : return string;
2796 :
2797 0 : RootedLinearString linearRepl(cx, replacement->ensureLinear(cx));
2798 0 : if (!linearRepl)
2799 0 : return nullptr;
2800 :
2801 0 : RootedLinearString linearPat(cx, pattern->ensureLinear(cx));
2802 0 : if (!linearPat)
2803 0 : return nullptr;
2804 :
2805 0 : RootedLinearString linearStr(cx, string->ensureLinear(cx));
2806 0 : if (!linearStr)
2807 0 : return nullptr;
2808 :
2809 0 : StringBuffer sb(cx);
2810 0 : if (linearStr->hasTwoByteChars()) {
2811 0 : if (!sb.ensureTwoByteChars())
2812 0 : return nullptr;
2813 0 : if (linearRepl->hasTwoByteChars()) {
2814 0 : if (!StrFlatReplaceGlobal<char16_t, char16_t>(cx, linearStr, linearPat, linearRepl, sb))
2815 0 : return nullptr;
2816 : } else {
2817 0 : if (!StrFlatReplaceGlobal<char16_t, Latin1Char>(cx, linearStr, linearPat, linearRepl, sb))
2818 0 : return nullptr;
2819 : }
2820 : } else {
2821 0 : if (linearRepl->hasTwoByteChars()) {
2822 0 : if (!sb.ensureTwoByteChars())
2823 0 : return nullptr;
2824 0 : if (!StrFlatReplaceGlobal<Latin1Char, char16_t>(cx, linearStr, linearPat, linearRepl, sb))
2825 0 : return nullptr;
2826 : } else {
2827 0 : if (!StrFlatReplaceGlobal<Latin1Char, Latin1Char>(cx, linearStr, linearPat, linearRepl, sb))
2828 0 : return nullptr;
2829 : }
2830 : }
2831 :
2832 0 : JSString *str = sb.finishString();
2833 0 : if (!str)
2834 0 : return nullptr;
2835 :
2836 0 : return str;
2837 : }
2838 :
2839 : JSString*
2840 28 : js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
2841 : HandleString replacement)
2842 : {
2843 56 : RootedLinearString repl(cx, replacement->ensureLinear(cx));
2844 28 : if (!repl)
2845 0 : return nullptr;
2846 :
2847 56 : RootedAtom pat(cx, AtomizeString(cx, pattern));
2848 28 : if (!pat)
2849 0 : return nullptr;
2850 :
2851 28 : size_t patternLength = pat->length();
2852 : int32_t match;
2853 : uint32_t dollarIndex;
2854 :
2855 : {
2856 56 : AutoCheckCannotGC nogc;
2857 56 : dollarIndex = repl->hasLatin1Chars()
2858 28 : ? FindDollarIndex(repl->latin1Chars(nogc), repl->length())
2859 0 : : FindDollarIndex(repl->twoByteChars(nogc), repl->length());
2860 : }
2861 :
2862 : /*
2863 : * |string| could be a rope, so we want to avoid flattening it for as
2864 : * long as possible.
2865 : */
2866 28 : if (string->isRope()) {
2867 0 : if (!RopeMatch(cx, &string->asRope(), pat, &match))
2868 0 : return nullptr;
2869 : } else {
2870 28 : match = StringMatch(&string->asLinear(), pat, 0);
2871 : }
2872 :
2873 28 : if (match < 0)
2874 14 : return string;
2875 :
2876 14 : if (dollarIndex != UINT32_MAX)
2877 0 : return BuildDollarReplacement(cx, string, repl, dollarIndex, match, patternLength);
2878 14 : return BuildFlatReplacement(cx, string, repl, match, patternLength);
2879 : }
2880 :
2881 : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
2882 : static JSObject*
2883 2 : SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
2884 : HandleObjectGroup group)
2885 : {
2886 2 : size_t strLength = str->length();
2887 2 : size_t sepLength = sep->length();
2888 2 : MOZ_ASSERT(sepLength != 0);
2889 :
2890 : // Step 12.
2891 2 : if (strLength == 0) {
2892 : // Step 12.a.
2893 2 : int match = StringMatch(str, sep, 0);
2894 :
2895 : // Step 12.b.
2896 2 : if (match != -1)
2897 0 : return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
2898 :
2899 : // Steps 12.c-e.
2900 4 : RootedValue v(cx, StringValue(str));
2901 2 : return NewCopiedArrayTryUseGroup(cx, group, v.address(), 1);
2902 : }
2903 :
2904 : // Step 3 (reordered).
2905 0 : AutoValueVector splits(cx);
2906 :
2907 : // Step 8 (reordered).
2908 0 : size_t lastEndIndex = 0;
2909 :
2910 : // Step 13.
2911 0 : size_t index = 0;
2912 :
2913 : // Step 14.
2914 0 : while (index != strLength) {
2915 : // Step 14.a.
2916 0 : int match = StringMatch(str, sep, index);
2917 :
2918 : // Step 14.b.
2919 : //
2920 : // Our match algorithm differs from the spec in that it returns the
2921 : // next index at which a match happens. If no match happens we're
2922 : // done.
2923 : //
2924 : // But what if the match is at the end of the string (and the string is
2925 : // not empty)? Per 14.c.i this shouldn't be a match, so we have to
2926 : // specially exclude it. Thus this case should hold:
2927 : //
2928 : // var a = "abc".split(/\b/);
2929 : // assertEq(a.length, 1);
2930 : // assertEq(a[0], "abc");
2931 0 : if (match == -1)
2932 0 : break;
2933 :
2934 : // Step 14.c.
2935 0 : size_t endIndex = match + sepLength;
2936 :
2937 : // Step 14.c.i.
2938 0 : if (endIndex == lastEndIndex) {
2939 0 : index++;
2940 0 : continue;
2941 : }
2942 :
2943 : // Step 14.c.ii.
2944 0 : MOZ_ASSERT(lastEndIndex < endIndex);
2945 0 : MOZ_ASSERT(sepLength <= strLength);
2946 0 : MOZ_ASSERT(lastEndIndex + sepLength <= endIndex);
2947 :
2948 : // Step 14.c.ii.1.
2949 0 : size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
2950 0 : JSString* sub = NewDependentString(cx, str, lastEndIndex, subLength);
2951 :
2952 : // Steps 14.c.ii.2-4.
2953 0 : if (!sub || !splits.append(StringValue(sub)))
2954 0 : return nullptr;
2955 :
2956 : // Step 14.c.ii.5.
2957 0 : if (splits.length() == limit)
2958 0 : return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
2959 :
2960 : // Step 14.c.ii.6.
2961 0 : index = endIndex;
2962 :
2963 : // Step 14.c.ii.7.
2964 0 : lastEndIndex = index;
2965 : }
2966 :
2967 : // Step 15.
2968 0 : JSString* sub = NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
2969 :
2970 : // Steps 16-17.
2971 0 : if (!sub || !splits.append(StringValue(sub)))
2972 0 : return nullptr;
2973 :
2974 : // Step 18.
2975 0 : return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
2976 : }
2977 :
2978 : // Fast-path for splitting a string into a character array via split("").
2979 : static JSObject*
2980 0 : CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
2981 : {
2982 0 : size_t strLength = str->length();
2983 0 : if (strLength == 0)
2984 0 : return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
2985 :
2986 0 : js::StaticStrings& staticStrings = cx->staticStrings();
2987 0 : uint32_t resultlen = (limit < strLength ? limit : strLength);
2988 :
2989 0 : AutoValueVector splits(cx);
2990 0 : if (!splits.reserve(resultlen))
2991 0 : return nullptr;
2992 :
2993 0 : for (size_t i = 0; i < resultlen; ++i) {
2994 0 : JSString* sub = staticStrings.getUnitStringForElement(cx, str, i);
2995 0 : if (!sub)
2996 0 : return nullptr;
2997 0 : splits.infallibleAppend(StringValue(sub));
2998 : }
2999 :
3000 0 : return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
3001 : }
3002 :
3003 : template <typename TextChar>
3004 : static MOZ_ALWAYS_INLINE JSObject*
3005 95 : SplitSingleCharHelper(JSContext* cx, HandleLinearString str, const TextChar* text,
3006 : uint32_t textLen, char16_t patCh, HandleObjectGroup group)
3007 : {
3008 : // Count the number of occurrences of patCh within text.
3009 95 : uint32_t count = 0;
3010 2814 : for (size_t index = 0; index < textLen; index++) {
3011 2719 : if (static_cast<char16_t>(text[index]) == patCh)
3012 125 : count++;
3013 : }
3014 :
3015 : // Handle zero-occurrence case - return input string in an array.
3016 95 : if (count == 0) {
3017 58 : RootedValue strValue(cx, StringValue(str.get()));
3018 29 : return NewCopiedArrayTryUseGroup(cx, group, &strValue.get(), 1);
3019 : }
3020 :
3021 : // Reserve memory for substring values.
3022 132 : AutoValueVector splits(cx);
3023 66 : if (!splits.reserve(count + 1))
3024 0 : return nullptr;
3025 :
3026 : // Add substrings.
3027 66 : size_t lastEndIndex = 0;
3028 2354 : for (size_t index = 0; index < textLen; index++) {
3029 2288 : if (static_cast<char16_t>(text[index]) == patCh) {
3030 125 : size_t subLength = size_t(index - lastEndIndex);
3031 125 : JSString* sub = NewDependentString(cx, str, lastEndIndex, subLength);
3032 125 : if (!sub || !splits.append(StringValue(sub)))
3033 0 : return nullptr;
3034 125 : lastEndIndex = index + 1;
3035 : }
3036 : }
3037 :
3038 : // Add substring for tail of string (after last match).
3039 66 : JSString* sub = NewDependentString(cx, str, lastEndIndex, textLen - lastEndIndex);
3040 66 : if (!sub || !splits.append(StringValue(sub)))
3041 0 : return nullptr;
3042 :
3043 66 : return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
3044 : }
3045 :
3046 : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
3047 : static JSObject*
3048 95 : SplitSingleCharHelper(JSContext* cx, HandleLinearString str, char16_t ch, HandleObjectGroup group) {
3049 :
3050 : // Step 12.
3051 95 : size_t strLength = str->length();
3052 :
3053 190 : AutoStableStringChars linearChars(cx);
3054 95 : if (!linearChars.init(cx, str))
3055 0 : return nullptr;
3056 :
3057 95 : if (linearChars.isLatin1()) {
3058 95 : return SplitSingleCharHelper(cx, str, linearChars.latin1Chars(), strLength, ch, group);
3059 : } else {
3060 0 : return SplitSingleCharHelper(cx, str, linearChars.twoByteChars(), strLength, ch, group);
3061 : }
3062 : }
3063 :
3064 : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
3065 : JSObject*
3066 97 : js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
3067 :
3068 : {
3069 194 : RootedLinearString linearStr(cx, str->ensureLinear(cx));
3070 97 : if (!linearStr)
3071 0 : return nullptr;
3072 :
3073 194 : RootedLinearString linearSep(cx, sep->ensureLinear(cx));
3074 97 : if (!linearSep)
3075 0 : return nullptr;
3076 :
3077 97 : if (linearSep->length() == 0)
3078 0 : return CharSplitHelper(cx, linearStr, limit, group);
3079 :
3080 97 : if (linearSep->length() == 1 && limit >= static_cast<uint32_t>(INT32_MAX)) {
3081 95 : char16_t ch = linearSep->latin1OrTwoByteChar(0);
3082 95 : return SplitSingleCharHelper(cx, linearStr, ch, group);
3083 : }
3084 :
3085 2 : return SplitHelper(cx, linearStr, limit, linearSep, group);
3086 : }
3087 :
3088 : /*
3089 : * Python-esque sequence operations.
3090 : */
3091 : bool
3092 0 : js::str_concat(JSContext* cx, unsigned argc, Value* vp)
3093 : {
3094 0 : CallArgs args = CallArgsFromVp(argc, vp);
3095 0 : JSString* str = ToStringForStringFunction(cx, args.thisv());
3096 0 : if (!str)
3097 0 : return false;
3098 :
3099 0 : for (unsigned i = 0; i < args.length(); i++) {
3100 0 : JSString* argStr = ToString<NoGC>(cx, args[i]);
3101 0 : if (!argStr) {
3102 0 : RootedString strRoot(cx, str);
3103 0 : argStr = ToString<CanGC>(cx, args[i]);
3104 0 : if (!argStr)
3105 0 : return false;
3106 0 : str = strRoot;
3107 : }
3108 :
3109 0 : JSString* next = ConcatStrings<NoGC>(cx, str, argStr);
3110 0 : if (next) {
3111 0 : str = next;
3112 : } else {
3113 0 : RootedString strRoot(cx, str), argStrRoot(cx, argStr);
3114 0 : str = ConcatStrings<CanGC>(cx, strRoot, argStrRoot);
3115 0 : if (!str)
3116 0 : return false;
3117 : }
3118 : }
3119 :
3120 0 : args.rval().setString(str);
3121 0 : return true;
3122 : }
3123 :
3124 : static const JSFunctionSpec string_methods[] = {
3125 : #if JS_HAS_TOSOURCE
3126 : JS_FN(js_toSource_str, str_toSource, 0,0),
3127 : #endif
3128 :
3129 : /* Java-like methods. */
3130 : JS_FN(js_toString_str, str_toString, 0,0),
3131 : JS_FN(js_valueOf_str, str_toString, 0,0),
3132 : JS_FN("toLowerCase", str_toLowerCase, 0,0),
3133 : JS_FN("toUpperCase", str_toUpperCase, 0,0),
3134 : JS_INLINABLE_FN("charAt", str_charAt, 1,0, StringCharAt),
3135 : JS_INLINABLE_FN("charCodeAt", str_charCodeAt, 1,0, StringCharCodeAt),
3136 : JS_SELF_HOSTED_FN("substring", "String_substring", 2,0),
3137 : JS_SELF_HOSTED_FN("padStart", "String_pad_start", 2,0),
3138 : JS_SELF_HOSTED_FN("padEnd", "String_pad_end", 2,0),
3139 : JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
3140 : JS_FN("includes", str_includes, 1,0),
3141 : JS_FN("indexOf", str_indexOf, 1,0),
3142 : JS_FN("lastIndexOf", str_lastIndexOf, 1,0),
3143 : JS_FN("startsWith", str_startsWith, 1,0),
3144 : JS_FN("endsWith", str_endsWith, 1,0),
3145 : JS_FN("trim", str_trim, 0,0),
3146 : JS_FN("trimLeft", str_trimLeft, 0,0),
3147 : JS_FN("trimRight", str_trimRight, 0,0),
3148 : #if EXPOSE_INTL_API
3149 : JS_SELF_HOSTED_FN("toLocaleLowerCase", "String_toLocaleLowerCase", 0,0),
3150 : JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
3151 : JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
3152 : #else
3153 : JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
3154 : JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
3155 : JS_FN("localeCompare", str_localeCompare, 1,0),
3156 : #endif
3157 : JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
3158 : #if EXPOSE_INTL_API
3159 : JS_FN("normalize", str_normalize, 0,0),
3160 : #endif
3161 :
3162 : /* Perl-ish methods (search is actually Python-esque). */
3163 : JS_SELF_HOSTED_FN("match", "String_match", 1,0),
3164 : JS_SELF_HOSTED_FN("search", "String_search", 1,0),
3165 : JS_SELF_HOSTED_FN("replace", "String_replace", 2,0),
3166 : JS_SELF_HOSTED_FN("split", "String_split", 2,0),
3167 : JS_SELF_HOSTED_FN("substr", "String_substr", 2,0),
3168 :
3169 : /* Python-esque sequence methods. */
3170 : JS_FN("concat", str_concat, 1,0),
3171 : JS_SELF_HOSTED_FN("slice", "String_slice", 2,0),
3172 :
3173 : /* HTML string methods. */
3174 : JS_SELF_HOSTED_FN("bold", "String_bold", 0,0),
3175 : JS_SELF_HOSTED_FN("italics", "String_italics", 0,0),
3176 : JS_SELF_HOSTED_FN("fixed", "String_fixed", 0,0),
3177 : JS_SELF_HOSTED_FN("strike", "String_strike", 0,0),
3178 : JS_SELF_HOSTED_FN("small", "String_small", 0,0),
3179 : JS_SELF_HOSTED_FN("big", "String_big", 0,0),
3180 : JS_SELF_HOSTED_FN("blink", "String_blink", 0,0),
3181 : JS_SELF_HOSTED_FN("sup", "String_sup", 0,0),
3182 : JS_SELF_HOSTED_FN("sub", "String_sub", 0,0),
3183 : JS_SELF_HOSTED_FN("anchor", "String_anchor", 1,0),
3184 : JS_SELF_HOSTED_FN("link", "String_link", 1,0),
3185 : JS_SELF_HOSTED_FN("fontcolor","String_fontcolor", 1,0),
3186 : JS_SELF_HOSTED_FN("fontsize", "String_fontsize", 1,0),
3187 :
3188 : JS_SELF_HOSTED_SYM_FN(iterator, "String_iterator", 0,0),
3189 : JS_FS_END
3190 : };
3191 :
3192 : // ES6 rev 27 (2014 Aug 24) 21.1.1
3193 : bool
3194 7 : js::StringConstructor(JSContext* cx, unsigned argc, Value* vp)
3195 : {
3196 7 : CallArgs args = CallArgsFromVp(argc, vp);
3197 :
3198 14 : RootedString str(cx);
3199 7 : if (args.length() > 0) {
3200 7 : if (!args.isConstructing() && args[0].isSymbol())
3201 0 : return js::SymbolDescriptiveString(cx, args[0].toSymbol(), args.rval());
3202 :
3203 7 : str = ToString<CanGC>(cx, args[0]);
3204 7 : if (!str)
3205 0 : return false;
3206 : } else {
3207 0 : str = cx->runtime()->emptyString;
3208 : }
3209 :
3210 7 : if (args.isConstructing()) {
3211 2 : RootedObject proto(cx);
3212 1 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
3213 0 : return false;
3214 :
3215 1 : StringObject* strobj = StringObject::create(cx, str, proto);
3216 1 : if (!strobj)
3217 0 : return false;
3218 1 : args.rval().setObject(*strobj);
3219 1 : return true;
3220 : }
3221 :
3222 6 : args.rval().setString(str);
3223 6 : return true;
3224 : }
3225 :
3226 : static bool
3227 0 : str_fromCharCode_few_args(JSContext* cx, const CallArgs& args)
3228 : {
3229 0 : MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE);
3230 :
3231 : char16_t chars[JSFatInlineString::MAX_LENGTH_TWO_BYTE];
3232 0 : for (unsigned i = 0; i < args.length(); i++) {
3233 : uint16_t code;
3234 0 : if (!ToUint16(cx, args[i], &code))
3235 0 : return false;
3236 0 : chars[i] = char16_t(code);
3237 : }
3238 0 : JSString* str = NewStringCopyN<CanGC>(cx, chars, args.length());
3239 0 : if (!str)
3240 0 : return false;
3241 0 : args.rval().setString(str);
3242 0 : return true;
3243 : }
3244 :
3245 : bool
3246 0 : js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp)
3247 : {
3248 0 : CallArgs args = CallArgsFromVp(argc, vp);
3249 :
3250 0 : MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
3251 :
3252 : // Optimize the single-char case.
3253 0 : if (args.length() == 1)
3254 0 : return str_fromCharCode_one_arg(cx, args[0], args.rval());
3255 :
3256 : // Optimize the case where the result will definitely fit in an inline
3257 : // string (thin or fat) and so we don't need to malloc the chars. (We could
3258 : // cover some cases where args.length() goes up to
3259 : // JSFatInlineString::MAX_LENGTH_LATIN1 if we also checked if the chars are
3260 : // all Latin-1, but it doesn't seem worth the effort.)
3261 0 : if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE)
3262 0 : return str_fromCharCode_few_args(cx, args);
3263 :
3264 0 : char16_t* chars = cx->pod_malloc<char16_t>(args.length() + 1);
3265 0 : if (!chars)
3266 0 : return false;
3267 0 : for (unsigned i = 0; i < args.length(); i++) {
3268 : uint16_t code;
3269 0 : if (!ToUint16(cx, args[i], &code)) {
3270 0 : js_free(chars);
3271 0 : return false;
3272 : }
3273 0 : chars[i] = char16_t(code);
3274 : }
3275 0 : chars[args.length()] = 0;
3276 0 : JSString* str = NewString<CanGC>(cx, chars, args.length());
3277 0 : if (!str) {
3278 0 : js_free(chars);
3279 0 : return false;
3280 : }
3281 :
3282 0 : args.rval().setString(str);
3283 0 : return true;
3284 : }
3285 :
3286 : static inline bool
3287 0 : CodeUnitToString(JSContext* cx, uint16_t ucode, MutableHandleValue rval)
3288 : {
3289 0 : if (StaticStrings::hasUnit(ucode)) {
3290 0 : rval.setString(cx->staticStrings().getUnit(ucode));
3291 0 : return true;
3292 : }
3293 :
3294 0 : char16_t c = char16_t(ucode);
3295 0 : JSString* str = NewStringCopyN<CanGC>(cx, &c, 1);
3296 0 : if (!str)
3297 0 : return false;
3298 :
3299 0 : rval.setString(str);
3300 0 : return true;
3301 : }
3302 :
3303 : bool
3304 0 : js::str_fromCharCode_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
3305 : {
3306 : uint16_t ucode;
3307 :
3308 0 : if (!ToUint16(cx, code, &ucode))
3309 0 : return false;
3310 :
3311 0 : return CodeUnitToString(cx, ucode, rval);
3312 : }
3313 :
3314 : static MOZ_ALWAYS_INLINE bool
3315 0 : ToCodePoint(JSContext* cx, HandleValue code, uint32_t* codePoint)
3316 : {
3317 : // String.fromCodePoint, Steps 5.a-b.
3318 : double nextCP;
3319 0 : if (!ToNumber(cx, code, &nextCP))
3320 0 : return false;
3321 :
3322 : // String.fromCodePoint, Steps 5.c-d.
3323 0 : if (JS::ToInteger(nextCP) != nextCP || nextCP < 0 || nextCP > unicode::NonBMPMax) {
3324 0 : ToCStringBuf cbuf;
3325 0 : if (char* numStr = NumberToCString(cx, &cbuf, nextCP))
3326 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_A_CODEPOINT, numStr);
3327 0 : return false;
3328 : }
3329 :
3330 0 : *codePoint = uint32_t(nextCP);
3331 0 : return true;
3332 : }
3333 :
3334 : bool
3335 0 : js::str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
3336 : {
3337 : // Steps 1-4 (omitted).
3338 :
3339 : // Steps 5.a-d.
3340 : uint32_t codePoint;
3341 0 : if (!ToCodePoint(cx, code, &codePoint))
3342 0 : return false;
3343 :
3344 : // Steps 5.e, 6.
3345 0 : if (!unicode::IsSupplementary(codePoint))
3346 0 : return CodeUnitToString(cx, uint16_t(codePoint), rval);
3347 :
3348 0 : char16_t chars[] = { unicode::LeadSurrogate(codePoint), unicode::TrailSurrogate(codePoint) };
3349 0 : JSString* str = NewStringCopyNDontDeflate<CanGC>(cx, chars, 2);
3350 0 : if (!str)
3351 0 : return false;
3352 :
3353 0 : rval.setString(str);
3354 0 : return true;
3355 : }
3356 :
3357 : static bool
3358 0 : str_fromCodePoint_few_args(JSContext* cx, const CallArgs& args)
3359 : {
3360 0 : MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE / 2);
3361 :
3362 : // Steps 1-2 (omitted).
3363 :
3364 : // Step 3.
3365 : char16_t elements[JSFatInlineString::MAX_LENGTH_TWO_BYTE];
3366 :
3367 : // Steps 4-5.
3368 0 : unsigned length = 0;
3369 0 : for (unsigned nextIndex = 0; nextIndex < args.length(); nextIndex++) {
3370 : // Steps 5.a-d.
3371 : uint32_t codePoint;
3372 0 : if (!ToCodePoint(cx, args[nextIndex], &codePoint))
3373 0 : return false;
3374 :
3375 : // Step 5.e.
3376 0 : unicode::UTF16Encode(codePoint, elements, &length);
3377 : }
3378 :
3379 : // Step 6.
3380 0 : JSString* str = NewStringCopyN<CanGC>(cx, elements, length);
3381 0 : if (!str)
3382 0 : return false;
3383 :
3384 0 : args.rval().setString(str);
3385 0 : return true;
3386 : }
3387 :
3388 : // ES2017 draft rev 40edb3a95a475c1b251141ac681b8793129d9a6d
3389 : // 21.1.2.2 String.fromCodePoint(...codePoints)
3390 : bool
3391 0 : js::str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp)
3392 : {
3393 0 : CallArgs args = CallArgsFromVp(argc, vp);
3394 :
3395 : // Optimize the single code-point case.
3396 0 : if (args.length() == 1)
3397 0 : return str_fromCodePoint_one_arg(cx, args[0], args.rval());
3398 :
3399 : // Optimize the case where the result will definitely fit in an inline
3400 : // string (thin or fat) and so we don't need to malloc the chars. (We could
3401 : // cover some cases where |args.length()| goes up to
3402 : // JSFatInlineString::MAX_LENGTH_LATIN1 / 2 if we also checked if the chars
3403 : // are all Latin-1, but it doesn't seem worth the effort.)
3404 0 : if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE / 2)
3405 0 : return str_fromCodePoint_few_args(cx, args);
3406 :
3407 : // Steps 1-2 (omitted).
3408 :
3409 : // Step 3.
3410 : static_assert(ARGS_LENGTH_MAX < std::numeric_limits<size_t>::max() / 2,
3411 : "|args.length() * 2 + 1| does not overflow");
3412 0 : char16_t* elements = cx->pod_malloc<char16_t>(args.length() * 2 + 1);
3413 0 : if (!elements)
3414 0 : return false;
3415 :
3416 : // Steps 4-5.
3417 0 : unsigned length = 0;
3418 0 : for (unsigned nextIndex = 0; nextIndex < args.length(); nextIndex++) {
3419 : // Steps 5.a-d.
3420 : uint32_t codePoint;
3421 0 : if (!ToCodePoint(cx, args[nextIndex], &codePoint)) {
3422 0 : js_free(elements);
3423 0 : return false;
3424 : }
3425 :
3426 : // Step 5.e.
3427 0 : unicode::UTF16Encode(codePoint, elements, &length);
3428 : }
3429 0 : elements[length] = 0;
3430 :
3431 : // Step 6.
3432 0 : JSString* str = NewString<CanGC>(cx, elements, length);
3433 0 : if (!str) {
3434 0 : js_free(elements);
3435 0 : return false;
3436 : }
3437 :
3438 0 : args.rval().setString(str);
3439 0 : return true;
3440 : }
3441 :
3442 : static const JSFunctionSpec string_static_methods[] = {
3443 : JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode),
3444 : JS_INLINABLE_FN("fromCodePoint", js::str_fromCodePoint, 1, 0, StringFromCodePoint),
3445 :
3446 : JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,0),
3447 : JS_SELF_HOSTED_FN("substring", "String_static_substring", 3,0),
3448 : JS_SELF_HOSTED_FN("substr", "String_static_substr", 3,0),
3449 : JS_SELF_HOSTED_FN("slice", "String_static_slice", 3,0),
3450 :
3451 : JS_SELF_HOSTED_FN("match", "String_generic_match", 2,0),
3452 : JS_SELF_HOSTED_FN("replace", "String_generic_replace", 3,0),
3453 : JS_SELF_HOSTED_FN("search", "String_generic_search", 2,0),
3454 : JS_SELF_HOSTED_FN("split", "String_generic_split", 3,0),
3455 :
3456 : JS_SELF_HOSTED_FN("toLowerCase", "String_static_toLowerCase", 1,0),
3457 : JS_SELF_HOSTED_FN("toUpperCase", "String_static_toUpperCase", 1,0),
3458 : JS_SELF_HOSTED_FN("charAt", "String_static_charAt", 2,0),
3459 : JS_SELF_HOSTED_FN("charCodeAt", "String_static_charCodeAt", 2,0),
3460 : JS_SELF_HOSTED_FN("includes", "String_static_includes", 2,0),
3461 : JS_SELF_HOSTED_FN("indexOf", "String_static_indexOf", 2,0),
3462 : JS_SELF_HOSTED_FN("lastIndexOf", "String_static_lastIndexOf", 2,0),
3463 : JS_SELF_HOSTED_FN("startsWith", "String_static_startsWith", 2,0),
3464 : JS_SELF_HOSTED_FN("endsWith", "String_static_endsWith", 2,0),
3465 : JS_SELF_HOSTED_FN("trim", "String_static_trim", 1,0),
3466 : JS_SELF_HOSTED_FN("trimLeft", "String_static_trimLeft", 1,0),
3467 : JS_SELF_HOSTED_FN("trimRight", "String_static_trimRight", 1,0),
3468 : JS_SELF_HOSTED_FN("toLocaleLowerCase","String_static_toLocaleLowerCase",1,0),
3469 : JS_SELF_HOSTED_FN("toLocaleUpperCase","String_static_toLocaleUpperCase",1,0),
3470 : #if EXPOSE_INTL_API
3471 : JS_SELF_HOSTED_FN("normalize", "String_static_normalize", 1,0),
3472 : #endif
3473 : JS_SELF_HOSTED_FN("concat", "String_static_concat", 2,0),
3474 :
3475 : JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0),
3476 : JS_FS_END
3477 : };
3478 :
3479 : /* static */ Shape*
3480 49 : StringObject::assignInitialShape(JSContext* cx, Handle<StringObject*> obj)
3481 : {
3482 49 : MOZ_ASSERT(obj->empty());
3483 :
3484 98 : return NativeObject::addDataProperty(cx, obj, cx->names().length, LENGTH_SLOT,
3485 98 : JSPROP_PERMANENT | JSPROP_READONLY);
3486 : }
3487 :
3488 : JSObject*
3489 48 : js::InitStringClass(JSContext* cx, HandleObject obj)
3490 : {
3491 48 : MOZ_ASSERT(obj->isNative());
3492 :
3493 48 : Handle<GlobalObject*> global = obj.as<GlobalObject>();
3494 :
3495 96 : Rooted<JSString*> empty(cx, cx->runtime()->emptyString);
3496 96 : RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &StringObject::class_));
3497 48 : if (!proto)
3498 0 : return nullptr;
3499 48 : Handle<StringObject*> protoObj = proto.as<StringObject>();
3500 48 : if (!StringObject::init(cx, protoObj, empty))
3501 0 : return nullptr;
3502 :
3503 : /* Now create the String function. */
3504 96 : RootedFunction ctor(cx);
3505 144 : ctor = GlobalObject::createConstructor(cx, StringConstructor, cx->names().String, 1,
3506 96 : AllocKind::FUNCTION, &jit::JitInfo_String);
3507 48 : if (!ctor)
3508 0 : return nullptr;
3509 :
3510 48 : if (!LinkConstructorAndPrototype(cx, ctor, proto))
3511 0 : return nullptr;
3512 :
3513 192 : if (!DefinePropertiesAndFunctions(cx, proto, nullptr, string_methods) ||
3514 144 : !DefinePropertiesAndFunctions(cx, ctor, nullptr, string_static_methods))
3515 : {
3516 0 : return nullptr;
3517 : }
3518 :
3519 : /*
3520 : * Define escape/unescape, the URI encode/decode functions, and maybe
3521 : * uneval on the global object.
3522 : */
3523 48 : if (!JS_DefineFunctions(cx, global, string_functions))
3524 0 : return nullptr;
3525 :
3526 48 : if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
3527 0 : return nullptr;
3528 :
3529 48 : return proto;
3530 : }
3531 :
3532 : const char*
3533 0 : js::ValueToPrintable(JSContext* cx, const Value& vArg, JSAutoByteString* bytes, bool asSource)
3534 : {
3535 0 : RootedValue v(cx, vArg);
3536 : JSString* str;
3537 0 : if (asSource)
3538 0 : str = ValueToSource(cx, v);
3539 : else
3540 0 : str = ToString<CanGC>(cx, v);
3541 0 : if (!str)
3542 0 : return nullptr;
3543 0 : str = QuoteString(cx, str, 0);
3544 0 : if (!str)
3545 0 : return nullptr;
3546 0 : return bytes->encodeLatin1(cx, str);
3547 : }
3548 :
3549 : template <AllowGC allowGC>
3550 : JSString*
3551 90 : js::ToStringSlow(JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
3552 : {
3553 : /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
3554 90 : MOZ_ASSERT(!arg.isString());
3555 :
3556 90 : Value v = arg;
3557 90 : if (!v.isPrimitive()) {
3558 2 : MOZ_ASSERT(!cx->helperThread());
3559 : if (!allowGC)
3560 0 : return nullptr;
3561 4 : RootedValue v2(cx, v);
3562 2 : if (!ToPrimitive(cx, JSTYPE_STRING, &v2))
3563 0 : return nullptr;
3564 2 : v = v2;
3565 : }
3566 :
3567 : JSString* str;
3568 90 : if (v.isString()) {
3569 2 : str = v.toString();
3570 88 : } else if (v.isInt32()) {
3571 45 : str = Int32ToString<allowGC>(cx, v.toInt32());
3572 43 : } else if (v.isDouble()) {
3573 8 : str = NumberToString<allowGC>(cx, v.toDouble());
3574 35 : } else if (v.isBoolean()) {
3575 29 : str = BooleanToString(cx, v.toBoolean());
3576 6 : } else if (v.isNull()) {
3577 5 : str = cx->names().null;
3578 1 : } else if (v.isSymbol()) {
3579 0 : MOZ_ASSERT(!cx->helperThread());
3580 : if (allowGC) {
3581 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3582 : JSMSG_SYMBOL_TO_STRING);
3583 : }
3584 0 : return nullptr;
3585 : } else {
3586 1 : MOZ_ASSERT(v.isUndefined());
3587 1 : str = cx->names().undefined;
3588 : }
3589 90 : return str;
3590 : }
3591 :
3592 : template JSString*
3593 : js::ToStringSlow<CanGC>(JSContext* cx, HandleValue arg);
3594 :
3595 : template JSString*
3596 : js::ToStringSlow<NoGC>(JSContext* cx, const Value& arg);
3597 :
3598 : JS_PUBLIC_API(JSString*)
3599 28 : js::ToStringSlow(JSContext* cx, HandleValue v)
3600 : {
3601 28 : return ToStringSlow<CanGC>(cx, v);
3602 : }
3603 :
3604 : static JSString*
3605 0 : SymbolToSource(JSContext* cx, Symbol* symbol)
3606 : {
3607 0 : RootedString desc(cx, symbol->description());
3608 0 : SymbolCode code = symbol->code();
3609 0 : if (code != SymbolCode::InSymbolRegistry && code != SymbolCode::UniqueSymbol) {
3610 : // Well-known symbol.
3611 0 : MOZ_ASSERT(uint32_t(code) < JS::WellKnownSymbolLimit);
3612 0 : return desc;
3613 : }
3614 :
3615 0 : StringBuffer buf(cx);
3616 0 : if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(") : !buf.append("Symbol("))
3617 0 : return nullptr;
3618 0 : if (desc) {
3619 0 : desc = StringToSource(cx, desc);
3620 0 : if (!desc || !buf.append(desc))
3621 0 : return nullptr;
3622 : }
3623 0 : if (!buf.append(')'))
3624 0 : return nullptr;
3625 0 : return buf.finishString();
3626 : }
3627 :
3628 : JSString*
3629 3 : js::ValueToSource(JSContext* cx, HandleValue v)
3630 : {
3631 3 : if (!CheckRecursionLimit(cx))
3632 0 : return nullptr;
3633 3 : assertSameCompartment(cx, v);
3634 :
3635 3 : if (v.isUndefined())
3636 0 : return cx->names().void0;
3637 3 : if (v.isString())
3638 3 : return StringToSource(cx, v.toString());
3639 0 : if (v.isSymbol())
3640 0 : return SymbolToSource(cx, v.toSymbol());
3641 0 : if (v.isPrimitive()) {
3642 : /* Special case to preserve negative zero, _contra_ toString. */
3643 0 : if (v.isDouble() && IsNegativeZero(v.toDouble())) {
3644 : /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
3645 : static const char16_t js_negzero_ucNstr[] = {'-', '0'};
3646 :
3647 0 : return NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2);
3648 : }
3649 0 : return ToString<CanGC>(cx, v);
3650 : }
3651 :
3652 0 : RootedValue fval(cx);
3653 0 : RootedObject obj(cx, &v.toObject());
3654 0 : if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval))
3655 0 : return nullptr;
3656 0 : if (IsCallable(fval)) {
3657 0 : RootedValue v(cx);
3658 0 : if (!js::Call(cx, fval, obj, &v))
3659 0 : return nullptr;
3660 :
3661 0 : return ToString<CanGC>(cx, v);
3662 : }
3663 :
3664 0 : return ObjectToSource(cx, obj);
3665 : }
3666 :
3667 : JSString*
3668 3 : js::StringToSource(JSContext* cx, JSString* str)
3669 : {
3670 3 : return QuoteString(cx, str, '"');
3671 : }
3672 :
3673 : bool
3674 1332 : js::EqualChars(JSLinearString* str1, JSLinearString* str2)
3675 : {
3676 1332 : MOZ_ASSERT(str1->length() == str2->length());
3677 :
3678 1332 : size_t len = str1->length();
3679 :
3680 2664 : AutoCheckCannotGC nogc;
3681 1332 : if (str1->hasTwoByteChars()) {
3682 102 : if (str2->hasTwoByteChars())
3683 1 : return PodEqual(str1->twoByteChars(nogc), str2->twoByteChars(nogc), len);
3684 :
3685 101 : return EqualChars(str2->latin1Chars(nogc), str1->twoByteChars(nogc), len);
3686 : }
3687 :
3688 1230 : if (str2->hasLatin1Chars())
3689 1228 : return PodEqual(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len);
3690 :
3691 2 : return EqualChars(str1->latin1Chars(nogc), str2->twoByteChars(nogc), len);
3692 : }
3693 :
3694 : bool
3695 7159 : js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2, bool* result)
3696 : {
3697 7159 : if (str1 == str2) {
3698 2698 : *result = true;
3699 2698 : return true;
3700 : }
3701 :
3702 4461 : size_t length1 = str1->length();
3703 4461 : if (length1 != str2->length()) {
3704 3537 : *result = false;
3705 3537 : return true;
3706 : }
3707 :
3708 924 : JSLinearString* linear1 = str1->ensureLinear(cx);
3709 924 : if (!linear1)
3710 0 : return false;
3711 924 : JSLinearString* linear2 = str2->ensureLinear(cx);
3712 924 : if (!linear2)
3713 0 : return false;
3714 :
3715 924 : *result = EqualChars(linear1, linear2);
3716 924 : return true;
3717 : }
3718 :
3719 : bool
3720 273 : js::EqualStrings(JSLinearString* str1, JSLinearString* str2)
3721 : {
3722 273 : if (str1 == str2)
3723 0 : return true;
3724 :
3725 273 : size_t length1 = str1->length();
3726 273 : if (length1 != str2->length())
3727 273 : return false;
3728 :
3729 0 : return EqualChars(str1, str2);
3730 : }
3731 :
3732 : static int32_t
3733 72 : CompareStringsImpl(JSLinearString* str1, JSLinearString* str2)
3734 : {
3735 72 : size_t len1 = str1->length();
3736 72 : size_t len2 = str2->length();
3737 :
3738 144 : AutoCheckCannotGC nogc;
3739 72 : if (str1->hasLatin1Chars()) {
3740 72 : const Latin1Char* chars1 = str1->latin1Chars(nogc);
3741 72 : return str2->hasLatin1Chars()
3742 72 : ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
3743 72 : : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
3744 : }
3745 :
3746 0 : const char16_t* chars1 = str1->twoByteChars(nogc);
3747 0 : return str2->hasLatin1Chars()
3748 0 : ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
3749 0 : : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
3750 : }
3751 :
3752 : int32_t
3753 0 : js::CompareChars(const char16_t* s1, size_t len1, JSLinearString* s2)
3754 : {
3755 0 : AutoCheckCannotGC nogc;
3756 0 : return s2->hasLatin1Chars()
3757 0 : ? CompareChars(s1, len1, s2->latin1Chars(nogc), s2->length())
3758 0 : : CompareChars(s1, len1, s2->twoByteChars(nogc), s2->length());
3759 : }
3760 :
3761 : bool
3762 72 : js::CompareStrings(JSContext* cx, JSString* str1, JSString* str2, int32_t* result)
3763 : {
3764 72 : MOZ_ASSERT(str1);
3765 72 : MOZ_ASSERT(str2);
3766 :
3767 72 : if (str1 == str2) {
3768 0 : *result = 0;
3769 0 : return true;
3770 : }
3771 :
3772 72 : JSLinearString* linear1 = str1->ensureLinear(cx);
3773 72 : if (!linear1)
3774 0 : return false;
3775 :
3776 72 : JSLinearString* linear2 = str2->ensureLinear(cx);
3777 72 : if (!linear2)
3778 0 : return false;
3779 :
3780 72 : *result = CompareStringsImpl(linear1, linear2);
3781 72 : return true;
3782 : }
3783 :
3784 : int32_t
3785 0 : js::CompareAtoms(JSAtom* atom1, JSAtom* atom2)
3786 : {
3787 0 : return CompareStringsImpl(atom1, atom2);
3788 : }
3789 :
3790 : bool
3791 797 : js::StringEqualsAscii(JSLinearString* str, const char* asciiBytes)
3792 : {
3793 797 : size_t length = strlen(asciiBytes);
3794 : #ifdef DEBUG
3795 10179 : for (size_t i = 0; i != length; ++i)
3796 9382 : MOZ_ASSERT(unsigned(asciiBytes[i]) <= 127);
3797 : #endif
3798 797 : if (length != str->length())
3799 679 : return false;
3800 :
3801 118 : const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes);
3802 :
3803 236 : AutoCheckCannotGC nogc;
3804 118 : return str->hasLatin1Chars()
3805 118 : ? PodEqual(latin1, str->latin1Chars(nogc), length)
3806 118 : : EqualChars(latin1, str->twoByteChars(nogc), length);
3807 : }
3808 :
3809 : size_t
3810 837 : js_strlen(const char16_t* s)
3811 : {
3812 : const char16_t* t;
3813 :
3814 40941 : for (t = s; *t != 0; t++)
3815 40104 : continue;
3816 837 : return (size_t)(t - s);
3817 : }
3818 :
3819 : int32_t
3820 0 : js_strcmp(const char16_t* lhs, const char16_t* rhs)
3821 : {
3822 : while (true) {
3823 0 : if (*lhs != *rhs)
3824 0 : return int32_t(*lhs) - int32_t(*rhs);
3825 0 : if (*lhs == 0)
3826 0 : return 0;
3827 0 : ++lhs; ++rhs;
3828 : }
3829 : }
3830 :
3831 : int32_t
3832 0 : js_fputs(const char16_t* s, FILE* f)
3833 : {
3834 0 : while (*s != 0) {
3835 0 : if (fputwc(wchar_t(*s), f) == WEOF)
3836 0 : return WEOF;
3837 0 : s++;
3838 : }
3839 0 : return 1;
3840 : }
3841 :
3842 : UniqueChars
3843 1784 : js::DuplicateString(JSContext* cx, const char* s)
3844 : {
3845 1784 : size_t n = strlen(s) + 1;
3846 1784 : auto ret = cx->make_pod_array<char>(n);
3847 1784 : if (!ret)
3848 0 : return ret;
3849 1784 : PodCopy(ret.get(), s, n);
3850 1784 : return ret;
3851 : }
3852 :
3853 : UniqueTwoByteChars
3854 2 : js::DuplicateString(JSContext* cx, const char16_t* s)
3855 : {
3856 2 : size_t n = js_strlen(s) + 1;
3857 2 : auto ret = cx->make_pod_array<char16_t>(n);
3858 2 : if (!ret)
3859 0 : return ret;
3860 2 : PodCopy(ret.get(), s, n);
3861 2 : return ret;
3862 : }
3863 :
3864 : UniqueChars
3865 0 : js::DuplicateString(const char* s)
3866 : {
3867 0 : return UniqueChars(js_strdup(s));
3868 : }
3869 :
3870 : UniqueChars
3871 0 : js::DuplicateString(const char* s, size_t n)
3872 : {
3873 0 : UniqueChars ret(js_pod_malloc<char>(n + 1));
3874 0 : if (!ret)
3875 0 : return nullptr;
3876 0 : PodCopy(ret.get(), s, n);
3877 0 : ret[n] = 0;
3878 0 : return ret;
3879 : }
3880 :
3881 : UniqueTwoByteChars
3882 0 : js::DuplicateString(const char16_t* s)
3883 : {
3884 0 : return DuplicateString(s, js_strlen(s));
3885 : }
3886 :
3887 : UniqueTwoByteChars
3888 95 : js::DuplicateString(const char16_t* s, size_t n)
3889 : {
3890 190 : UniqueTwoByteChars ret(js_pod_malloc<char16_t>(n + 1));
3891 95 : if (!ret)
3892 0 : return nullptr;
3893 95 : PodCopy(ret.get(), s, n);
3894 95 : ret[n] = 0;
3895 95 : return ret;
3896 : }
3897 :
3898 : template <typename CharT>
3899 : const CharT*
3900 280 : js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)
3901 : {
3902 532 : while (s < limit) {
3903 252 : if (*s == c)
3904 0 : return s;
3905 252 : s++;
3906 : }
3907 28 : return nullptr;
3908 : }
3909 :
3910 : template const Latin1Char*
3911 : js_strchr_limit(const Latin1Char* s, char16_t c, const Latin1Char* limit);
3912 :
3913 : template const char16_t*
3914 : js_strchr_limit(const char16_t* s, char16_t c, const char16_t* limit);
3915 :
3916 : char16_t*
3917 428 : js::InflateString(JSContext* cx, const char* bytes, size_t* lengthp)
3918 : {
3919 : size_t nchars;
3920 : char16_t* chars;
3921 428 : size_t nbytes = *lengthp;
3922 :
3923 428 : nchars = nbytes;
3924 428 : chars = cx->pod_malloc<char16_t>(nchars + 1);
3925 428 : if (!chars)
3926 0 : goto bad;
3927 3315994 : for (size_t i = 0; i < nchars; i++)
3928 3315566 : chars[i] = (unsigned char) bytes[i];
3929 428 : *lengthp = nchars;
3930 428 : chars[nchars] = 0;
3931 428 : return chars;
3932 :
3933 : bad:
3934 : // For compatibility with callers of JS_DecodeBytes we must zero lengthp
3935 : // on errors.
3936 0 : *lengthp = 0;
3937 0 : return nullptr;
3938 : }
3939 :
3940 : template <typename CharT>
3941 : bool
3942 3914 : js::DeflateStringToBuffer(JSContext* maybecx, const CharT* src, size_t srclen,
3943 : char* dst, size_t* dstlenp)
3944 : {
3945 3914 : size_t dstlen = *dstlenp;
3946 3914 : if (srclen > dstlen) {
3947 0 : for (size_t i = 0; i < dstlen; i++)
3948 0 : dst[i] = char(src[i]);
3949 0 : if (maybecx) {
3950 0 : AutoSuppressGC suppress(maybecx);
3951 0 : JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr,
3952 : JSMSG_BUFFER_TOO_SMALL);
3953 : }
3954 0 : return false;
3955 : }
3956 139369 : for (size_t i = 0; i < srclen; i++)
3957 135455 : dst[i] = char(src[i]);
3958 3914 : *dstlenp = srclen;
3959 3914 : return true;
3960 : }
3961 :
3962 : template bool
3963 : js::DeflateStringToBuffer(JSContext* maybecx, const Latin1Char* src, size_t srclen,
3964 : char* dst, size_t* dstlenp);
3965 :
3966 : template bool
3967 : js::DeflateStringToBuffer(JSContext* maybecx, const char16_t* src, size_t srclen,
3968 : char* dst, size_t* dstlenp);
3969 :
3970 : #define ____ false
3971 :
3972 : /*
3973 : * Identifier start chars:
3974 : * - 36: $
3975 : * - 65..90: A..Z
3976 : * - 95: _
3977 : * - 97..122: a..z
3978 : */
3979 : const bool js_isidstart[] = {
3980 : /* 0 1 2 3 4 5 6 7 8 9 */
3981 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3982 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3983 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3984 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
3985 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3986 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3987 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
3988 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
3989 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
3990 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
3991 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
3992 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
3993 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
3994 : };
3995 :
3996 : /*
3997 : * Identifier chars:
3998 : * - 36: $
3999 : * - 48..57: 0..9
4000 : * - 65..90: A..Z
4001 : * - 95: _
4002 : * - 97..122: a..z
4003 : */
4004 : const bool js_isident[] = {
4005 : /* 0 1 2 3 4 5 6 7 8 9 */
4006 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4007 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4008 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4009 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
4010 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
4011 : /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
4012 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
4013 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
4014 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
4015 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
4016 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
4017 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
4018 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
4019 : };
4020 :
4021 : /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
4022 : const bool js_isspace[] = {
4023 : /* 0 1 2 3 4 5 6 7 8 9 */
4024 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
4025 : /* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
4026 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4027 : /* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
4028 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4029 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4030 : /* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4031 : /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4032 : /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4033 : /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4034 : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4035 : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4036 : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
4037 : };
4038 :
4039 : /*
4040 : * Uri reserved chars + #:
4041 : * - 35: #
4042 : * - 36: $
4043 : * - 38: &
4044 : * - 43: +
4045 : * - 44: ,
4046 : * - 47: /
4047 : * - 58: :
4048 : * - 59: ;
4049 : * - 61: =
4050 : * - 63: ?
4051 : * - 64: @
4052 : */
4053 : static const bool js_isUriReservedPlusPound[] = {
4054 : /* 0 1 2 3 4 5 6 7 8 9 */
4055 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4056 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4057 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4058 : /* 3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____,
4059 : /* 4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____,
4060 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
4061 : /* 6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____,
4062 : /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4063 : /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4064 : /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4065 : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4066 : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4067 : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
4068 : };
4069 :
4070 : /*
4071 : * Uri unescaped chars:
4072 : * - 33: !
4073 : * - 39: '
4074 : * - 40: (
4075 : * - 41: )
4076 : * - 42: *
4077 : * - 45: -
4078 : * - 46: .
4079 : * - 48..57: 0-9
4080 : * - 65..90: A-Z
4081 : * - 95: _
4082 : * - 97..122: a-z
4083 : * - 126: ~
4084 : */
4085 : static const bool js_isUriUnescaped[] = {
4086 : /* 0 1 2 3 4 5 6 7 8 9 */
4087 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4088 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4089 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
4090 : /* 3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true,
4091 : /* 4 */ true, true, true, ____, ____, true, true, ____, true, true,
4092 : /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
4093 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
4094 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
4095 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
4096 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
4097 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
4098 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
4099 : /* 12 */ true, true, true, ____, ____, ____, true, ____
4100 : };
4101 :
4102 : #undef ____
4103 :
4104 : #define URI_CHUNK 64U
4105 :
4106 : static inline bool
4107 511 : TransferBufferToString(StringBuffer& sb, MutableHandleValue rval)
4108 : {
4109 511 : JSString* str = sb.finishString();
4110 511 : if (!str)
4111 0 : return false;
4112 511 : rval.setString(str);
4113 511 : return true;
4114 : }
4115 :
4116 : /*
4117 : * ECMA 3, 15.1.3 URI Handling Function Properties
4118 : *
4119 : * The following are implementations of the algorithms
4120 : * given in the ECMA specification for the hidden functions
4121 : * 'Encode' and 'Decode'.
4122 : */
4123 : enum EncodeResult { Encode_Failure, Encode_BadUri, Encode_Success };
4124 :
4125 : template <typename CharT>
4126 : static EncodeResult
4127 510 : Encode(StringBuffer& sb, const CharT* chars, size_t length,
4128 : const bool* unescapedSet, const bool* unescapedSet2)
4129 : {
4130 : static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
4131 :
4132 : char16_t hexBuf[4];
4133 510 : hexBuf[0] = '%';
4134 510 : hexBuf[3] = 0;
4135 :
4136 8040 : for (size_t k = 0; k < length; k++) {
4137 7530 : char16_t c = chars[k];
4138 7530 : if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) {
4139 14520 : if (!sb.append(c))
4140 0 : return Encode_Failure;
4141 : } else {
4142 270 : if (unicode::IsTrailSurrogate(c))
4143 0 : return Encode_BadUri;
4144 :
4145 : uint32_t v;
4146 270 : if (!unicode::IsLeadSurrogate(c)) {
4147 270 : v = c;
4148 : } else {
4149 0 : k++;
4150 0 : if (k == length)
4151 0 : return Encode_BadUri;
4152 :
4153 0 : char16_t c2 = chars[k];
4154 0 : if (!unicode::IsTrailSurrogate(c2))
4155 0 : return Encode_BadUri;
4156 :
4157 0 : v = unicode::UTF16Decode(c, c2);
4158 : }
4159 : uint8_t utf8buf[4];
4160 270 : size_t L = OneUcs4ToUtf8Char(utf8buf, v);
4161 540 : for (size_t j = 0; j < L; j++) {
4162 270 : hexBuf[1] = HexDigits[utf8buf[j] >> 4];
4163 270 : hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
4164 270 : if (!sb.append(hexBuf, 3))
4165 0 : return Encode_Failure;
4166 : }
4167 : }
4168 : }
4169 :
4170 510 : return Encode_Success;
4171 : }
4172 :
4173 : static bool
4174 510 : Encode(JSContext* cx, HandleLinearString str, const bool* unescapedSet,
4175 : const bool* unescapedSet2, MutableHandleValue rval)
4176 : {
4177 510 : size_t length = str->length();
4178 510 : if (length == 0) {
4179 0 : rval.setString(cx->runtime()->emptyString);
4180 0 : return true;
4181 : }
4182 :
4183 1020 : StringBuffer sb(cx);
4184 510 : if (!sb.reserve(length))
4185 0 : return false;
4186 :
4187 : EncodeResult res;
4188 510 : if (str->hasLatin1Chars()) {
4189 1020 : AutoCheckCannotGC nogc;
4190 510 : res = Encode(sb, str->latin1Chars(nogc), str->length(), unescapedSet, unescapedSet2);
4191 : } else {
4192 0 : AutoCheckCannotGC nogc;
4193 0 : res = Encode(sb, str->twoByteChars(nogc), str->length(), unescapedSet, unescapedSet2);
4194 : }
4195 :
4196 510 : if (res == Encode_Failure)
4197 0 : return false;
4198 :
4199 510 : if (res == Encode_BadUri) {
4200 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
4201 0 : return false;
4202 : }
4203 :
4204 510 : MOZ_ASSERT(res == Encode_Success);
4205 510 : return TransferBufferToString(sb, rval);
4206 : }
4207 :
4208 : enum DecodeResult { Decode_Failure, Decode_BadUri, Decode_Success };
4209 :
4210 : template <typename CharT>
4211 : static DecodeResult
4212 1 : Decode(StringBuffer& sb, const CharT* chars, size_t length, const bool* reservedSet)
4213 : {
4214 54 : for (size_t k = 0; k < length; k++) {
4215 53 : char16_t c = chars[k];
4216 53 : if (c == '%') {
4217 0 : size_t start = k;
4218 0 : if ((k + 2) >= length)
4219 0 : return Decode_BadUri;
4220 :
4221 0 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
4222 0 : return Decode_BadUri;
4223 :
4224 0 : uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
4225 0 : k += 2;
4226 0 : if (!(B & 0x80)) {
4227 0 : c = char16_t(B);
4228 : } else {
4229 0 : int n = 1;
4230 0 : while (B & (0x80 >> n))
4231 0 : n++;
4232 :
4233 0 : if (n == 1 || n > 4)
4234 0 : return Decode_BadUri;
4235 :
4236 : uint8_t octets[4];
4237 0 : octets[0] = (uint8_t)B;
4238 0 : if (k + 3 * (n - 1) >= length)
4239 0 : return Decode_BadUri;
4240 :
4241 0 : for (int j = 1; j < n; j++) {
4242 0 : k++;
4243 0 : if (chars[k] != '%')
4244 0 : return Decode_BadUri;
4245 :
4246 0 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
4247 0 : return Decode_BadUri;
4248 :
4249 0 : B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
4250 0 : if ((B & 0xC0) != 0x80)
4251 0 : return Decode_BadUri;
4252 :
4253 0 : k += 2;
4254 0 : octets[j] = char(B);
4255 : }
4256 0 : uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
4257 0 : if (v >= unicode::NonBMPMin) {
4258 0 : if (v > unicode::NonBMPMax)
4259 0 : return Decode_BadUri;
4260 :
4261 0 : char16_t H = unicode::LeadSurrogate(v);
4262 0 : if (!sb.append(H))
4263 0 : return Decode_Failure;
4264 0 : c = unicode::TrailSurrogate(v);
4265 : } else {
4266 0 : c = char16_t(v);
4267 : }
4268 : }
4269 0 : if (c < 128 && reservedSet && reservedSet[c]) {
4270 0 : if (!sb.append(chars + start, k - start + 1))
4271 0 : return Decode_Failure;
4272 : } else {
4273 0 : if (!sb.append(c))
4274 0 : return Decode_Failure;
4275 : }
4276 : } else {
4277 53 : if (!sb.append(c))
4278 0 : return Decode_Failure;
4279 : }
4280 : }
4281 :
4282 1 : return Decode_Success;
4283 : }
4284 :
4285 : static bool
4286 1 : Decode(JSContext* cx, HandleLinearString str, const bool* reservedSet, MutableHandleValue rval)
4287 : {
4288 1 : size_t length = str->length();
4289 1 : if (length == 0) {
4290 0 : rval.setString(cx->runtime()->emptyString);
4291 0 : return true;
4292 : }
4293 :
4294 2 : StringBuffer sb(cx);
4295 :
4296 : DecodeResult res;
4297 1 : if (str->hasLatin1Chars()) {
4298 2 : AutoCheckCannotGC nogc;
4299 1 : res = Decode(sb, str->latin1Chars(nogc), str->length(), reservedSet);
4300 : } else {
4301 0 : AutoCheckCannotGC nogc;
4302 0 : res = Decode(sb, str->twoByteChars(nogc), str->length(), reservedSet);
4303 : }
4304 :
4305 1 : if (res == Decode_Failure)
4306 0 : return false;
4307 :
4308 1 : if (res == Decode_BadUri) {
4309 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
4310 0 : return false;
4311 : }
4312 :
4313 1 : MOZ_ASSERT(res == Decode_Success);
4314 1 : return TransferBufferToString(sb, rval);
4315 : }
4316 :
4317 : static bool
4318 1 : str_decodeURI(JSContext* cx, unsigned argc, Value* vp)
4319 : {
4320 1 : CallArgs args = CallArgsFromVp(argc, vp);
4321 2 : RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
4322 1 : if (!str)
4323 0 : return false;
4324 :
4325 1 : return Decode(cx, str, js_isUriReservedPlusPound, args.rval());
4326 : }
4327 :
4328 : static bool
4329 0 : str_decodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
4330 : {
4331 0 : CallArgs args = CallArgsFromVp(argc, vp);
4332 0 : RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
4333 0 : if (!str)
4334 0 : return false;
4335 :
4336 0 : return Decode(cx, str, nullptr, args.rval());
4337 : }
4338 :
4339 : static bool
4340 0 : str_encodeURI(JSContext* cx, unsigned argc, Value* vp)
4341 : {
4342 0 : CallArgs args = CallArgsFromVp(argc, vp);
4343 0 : RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
4344 0 : if (!str)
4345 0 : return false;
4346 :
4347 0 : return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval());
4348 : }
4349 :
4350 : static bool
4351 510 : str_encodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
4352 : {
4353 510 : CallArgs args = CallArgsFromVp(argc, vp);
4354 1020 : RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
4355 510 : if (!str)
4356 0 : return false;
4357 :
4358 510 : return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
4359 : }
4360 :
4361 : bool
4362 0 : js::EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length)
4363 : {
4364 0 : EncodeResult result = Encode(sb, chars, length, js_isUriUnescaped, js_isUriReservedPlusPound);
4365 0 : if (result == EncodeResult::Encode_Failure)
4366 0 : return false;
4367 0 : if (result == EncodeResult::Encode_BadUri) {
4368 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
4369 0 : return false;
4370 : }
4371 0 : return true;
4372 : }
4373 :
4374 : /*
4375 : * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
4376 : * least 4 bytes long. Return the number of UTF-8 bytes of data written.
4377 : */
4378 : uint32_t
4379 270 : js::OneUcs4ToUtf8Char(uint8_t* utf8Buffer, uint32_t ucs4Char)
4380 : {
4381 270 : MOZ_ASSERT(ucs4Char <= unicode::NonBMPMax);
4382 :
4383 270 : if (ucs4Char < 0x80) {
4384 270 : utf8Buffer[0] = uint8_t(ucs4Char);
4385 270 : return 1;
4386 : }
4387 :
4388 0 : uint32_t a = ucs4Char >> 11;
4389 0 : uint32_t utf8Length = 2;
4390 0 : while (a) {
4391 0 : a >>= 5;
4392 0 : utf8Length++;
4393 : }
4394 :
4395 0 : MOZ_ASSERT(utf8Length <= 4);
4396 :
4397 0 : uint32_t i = utf8Length;
4398 0 : while (--i) {
4399 0 : utf8Buffer[i] = uint8_t((ucs4Char & 0x3F) | 0x80);
4400 0 : ucs4Char >>= 6;
4401 : }
4402 :
4403 0 : utf8Buffer[0] = uint8_t(0x100 - (1 << (8 - utf8Length)) + ucs4Char);
4404 0 : return utf8Length;
4405 : }
4406 :
4407 : size_t
4408 0 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, JSLinearString* str,
4409 : uint32_t quote)
4410 : {
4411 0 : size_t len = str->length();
4412 0 : AutoCheckCannotGC nogc;
4413 0 : return str->hasLatin1Chars()
4414 0 : ? PutEscapedStringImpl(buffer, bufferSize, out, str->latin1Chars(nogc), len, quote)
4415 0 : : PutEscapedStringImpl(buffer, bufferSize, out, str->twoByteChars(nogc), len, quote);
4416 : }
4417 :
4418 : template <typename CharT>
4419 : size_t
4420 0 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const CharT* chars,
4421 : size_t length, uint32_t quote)
4422 : {
4423 : enum {
4424 : STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
4425 : } state;
4426 :
4427 0 : MOZ_ASSERT(quote == 0 || quote == '\'' || quote == '"');
4428 0 : MOZ_ASSERT_IF(!buffer, bufferSize == 0);
4429 0 : MOZ_ASSERT_IF(out, !buffer);
4430 :
4431 0 : if (bufferSize == 0)
4432 0 : buffer = nullptr;
4433 : else
4434 0 : bufferSize--;
4435 :
4436 0 : const CharT* charsEnd = chars + length;
4437 0 : size_t n = 0;
4438 0 : state = FIRST_QUOTE;
4439 0 : unsigned shift = 0;
4440 0 : unsigned hex = 0;
4441 0 : unsigned u = 0;
4442 0 : char c = 0; /* to quell GCC warnings */
4443 :
4444 0 : for (;;) {
4445 0 : switch (state) {
4446 : case STOP:
4447 0 : goto stop;
4448 : case FIRST_QUOTE:
4449 0 : state = CHARS;
4450 0 : goto do_quote;
4451 : case LAST_QUOTE:
4452 0 : state = STOP;
4453 : do_quote:
4454 0 : if (quote == 0)
4455 0 : continue;
4456 0 : c = (char)quote;
4457 0 : break;
4458 : case CHARS:
4459 0 : if (chars == charsEnd) {
4460 0 : state = LAST_QUOTE;
4461 0 : continue;
4462 : }
4463 0 : u = *chars++;
4464 0 : if (u < ' ') {
4465 0 : if (u != 0) {
4466 0 : const char* escape = strchr(js_EscapeMap, (int)u);
4467 0 : if (escape) {
4468 0 : u = escape[1];
4469 0 : goto do_escape;
4470 : }
4471 : }
4472 0 : goto do_hex_escape;
4473 : }
4474 0 : if (u < 127) {
4475 0 : if (u == quote || u == '\\')
4476 : goto do_escape;
4477 0 : c = (char)u;
4478 0 : } else if (u < 0x100) {
4479 0 : goto do_hex_escape;
4480 : } else {
4481 0 : shift = 16;
4482 0 : hex = u;
4483 0 : u = 'u';
4484 0 : goto do_escape;
4485 : }
4486 0 : break;
4487 : do_hex_escape:
4488 0 : shift = 8;
4489 0 : hex = u;
4490 0 : u = 'x';
4491 : do_escape:
4492 0 : c = '\\';
4493 0 : state = ESCAPE_START;
4494 0 : break;
4495 : case ESCAPE_START:
4496 0 : MOZ_ASSERT(' ' <= u && u < 127);
4497 0 : c = (char)u;
4498 0 : state = ESCAPE_MORE;
4499 0 : break;
4500 : case ESCAPE_MORE:
4501 0 : if (shift == 0) {
4502 0 : state = CHARS;
4503 0 : continue;
4504 : }
4505 0 : shift -= 4;
4506 0 : u = 0xF & (hex >> shift);
4507 0 : c = (char)(u + (u < 10 ? '0' : 'A' - 10));
4508 0 : break;
4509 : }
4510 0 : if (buffer) {
4511 0 : MOZ_ASSERT(n <= bufferSize);
4512 0 : if (n != bufferSize) {
4513 0 : buffer[n] = c;
4514 : } else {
4515 0 : buffer[n] = '\0';
4516 0 : buffer = nullptr;
4517 : }
4518 0 : } else if (out) {
4519 0 : if (!out->put(&c, 1))
4520 0 : return size_t(-1);
4521 : }
4522 0 : n++;
4523 : }
4524 : stop:
4525 0 : if (buffer)
4526 0 : buffer[n] = '\0';
4527 0 : return n;
4528 : }
4529 :
4530 : template size_t
4531 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const Latin1Char* chars,
4532 : size_t length, uint32_t quote);
4533 :
4534 : template size_t
4535 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char* chars,
4536 : size_t length, uint32_t quote);
4537 :
4538 : template size_t
4539 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char16_t* chars,
4540 : size_t length, uint32_t quote);
4541 :
4542 : template size_t
4543 : js::PutEscapedString(char* buffer, size_t bufferSize, const Latin1Char* chars, size_t length,
4544 : uint32_t quote);
4545 :
4546 : template size_t
4547 : js::PutEscapedString(char* buffer, size_t bufferSize, const char16_t* chars, size_t length,
4548 : uint32_t quote);
4549 :
4550 : static bool
4551 8 : FlatStringMatchHelper(JSContext* cx, HandleString str, HandleString pattern, bool* isFlat, int32_t* match)
4552 : {
4553 16 : RootedLinearString linearPattern(cx, pattern->ensureLinear(cx));
4554 8 : if (!linearPattern)
4555 0 : return false;
4556 :
4557 : static const size_t MAX_FLAT_PAT_LEN = 256;
4558 8 : if (linearPattern->length() > MAX_FLAT_PAT_LEN || StringHasRegExpMetaChars(linearPattern)) {
4559 0 : *isFlat = false;
4560 0 : return true;
4561 : }
4562 :
4563 8 : *isFlat = true;
4564 8 : if (str->isRope()) {
4565 0 : if (!RopeMatch(cx, &str->asRope(), linearPattern, match))
4566 0 : return false;
4567 : } else {
4568 8 : *match = StringMatch(&str->asLinear(), linearPattern);
4569 : }
4570 :
4571 8 : return true;
4572 : }
4573 :
4574 : static bool
4575 8 : BuildFlatMatchArray(JSContext* cx, HandleString str, HandleString pattern, int32_t match,
4576 : MutableHandleValue rval)
4577 : {
4578 8 : if (match < 0) {
4579 7 : rval.setNull();
4580 7 : return true;
4581 : }
4582 :
4583 : /* Get the templateObject that defines the shape and type of the output object */
4584 1 : JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
4585 1 : if (!templateObject)
4586 0 : return false;
4587 :
4588 2 : RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, 1, templateObject));
4589 1 : if (!arr)
4590 0 : return false;
4591 :
4592 : /* Store a Value for each pair. */
4593 1 : arr->setDenseInitializedLength(1);
4594 1 : arr->initDenseElement(0, StringValue(pattern));
4595 :
4596 : /* Set the |index| property. (TemplateObject positions it in slot 0) */
4597 1 : arr->setSlot(0, Int32Value(match));
4598 :
4599 : /* Set the |input| property. (TemplateObject positions it in slot 1) */
4600 1 : arr->setSlot(1, StringValue(str));
4601 :
4602 : #ifdef DEBUG
4603 2 : RootedValue test(cx);
4604 2 : RootedId id(cx, NameToId(cx->names().index));
4605 1 : if (!NativeGetProperty(cx, arr, id, &test))
4606 0 : return false;
4607 1 : MOZ_ASSERT(test == arr->getSlot(0));
4608 1 : id = NameToId(cx->names().input);
4609 1 : if (!NativeGetProperty(cx, arr, id, &test))
4610 0 : return false;
4611 1 : MOZ_ASSERT(test == arr->getSlot(1));
4612 : #endif
4613 :
4614 1 : rval.setObject(*arr);
4615 1 : return true;
4616 : }
4617 :
4618 : #ifdef DEBUG
4619 : static bool
4620 8 : CallIsStringOptimizable(JSContext* cx, const char* name, bool* result)
4621 : {
4622 8 : JSAtom* atom = Atomize(cx, name, strlen(name));
4623 8 : if (!atom)
4624 0 : return false;
4625 16 : RootedPropertyName propName(cx, atom->asPropertyName());
4626 :
4627 16 : RootedValue funcVal(cx);
4628 8 : if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), propName, propName, 0, &funcVal))
4629 0 : return false;
4630 :
4631 16 : FixedInvokeArgs<0> args(cx);
4632 :
4633 16 : RootedValue rval(cx);
4634 8 : if (!Call(cx, funcVal, UndefinedHandleValue, args, &rval))
4635 0 : return false;
4636 :
4637 8 : *result = rval.toBoolean();
4638 8 : return true;
4639 : }
4640 : #endif
4641 :
4642 : bool
4643 8 : js::FlatStringMatch(JSContext* cx, unsigned argc, Value* vp)
4644 : {
4645 8 : CallArgs args = CallArgsFromVp(argc, vp);
4646 8 : MOZ_ASSERT(args.length() == 2);
4647 8 : MOZ_ASSERT(args[0].isString());
4648 8 : MOZ_ASSERT(args[1].isString());
4649 : #ifdef DEBUG
4650 8 : bool isOptimizable = false;
4651 8 : if (!CallIsStringOptimizable(cx, "IsStringMatchOptimizable", &isOptimizable))
4652 0 : return false;
4653 8 : MOZ_ASSERT(isOptimizable);
4654 : #endif
4655 :
4656 16 : RootedString str(cx,args[0].toString());
4657 16 : RootedString pattern(cx, args[1].toString());
4658 :
4659 8 : bool isFlat = false;
4660 8 : int32_t match = 0;
4661 8 : if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
4662 0 : return false;
4663 :
4664 8 : if (!isFlat) {
4665 0 : args.rval().setUndefined();
4666 0 : return true;
4667 : }
4668 :
4669 8 : return BuildFlatMatchArray(cx, str, pattern, match, args.rval());
4670 : }
4671 :
4672 : bool
4673 0 : js::FlatStringSearch(JSContext* cx, unsigned argc, Value* vp)
4674 : {
4675 0 : CallArgs args = CallArgsFromVp(argc, vp);
4676 0 : MOZ_ASSERT(args.length() == 2);
4677 0 : MOZ_ASSERT(args[0].isString());
4678 0 : MOZ_ASSERT(args[1].isString());
4679 : #ifdef DEBUG
4680 0 : bool isOptimizable = false;
4681 0 : if (!CallIsStringOptimizable(cx, "IsStringSearchOptimizable", &isOptimizable))
4682 0 : return false;
4683 0 : MOZ_ASSERT(isOptimizable);
4684 : #endif
4685 :
4686 0 : RootedString str(cx,args[0].toString());
4687 0 : RootedString pattern(cx, args[1].toString());
4688 :
4689 0 : bool isFlat = false;
4690 0 : int32_t match = 0;
4691 0 : if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
4692 0 : return false;
4693 :
4694 0 : if (!isFlat) {
4695 0 : args.rval().setInt32(-2);
4696 0 : return true;
4697 : }
4698 :
4699 0 : args.rval().setInt32(match);
4700 0 : return true;
4701 : }
|