1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla Communicator client code, released
18 : * March 31, 1998.
19 : *
20 : * The Initial Developer of the Original Code is
21 : * Netscape Communications Corporation.
22 : * Portions created by the Initial Developer are Copyright (C) 1998
23 : * the Initial Developer. All Rights Reserved.
24 : *
25 : * Contributor(s):
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : /*
42 : * JS string type implementation.
43 : *
44 : * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these
45 : * native methods store strings (possibly newborn) converted from their 'this'
46 : * parameter and arguments on the stack: 'this' conversions at argv[-1], arg
47 : * conversions at their index (argv[0], argv[1]). This is a legitimate method
48 : * of rooting things that might lose their newborn root due to subsequent GC
49 : * allocations in the same native method.
50 : */
51 :
52 : #include "mozilla/Attributes.h"
53 :
54 : #include <stdlib.h>
55 : #include <string.h>
56 : #include "jstypes.h"
57 : #include "jsutil.h"
58 : #include "jshash.h"
59 : #include "jsprf.h"
60 : #include "jsapi.h"
61 : #include "jsarray.h"
62 : #include "jsatom.h"
63 : #include "jsbool.h"
64 : #include "jscntxt.h"
65 : #include "jsgc.h"
66 : #include "jsinterp.h"
67 : #include "jslock.h"
68 : #include "jsnum.h"
69 : #include "jsobj.h"
70 : #include "jsopcode.h"
71 : #include "jsprobes.h"
72 : #include "jsscope.h"
73 : #include "jsstr.h"
74 : #include "jsversion.h"
75 :
76 : #include "builtin/RegExp.h"
77 : #include "vm/GlobalObject.h"
78 : #include "vm/RegExpObject.h"
79 :
80 : #include "jsinferinlines.h"
81 : #include "jsobjinlines.h"
82 : #include "jsstrinlines.h"
83 : #include "jsautooplen.h" // generated headers last
84 :
85 : #include "vm/MethodGuard-inl.h"
86 : #include "vm/RegExpObject-inl.h"
87 : #include "vm/RegExpStatics-inl.h"
88 : #include "vm/StringObject-inl.h"
89 : #include "vm/String-inl.h"
90 : #include "vm/StringBuffer-inl.h"
91 :
92 : using namespace js;
93 : using namespace js::gc;
94 : using namespace js::types;
95 : using namespace js::unicode;
96 :
97 : static JSLinearString *
98 1224278 : ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno)
99 : {
100 1224278 : if (argno >= args.length())
101 27 : return cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
102 :
103 1224251 : Value &arg = args[argno];
104 1224251 : JSString *str = ToString(cx, arg);
105 1224251 : if (!str)
106 72 : return NULL;
107 :
108 1224179 : arg = StringValue(str);
109 1224179 : return str->ensureLinear(cx);
110 : }
111 :
112 : /*
113 : * Forward declarations for URI encode/decode and helper routines
114 : */
115 : static JSBool
116 : str_decodeURI(JSContext *cx, unsigned argc, Value *vp);
117 :
118 : static JSBool
119 : str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
120 :
121 : static JSBool
122 : str_encodeURI(JSContext *cx, unsigned argc, Value *vp);
123 :
124 : static JSBool
125 : str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
126 :
127 : static const uint32_t INVALID_UTF8 = UINT32_MAX;
128 :
129 : static uint32_t
130 : Utf8ToOneUcs4Char(const uint8_t *utf8Buffer, int utf8Length);
131 :
132 : /*
133 : * Global string methods
134 : */
135 :
136 :
137 : /* ES5 B.2.1 */
138 : static JSBool
139 314 : str_escape(JSContext *cx, unsigned argc, Value *vp)
140 : {
141 314 : CallArgs args = CallArgsFromVp(argc, vp);
142 :
143 : const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
144 314 : '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
145 :
146 314 : JSLinearString *str = ArgToRootedString(cx, args, 0);
147 314 : if (!str)
148 0 : return false;
149 :
150 314 : size_t length = str->length();
151 314 : const jschar *chars = str->chars();
152 :
153 : static const uint8_t shouldPassThrough[256] = {
154 : 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
155 : 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
156 : 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1, /* !"#$%&'()*+,-./ */
157 : 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */
158 : 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */
159 : 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */
160 : 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */
161 : 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* pqrstuvwxyz{\}~ DEL */
162 : };
163 :
164 : /* In step 7, exactly 69 characters should pass through unencoded. */
165 : #ifdef DEBUG
166 314 : size_t count = 0;
167 80698 : for (size_t i = 0; i < sizeof(shouldPassThrough); i++) {
168 80384 : if (shouldPassThrough[i]) {
169 21666 : count++;
170 : }
171 : }
172 314 : JS_ASSERT(count == 69);
173 : #endif
174 :
175 :
176 : /* Take a first pass and see how big the result string will need to be. */
177 314 : size_t newlength = length;
178 13192 : for (size_t i = 0; i < length; i++) {
179 12878 : jschar ch = chars[i];
180 12878 : if (ch < 128 && shouldPassThrough[ch])
181 9126 : continue;
182 :
183 : /* The character will be encoded as %XX or %uXXXX. */
184 3752 : newlength += (ch < 256) ? 2 : 5;
185 :
186 : /*
187 : * This overflow test works because newlength is incremented by at
188 : * most 5 on each iteration.
189 : */
190 3752 : if (newlength < length) {
191 0 : js_ReportAllocationOverflow(cx);
192 0 : return false;
193 : }
194 : }
195 :
196 314 : if (newlength >= ~(size_t)0 / sizeof(jschar)) {
197 0 : js_ReportAllocationOverflow(cx);
198 0 : return false;
199 : }
200 :
201 314 : jschar *newchars = (jschar *) cx->malloc_((newlength + 1) * sizeof(jschar));
202 314 : if (!newchars)
203 0 : return false;
204 : size_t i, ni;
205 13192 : for (i = 0, ni = 0; i < length; i++) {
206 12878 : jschar ch = chars[i];
207 12878 : if (ch < 128 && shouldPassThrough[ch]) {
208 9126 : newchars[ni++] = ch;
209 3752 : } else if (ch < 256) {
210 564 : newchars[ni++] = '%';
211 564 : newchars[ni++] = digits[ch >> 4];
212 564 : newchars[ni++] = digits[ch & 0xF];
213 : } else {
214 3188 : newchars[ni++] = '%';
215 3188 : newchars[ni++] = 'u';
216 3188 : newchars[ni++] = digits[ch >> 12];
217 3188 : newchars[ni++] = digits[(ch & 0xF00) >> 8];
218 3188 : newchars[ni++] = digits[(ch & 0xF0) >> 4];
219 3188 : newchars[ni++] = digits[ch & 0xF];
220 : }
221 : }
222 314 : JS_ASSERT(ni == newlength);
223 314 : newchars[newlength] = 0;
224 :
225 314 : JSString *retstr = js_NewString(cx, newchars, newlength);
226 314 : if (!retstr) {
227 0 : cx->free_(newchars);
228 0 : return false;
229 : }
230 :
231 314 : args.rval() = StringValue(retstr);
232 314 : return true;
233 : }
234 :
235 : static inline bool
236 0 : Unhex4(const jschar *chars, jschar *result)
237 : {
238 0 : jschar a = chars[0],
239 0 : b = chars[1],
240 0 : c = chars[2],
241 0 : d = chars[3];
242 :
243 0 : if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d)))
244 0 : return false;
245 :
246 0 : *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d);
247 0 : return true;
248 : }
249 :
250 : static inline bool
251 0 : Unhex2(const jschar *chars, jschar *result)
252 : {
253 0 : jschar a = chars[0],
254 0 : b = chars[1];
255 :
256 0 : if (!(JS7_ISHEX(a) && JS7_ISHEX(b)))
257 0 : return false;
258 :
259 0 : *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b);
260 0 : return true;
261 : }
262 :
263 : /* ES5 B.2.2 */
264 : static JSBool
265 0 : str_unescape(JSContext *cx, unsigned argc, Value *vp)
266 : {
267 0 : CallArgs args = CallArgsFromVp(argc, vp);
268 :
269 : /* Step 1. */
270 0 : JSLinearString *str = ArgToRootedString(cx, args, 0);
271 0 : if (!str)
272 0 : return false;
273 :
274 : /* Step 2. */
275 0 : size_t length = str->length();
276 0 : const jschar *chars = str->chars();
277 :
278 : /* Step 3. */
279 0 : StringBuffer sb(cx);
280 :
281 : /*
282 : * Note that the spec algorithm has been optimized to avoid building
283 : * a string in the case where no escapes are present.
284 : */
285 :
286 : /* Step 4. */
287 0 : size_t k = 0;
288 0 : bool building = false;
289 :
290 0 : while (true) {
291 : /* Step 5. */
292 0 : if (k == length) {
293 : JSLinearString *result;
294 0 : if (building) {
295 0 : result = sb.finishString();
296 0 : if (!result)
297 0 : return false;
298 : } else {
299 0 : result = str;
300 : }
301 :
302 0 : args.rval() = StringValue(result);
303 0 : return true;
304 : }
305 :
306 : /* Step 6. */
307 0 : jschar c = chars[k];
308 :
309 : /* Step 7. */
310 0 : if (c != '%')
311 0 : goto step_18;
312 :
313 : /* Step 8. */
314 0 : if (k > length - 6)
315 0 : goto step_14;
316 :
317 : /* Step 9. */
318 0 : if (chars[k + 1] != 'u')
319 0 : goto step_14;
320 :
321 : #define ENSURE_BUILDING \
322 : JS_BEGIN_MACRO \
323 : if (!building) { \
324 : building = true; \
325 : if (!sb.reserve(length)) \
326 : return false; \
327 : sb.infallibleAppend(chars, chars + k); \
328 : } \
329 : JS_END_MACRO
330 :
331 : /* Step 10-13. */
332 0 : if (Unhex4(&chars[k + 2], &c)) {
333 0 : ENSURE_BUILDING;
334 0 : k += 5;
335 0 : goto step_18;
336 : }
337 :
338 : step_14:
339 : /* Step 14. */
340 0 : if (k > length - 3)
341 0 : goto step_18;
342 :
343 : /* Step 15-17. */
344 0 : if (Unhex2(&chars[k + 1], &c)) {
345 0 : ENSURE_BUILDING;
346 0 : k += 2;
347 : }
348 :
349 : step_18:
350 0 : if (building)
351 0 : sb.infallibleAppend(c);
352 :
353 : /* Step 19. */
354 0 : k += 1;
355 : }
356 : #undef ENSURE_BUILDING
357 : }
358 :
359 : #if JS_HAS_UNEVAL
360 : static JSBool
361 4646 : str_uneval(JSContext *cx, unsigned argc, Value *vp)
362 : {
363 4646 : CallArgs args = CallArgsFromVp(argc, vp);
364 4646 : JSString *str = js_ValueToSource(cx, args.length() != 0 ? args[0] : UndefinedValue());
365 4646 : if (!str)
366 0 : return false;
367 :
368 4646 : args.rval() = StringValue(str);
369 4646 : return true;
370 : }
371 : #endif
372 :
373 : const char js_escape_str[] = "escape";
374 : const char js_unescape_str[] = "unescape";
375 : #if JS_HAS_UNEVAL
376 : const char js_uneval_str[] = "uneval";
377 : #endif
378 : const char js_decodeURI_str[] = "decodeURI";
379 : const char js_encodeURI_str[] = "encodeURI";
380 : const char js_decodeURIComponent_str[] = "decodeURIComponent";
381 : const char js_encodeURIComponent_str[] = "encodeURIComponent";
382 :
383 : static JSFunctionSpec string_functions[] = {
384 : JS_FN(js_escape_str, str_escape, 1,0),
385 : JS_FN(js_unescape_str, str_unescape, 1,0),
386 : #if JS_HAS_UNEVAL
387 : JS_FN(js_uneval_str, str_uneval, 1,0),
388 : #endif
389 : JS_FN(js_decodeURI_str, str_decodeURI, 1,0),
390 : JS_FN(js_encodeURI_str, str_encodeURI, 1,0),
391 : JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0),
392 : JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0),
393 :
394 : JS_FS_END
395 : };
396 :
397 : jschar js_empty_ucstr[] = {0};
398 : JSSubString js_EmptySubString = {0, js_empty_ucstr};
399 :
400 : static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
401 :
402 : static JSBool
403 46206 : str_enumerate(JSContext *cx, JSObject *obj)
404 : {
405 46206 : JSString *str = obj->asString().unbox();
406 503749 : for (size_t i = 0, length = str->length(); i < length; i++) {
407 457543 : JSString *str1 = js_NewDependentString(cx, str, i, 1);
408 457543 : if (!str1)
409 0 : return false;
410 457543 : if (!obj->defineElement(cx, i, StringValue(str1),
411 : JS_PropertyStub, JS_StrictPropertyStub,
412 457543 : STRING_ELEMENT_ATTRS)) {
413 0 : return false;
414 : }
415 : }
416 :
417 46206 : return true;
418 : }
419 :
420 : static JSBool
421 254783 : str_resolve(JSContext *cx, JSObject *obj, jsid id, unsigned flags,
422 : JSObject **objp)
423 : {
424 254783 : if (!JSID_IS_INT(id))
425 236237 : return JS_TRUE;
426 :
427 18546 : JSString *str = obj->asString().unbox();
428 :
429 18546 : int32_t slot = JSID_TO_INT(id);
430 18546 : if ((size_t)slot < str->length()) {
431 12494 : JSString *str1 = cx->runtime->staticStrings.getUnitStringForElement(cx, str, size_t(slot));
432 12494 : if (!str1)
433 0 : return JS_FALSE;
434 12494 : if (!obj->defineElement(cx, uint32_t(slot), StringValue(str1), NULL, NULL,
435 12494 : STRING_ELEMENT_ATTRS)) {
436 0 : return JS_FALSE;
437 : }
438 12494 : *objp = obj;
439 : }
440 18546 : return JS_TRUE;
441 : }
442 :
443 : Class js::StringClass = {
444 : js_String_str,
445 : JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
446 : JSCLASS_NEW_RESOLVE | JSCLASS_HAS_CACHED_PROTO(JSProto_String),
447 : JS_PropertyStub, /* addProperty */
448 : JS_PropertyStub, /* delProperty */
449 : JS_PropertyStub, /* getProperty */
450 : JS_StrictPropertyStub, /* setProperty */
451 : str_enumerate,
452 : (JSResolveOp)str_resolve,
453 : JS_ConvertStub
454 : };
455 :
456 : /*
457 : * Returns a JSString * for the |this| value associated with 'call', or throws
458 : * a TypeError if |this| is null or undefined. This algorithm is the same as
459 : * calling CheckObjectCoercible(this), then returning ToString(this), as all
460 : * String.prototype.* methods do (other than toString and valueOf).
461 : */
462 : static JS_ALWAYS_INLINE JSString *
463 2277123 : ThisToStringForStringProto(JSContext *cx, CallReceiver call)
464 : {
465 2277123 : JS_CHECK_RECURSION(cx, return NULL);
466 :
467 2277123 : if (call.thisv().isString())
468 2273157 : return call.thisv().toString();
469 :
470 3966 : if (call.thisv().isObject()) {
471 3966 : JSObject *obj = &call.thisv().toObject();
472 6744 : if (obj->isString() &&
473 : ClassMethodIsNative(cx, obj,
474 : &StringClass,
475 2778 : ATOM_TO_JSID(cx->runtime->atomState.toStringAtom),
476 2778 : js_str_toString))
477 : {
478 2778 : JSString *str = obj->asString().unbox();
479 2778 : call.thisv().setString(str);
480 2778 : return str;
481 : }
482 0 : } else if (call.thisv().isNullOrUndefined()) {
483 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO,
484 0 : call.thisv().isNull() ? "null" : "undefined", "object");
485 0 : return NULL;
486 : }
487 :
488 1188 : JSString *str = ToStringSlow(cx, call.thisv());
489 1188 : if (!str)
490 0 : return NULL;
491 :
492 1188 : call.thisv().setString(str);
493 1188 : return str;
494 : }
495 :
496 : #if JS_HAS_TOSOURCE
497 :
498 : /*
499 : * String.prototype.quote is generic (as are most string methods), unlike
500 : * toSource, toString, and valueOf.
501 : */
502 : static JSBool
503 0 : str_quote(JSContext *cx, unsigned argc, Value *vp)
504 : {
505 0 : CallArgs args = CallArgsFromVp(argc, vp);
506 0 : JSString *str = ThisToStringForStringProto(cx, args);
507 0 : if (!str)
508 0 : return false;
509 0 : str = js_QuoteString(cx, str, '"');
510 0 : if (!str)
511 0 : return false;
512 0 : args.rval() = StringValue(str);
513 0 : return true;
514 : }
515 :
516 : static JSBool
517 128 : str_toSource(JSContext *cx, unsigned argc, Value *vp)
518 : {
519 128 : CallArgs args = CallArgsFromVp(argc, vp);
520 :
521 : JSString *str;
522 : bool ok;
523 128 : if (!BoxedPrimitiveMethodGuard(cx, args, str_toSource, &str, &ok))
524 63 : return ok;
525 :
526 65 : str = js_QuoteString(cx, str, '"');
527 65 : if (!str)
528 0 : return false;
529 :
530 130 : StringBuffer sb(cx);
531 65 : if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
532 0 : return false;
533 :
534 65 : str = sb.finishString();
535 65 : if (!str)
536 0 : return false;
537 65 : args.rval() = StringValue(str);
538 65 : return true;
539 : }
540 :
541 : #endif /* JS_HAS_TOSOURCE */
542 :
543 : JSBool
544 5296 : js_str_toString(JSContext *cx, unsigned argc, Value *vp)
545 : {
546 5296 : CallArgs args = CallArgsFromVp(argc, vp);
547 :
548 : JSString *str;
549 : bool ok;
550 5296 : if (!BoxedPrimitiveMethodGuard(cx, args, js_str_toString, &str, &ok))
551 63 : return ok;
552 :
553 5233 : args.rval() = StringValue(str);
554 5233 : return true;
555 : }
556 :
557 : /*
558 : * Java-like string native methods.
559 : */
560 :
561 : JS_ALWAYS_INLINE bool
562 690961 : ValueToIntegerRange(JSContext *cx, const Value &v, int32_t *out)
563 : {
564 690961 : if (v.isInt32()) {
565 690951 : *out = v.toInt32();
566 : } else {
567 : double d;
568 10 : if (!ToInteger(cx, v, &d))
569 0 : return false;
570 10 : if (d > INT32_MAX)
571 0 : *out = INT32_MAX;
572 10 : else if (d < INT32_MIN)
573 0 : *out = INT32_MIN;
574 : else
575 10 : *out = int32_t(d);
576 : }
577 :
578 690961 : return true;
579 : }
580 :
581 : static JSBool
582 355110 : str_substring(JSContext *cx, unsigned argc, Value *vp)
583 : {
584 355110 : CallArgs args = CallArgsFromVp(argc, vp);
585 :
586 355110 : JSString *str = ThisToStringForStringProto(cx, args);
587 355110 : if (!str)
588 0 : return false;
589 :
590 : int32_t length, begin, end;
591 355110 : if (args.length() > 0) {
592 355110 : end = length = int32_t(str->length());
593 :
594 355110 : if (!ValueToIntegerRange(cx, args[0], &begin))
595 0 : return false;
596 :
597 355110 : if (begin < 0)
598 0 : begin = 0;
599 355110 : else if (begin > length)
600 45 : begin = length;
601 :
602 355110 : if (args.hasDefined(1)) {
603 274174 : if (!ValueToIntegerRange(cx, args[1], &end))
604 0 : return false;
605 :
606 274174 : if (end > length) {
607 335 : end = length;
608 : } else {
609 273839 : if (end < 0)
610 511 : end = 0;
611 273839 : if (end < begin) {
612 0 : int32_t tmp = begin;
613 0 : begin = end;
614 0 : end = tmp;
615 : }
616 : }
617 : }
618 :
619 355110 : str = js_NewDependentString(cx, str, size_t(begin), size_t(end - begin));
620 355110 : if (!str)
621 0 : return false;
622 : }
623 :
624 355110 : args.rval() = StringValue(str);
625 355110 : return true;
626 : }
627 :
628 : JSString* JS_FASTCALL
629 118340 : js_toLowerCase(JSContext *cx, JSString *str)
630 : {
631 118340 : size_t n = str->length();
632 118340 : const jschar *s = str->getChars(cx);
633 118340 : if (!s)
634 0 : return NULL;
635 :
636 118340 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
637 118340 : if (!news)
638 0 : return NULL;
639 1268669 : for (size_t i = 0; i < n; i++)
640 1150329 : news[i] = unicode::ToLowerCase(s[i]);
641 118340 : news[n] = 0;
642 118340 : str = js_NewString(cx, news, n);
643 118340 : if (!str) {
644 0 : cx->free_(news);
645 0 : return NULL;
646 : }
647 118340 : return str;
648 : }
649 :
650 : static inline bool
651 118340 : ToLowerCaseHelper(JSContext *cx, CallReceiver call)
652 : {
653 118340 : JSString *str = ThisToStringForStringProto(cx, call);
654 118340 : if (!str)
655 0 : return false;
656 :
657 118340 : str = js_toLowerCase(cx, str);
658 118340 : if (!str)
659 0 : return false;
660 :
661 118340 : call.rval() = StringValue(str);
662 118340 : return true;
663 : }
664 :
665 : static JSBool
666 118340 : str_toLowerCase(JSContext *cx, unsigned argc, Value *vp)
667 : {
668 118340 : return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp));
669 : }
670 :
671 : static JSBool
672 0 : str_toLocaleLowerCase(JSContext *cx, unsigned argc, Value *vp)
673 : {
674 0 : CallArgs args = CallArgsFromVp(argc, vp);
675 :
676 : /*
677 : * Forcefully ignore the first (or any) argument and return toLowerCase(),
678 : * ECMA has reserved that argument, presumably for defining the locale.
679 : */
680 0 : if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) {
681 0 : JSString *str = ThisToStringForStringProto(cx, args);
682 0 : if (!str)
683 0 : return false;
684 :
685 : Value result;
686 0 : if (!cx->localeCallbacks->localeToLowerCase(cx, str, &result))
687 0 : return false;
688 :
689 0 : args.rval() = result;
690 0 : return true;
691 : }
692 :
693 0 : return ToLowerCaseHelper(cx, args);
694 : }
695 :
696 : JSString* JS_FASTCALL
697 22738 : js_toUpperCase(JSContext *cx, JSString *str)
698 : {
699 22738 : size_t n = str->length();
700 22738 : const jschar *s = str->getChars(cx);
701 22738 : if (!s)
702 0 : return NULL;
703 22738 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
704 22738 : if (!news)
705 0 : return NULL;
706 110658 : for (size_t i = 0; i < n; i++)
707 87920 : news[i] = unicode::ToUpperCase(s[i]);
708 22738 : news[n] = 0;
709 22738 : str = js_NewString(cx, news, n);
710 22738 : if (!str) {
711 0 : cx->free_(news);
712 0 : return NULL;
713 : }
714 22738 : return str;
715 : }
716 :
717 : static JSBool
718 22738 : ToUpperCaseHelper(JSContext *cx, CallReceiver call)
719 : {
720 22738 : JSString *str = ThisToStringForStringProto(cx, call);
721 22738 : if (!str)
722 0 : return false;
723 :
724 22738 : str = js_toUpperCase(cx, str);
725 22738 : if (!str)
726 0 : return false;
727 :
728 22738 : call.rval() = StringValue(str);
729 22738 : return true;
730 : }
731 :
732 : static JSBool
733 22738 : str_toUpperCase(JSContext *cx, unsigned argc, Value *vp)
734 : {
735 22738 : return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp));
736 : }
737 :
738 : static JSBool
739 0 : str_toLocaleUpperCase(JSContext *cx, unsigned argc, Value *vp)
740 : {
741 0 : CallArgs args = CallArgsFromVp(argc, vp);
742 :
743 : /*
744 : * Forcefully ignore the first (or any) argument and return toUpperCase(),
745 : * ECMA has reserved that argument, presumably for defining the locale.
746 : */
747 0 : if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) {
748 0 : JSString *str = ThisToStringForStringProto(cx, args);
749 0 : if (!str)
750 0 : return false;
751 :
752 : Value result;
753 0 : if (!cx->localeCallbacks->localeToUpperCase(cx, str, &result))
754 0 : return false;
755 :
756 0 : args.rval() = result;
757 0 : return true;
758 : }
759 :
760 0 : return ToUpperCaseHelper(cx, args);
761 : }
762 :
763 : static JSBool
764 148 : str_localeCompare(JSContext *cx, unsigned argc, Value *vp)
765 : {
766 148 : CallArgs args = CallArgsFromVp(argc, vp);
767 148 : JSString *str = ThisToStringForStringProto(cx, args);
768 148 : if (!str)
769 0 : return false;
770 :
771 148 : if (args.length() == 0) {
772 9 : args.rval() = Int32Value(0);
773 : } else {
774 139 : JSString *thatStr = ToString(cx, args[0]);
775 139 : if (!thatStr)
776 0 : return false;
777 :
778 139 : if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) {
779 85 : args[0].setString(thatStr);
780 :
781 : Value result;
782 85 : if (!cx->localeCallbacks->localeCompare(cx, str, thatStr, &result))
783 0 : return true;
784 :
785 85 : args.rval() = result;
786 85 : return true;
787 : }
788 :
789 : int32_t result;
790 54 : if (!CompareStrings(cx, str, thatStr, &result))
791 0 : return false;
792 :
793 54 : args.rval() = Int32Value(result);
794 : }
795 63 : return true;
796 : }
797 :
798 : JSBool
799 61236 : js_str_charAt(JSContext *cx, unsigned argc, Value *vp)
800 : {
801 61236 : CallArgs args = CallArgsFromVp(argc, vp);
802 :
803 : JSString *str;
804 : size_t i;
805 61236 : if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
806 61055 : str = args.thisv().toString();
807 61055 : i = size_t(args[0].toInt32());
808 61055 : if (i >= str->length())
809 3969 : goto out_of_range;
810 : } else {
811 181 : str = ThisToStringForStringProto(cx, args);
812 181 : if (!str)
813 0 : return false;
814 :
815 181 : double d = 0.0;
816 181 : if (args.length() > 0 && !ToInteger(cx, args[0], &d))
817 0 : return false;
818 :
819 181 : if (d < 0 || str->length() <= d)
820 117 : goto out_of_range;
821 64 : i = size_t(d);
822 : }
823 :
824 57150 : str = cx->runtime->staticStrings.getUnitStringForElement(cx, str, i);
825 57150 : if (!str)
826 0 : return false;
827 57150 : args.rval() = StringValue(str);
828 57150 : return true;
829 :
830 : out_of_range:
831 4086 : args.rval() = StringValue(cx->runtime->emptyString);
832 4086 : return true;
833 : }
834 :
835 : JSBool
836 4135224 : js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp)
837 : {
838 4135224 : CallArgs args = CallArgsFromVp(argc, vp);
839 :
840 : JSString *str;
841 : size_t i;
842 4135224 : if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
843 3666752 : str = args.thisv().toString();
844 3666752 : i = size_t(args[0].toInt32());
845 3666752 : if (i >= str->length())
846 135 : goto out_of_range;
847 : } else {
848 468472 : str = ThisToStringForStringProto(cx, args);
849 468472 : if (!str)
850 0 : return false;
851 :
852 468472 : double d = 0.0;
853 468472 : if (args.length() > 0 && !ToInteger(cx, args[0], &d))
854 0 : return false;
855 :
856 468472 : if (d < 0 || str->length() <= d)
857 306 : goto out_of_range;
858 468166 : i = size_t(d);
859 : }
860 :
861 : const jschar *chars;
862 4134783 : chars = str->getChars(cx);
863 4134783 : if (!chars)
864 0 : return false;
865 :
866 4134783 : args.rval() = Int32Value(chars[i]);
867 4134783 : return true;
868 :
869 : out_of_range:
870 441 : args.rval() = DoubleValue(js_NaN);
871 441 : return true;
872 : }
873 :
874 : /*
875 : * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen.
876 : * The patlen argument must be positive and no greater than sBMHPatLenMax.
877 : *
878 : * Return the index of pat in text, or -1 if not found.
879 : */
880 : static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */
881 : static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */
882 : static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */
883 :
884 : int
885 10 : js_BoyerMooreHorspool(const jschar *text, uint32_t textlen,
886 : const jschar *pat, uint32_t patlen)
887 : {
888 : uint8_t skip[sBMHCharSetSize];
889 :
890 10 : JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax);
891 2570 : for (uint32_t i = 0; i < sBMHCharSetSize; i++)
892 2560 : skip[i] = (uint8_t)patlen;
893 10 : uint32_t m = patlen - 1;
894 266 : for (uint32_t i = 0; i < m; i++) {
895 256 : jschar c = pat[i];
896 256 : if (c >= sBMHCharSetSize)
897 0 : return sBMHBadPattern;
898 256 : skip[c] = (uint8_t)(m - i);
899 : }
900 : jschar c;
901 12544 : for (uint32_t k = m;
902 : k < textlen;
903 12534 : k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) {
904 6547 : for (uint32_t i = k, j = m; ; i--, j--) {
905 6547 : if (text[i] != pat[j])
906 : break;
907 280 : if (j == 0)
908 6 : return static_cast<int>(i); /* safe: max string size */
909 : }
910 : }
911 4 : return -1;
912 : }
913 :
914 : struct MemCmp {
915 : typedef uint32_t Extent;
916 : static JS_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) {
917 : return (patlen - 1) * sizeof(jschar);
918 : }
919 : static JS_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
920 : return memcmp(p, t, extent) == 0;
921 : }
922 : };
923 :
924 : struct ManualCmp {
925 : typedef const jschar *Extent;
926 32016 : static JS_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) {
927 32016 : return pat + patlen;
928 : }
929 36021 : static JS_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
930 206606 : for (; p != extent; ++p, ++t) {
931 191132 : if (*p != *t)
932 20547 : return false;
933 : }
934 15474 : return true;
935 : }
936 : };
937 :
938 : template <class InnerMatch>
939 : static int
940 32016 : UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen)
941 : {
942 32016 : JS_ASSERT(patlen > 0 && textlen > 0);
943 32016 : const jschar *textend = text + textlen - (patlen - 1);
944 32016 : const jschar p0 = *pat;
945 32016 : const jschar *const patNext = pat + 1;
946 32016 : const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
947 : uint8_t fixup;
948 :
949 32016 : const jschar *t = text;
950 32016 : switch ((textend - t) & 7) {
951 1701 : case 0: if (*t++ == p0) { fixup = 8; goto match; }
952 2830 : case 7: if (*t++ == p0) { fixup = 7; goto match; }
953 3736 : case 6: if (*t++ == p0) { fixup = 6; goto match; }
954 7404 : case 5: if (*t++ == p0) { fixup = 5; goto match; }
955 5484 : case 4: if (*t++ == p0) { fixup = 4; goto match; }
956 10168 : case 3: if (*t++ == p0) { fixup = 3; goto match; }
957 15908 : case 2: if (*t++ == p0) { fixup = 2; goto match; }
958 16411 : case 1: if (*t++ == p0) { fixup = 1; goto match; }
959 : }
960 119103 : while (t != textend) {
961 90975 : if (t[0] == p0) { t += 1; fixup = 8; goto match; }
962 90868 : if (t[1] == p0) { t += 2; fixup = 7; goto match; }
963 87099 : if (t[2] == p0) { t += 3; fixup = 6; goto match; }
964 83413 : if (t[3] == p0) { t += 4; fixup = 5; goto match; }
965 80025 : if (t[4] == p0) { t += 5; fixup = 4; goto match; }
966 79898 : if (t[5] == p0) { t += 6; fixup = 3; goto match; }
967 79814 : if (t[6] == p0) { t += 7; fixup = 2; goto match; }
968 79543 : if (t[7] == p0) { t += 8; fixup = 1; goto match; }
969 79296 : t += 8;
970 79296 : continue;
971 65893 : do {
972 49258 : if (*t++ == p0) {
973 : match:
974 36021 : if (!InnerMatch::match(patNext, t, extent))
975 20547 : goto failed_match;
976 15474 : return t - text - 1;
977 : }
978 : failed_match:;
979 : } while (--fixup > 0);
980 : }
981 16542 : return -1;
982 : }
983 :
984 : static JS_ALWAYS_INLINE int
985 18371837 : StringMatch(const jschar *text, uint32_t textlen,
986 : const jschar *pat, uint32_t patlen)
987 : {
988 18371837 : if (patlen == 0)
989 201162 : return 0;
990 18170675 : if (textlen < patlen)
991 34808 : return -1;
992 :
993 : #if defined(__i386__) || defined(_M_IX86) || defined(__i386)
994 : /*
995 : * Given enough registers, the unrolled loop below is faster than the
996 : * following loop. 32-bit x86 does not have enough registers.
997 : */
998 18135867 : if (patlen == 1) {
999 18103841 : const jschar p0 = *pat;
1000 1145702993 : for (const jschar *c = text, *end = text + textlen; c != end; ++c) {
1001 1128202427 : if (*c == p0)
1002 603275 : return c - text;
1003 : }
1004 17500566 : return -1;
1005 : }
1006 : #endif
1007 :
1008 : /*
1009 : * If the text or pattern string is short, BMH will be more expensive than
1010 : * the basic linear scan due to initialization cost and a more complex loop
1011 : * body. While the correct threshold is input-dependent, we can make a few
1012 : * conservative observations:
1013 : * - When |textlen| is "big enough", the initialization time will be
1014 : * proportionally small, so the worst-case slowdown is minimized.
1015 : * - When |patlen| is "too small", even the best case for BMH will be
1016 : * slower than a simple scan for large |textlen| due to the more complex
1017 : * loop body of BMH.
1018 : * From this, the values for "big enough" and "too small" are determined
1019 : * empirically. See bug 526348.
1020 : */
1021 32026 : if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) {
1022 10 : int index = js_BoyerMooreHorspool(text, textlen, pat, patlen);
1023 10 : if (index != sBMHBadPattern)
1024 10 : return index;
1025 : }
1026 :
1027 : /*
1028 : * For big patterns with large potential overlap we want the SIMD-optimized
1029 : * speed of memcmp. For small patterns, a simple loop is faster.
1030 : *
1031 : * FIXME: Linux memcmp performance is sad and the manual loop is faster.
1032 : */
1033 : return
1034 : #if !defined(__linux__)
1035 : patlen > 128 ? UnrolledMatch<MemCmp>(text, textlen, pat, patlen)
1036 : :
1037 : #endif
1038 32016 : UnrolledMatch<ManualCmp>(text, textlen, pat, patlen);
1039 : }
1040 :
1041 : static const size_t sRopeMatchThresholdRatioLog2 = 5;
1042 :
1043 : /*
1044 : * RopeMatch takes the text to search, the patern to search for in the text.
1045 : * RopeMatch returns false on OOM and otherwise returns the match index through
1046 : * the 'match' outparam (-1 for not found).
1047 : */
1048 : static bool
1049 456558 : RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match)
1050 : {
1051 456558 : JS_ASSERT(textstr->isRope());
1052 :
1053 456558 : if (patlen == 0) {
1054 0 : *match = 0;
1055 0 : return true;
1056 : }
1057 456558 : if (textstr->length() < patlen) {
1058 0 : *match = -1;
1059 0 : return true;
1060 : }
1061 :
1062 : /*
1063 : * List of leaf nodes in the rope. If we run out of memory when trying to
1064 : * append to this list, we can still fall back to StringMatch, so use the
1065 : * system allocator so we don't report OOM in that case.
1066 : */
1067 913116 : Vector<JSLinearString *, 16, SystemAllocPolicy> strs;
1068 :
1069 : /*
1070 : * We don't want to do rope matching if there is a poor node-to-char ratio,
1071 : * since this means spending a lot of time in the match loop below. We also
1072 : * need to build the list of leaf nodes. Do both here: iterate over the
1073 : * nodes so long as there are not too many.
1074 : */
1075 : {
1076 456558 : size_t textstrlen = textstr->length();
1077 456558 : size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
1078 913116 : StringSegmentRange r(cx);
1079 456558 : if (!r.init(textstr))
1080 0 : return false;
1081 37205463 : while (!r.empty()) {
1082 36315546 : if (threshold-- == 0 || !strs.append(r.front())) {
1083 23199 : const jschar *chars = textstr->getChars(cx);
1084 23199 : if (!chars)
1085 0 : return false;
1086 23199 : *match = StringMatch(chars, textstrlen, pat, patlen);
1087 23199 : return true;
1088 : }
1089 36292347 : if (!r.popFront())
1090 0 : return false;
1091 : }
1092 : }
1093 :
1094 : /* Absolute offset from the beginning of the logical string textstr. */
1095 433359 : int pos = 0;
1096 :
1097 17877708 : for (JSLinearString **outerp = strs.begin(); outerp != strs.end(); ++outerp) {
1098 : /* Try to find a match within 'outer'. */
1099 17877708 : JSLinearString *outer = *outerp;
1100 17877708 : const jschar *chars = outer->chars();
1101 17877708 : size_t len = outer->length();
1102 17877708 : int matchResult = StringMatch(chars, len, pat, patlen);
1103 17877708 : if (matchResult != -1) {
1104 : /* Matched! */
1105 433359 : *match = pos + matchResult;
1106 433359 : return true;
1107 : }
1108 :
1109 : /* Try to find a match starting in 'outer' and running into other nodes. */
1110 17444349 : const jschar *const text = chars + (patlen > len ? 0 : len - patlen + 1);
1111 17444349 : const jschar *const textend = chars + len;
1112 17444349 : const jschar p0 = *pat;
1113 17444349 : const jschar *const p1 = pat + 1;
1114 17444349 : const jschar *const patend = pat + patlen;
1115 34888887 : for (const jschar *t = text; t != textend; ) {
1116 189 : if (*t++ != p0)
1117 171 : continue;
1118 18 : JSLinearString **innerp = outerp;
1119 18 : const jschar *ttend = textend;
1120 36 : for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) {
1121 90 : while (tt == ttend) {
1122 18 : if (++innerp == strs.end()) {
1123 0 : *match = -1;
1124 0 : return true;
1125 : }
1126 18 : JSLinearString *inner = *innerp;
1127 18 : tt = inner->chars();
1128 18 : ttend = tt + inner->length();
1129 : }
1130 36 : if (*pp != *tt)
1131 18 : goto break_continue;
1132 : }
1133 :
1134 : /* Matched! */
1135 0 : *match = pos + (t - chars) - 1; /* -1 because of *t++ above */
1136 0 : return true;
1137 :
1138 : break_continue:;
1139 : }
1140 :
1141 17444349 : pos += len;
1142 : }
1143 :
1144 0 : *match = -1;
1145 0 : return true;
1146 : }
1147 :
1148 : static JSBool
1149 92665 : str_indexOf(JSContext *cx, unsigned argc, Value *vp)
1150 : {
1151 92665 : CallArgs args = CallArgsFromVp(argc, vp);
1152 92665 : JSString *str = ThisToStringForStringProto(cx, args);
1153 92665 : if (!str)
1154 0 : return false;
1155 :
1156 92665 : JSLinearString *patstr = ArgToRootedString(cx, args, 0);
1157 92665 : if (!patstr)
1158 0 : return false;
1159 :
1160 92665 : uint32_t textlen = str->length();
1161 92665 : const jschar *text = str->getChars(cx);
1162 92665 : if (!text)
1163 0 : return false;
1164 :
1165 92665 : uint32_t patlen = patstr->length();
1166 92665 : const jschar *pat = patstr->chars();
1167 :
1168 : uint32_t start;
1169 92665 : if (args.length() > 1) {
1170 0 : if (args[1].isInt32()) {
1171 0 : int i = args[1].toInt32();
1172 0 : if (i <= 0) {
1173 0 : start = 0;
1174 0 : } else if (uint32_t(i) > textlen) {
1175 0 : start = textlen;
1176 0 : textlen = 0;
1177 : } else {
1178 0 : start = i;
1179 0 : text += start;
1180 0 : textlen -= start;
1181 : }
1182 : } else {
1183 : double d;
1184 0 : if (!ToInteger(cx, args[1], &d))
1185 0 : return false;
1186 0 : if (d <= 0) {
1187 0 : start = 0;
1188 0 : } else if (d > textlen) {
1189 0 : start = textlen;
1190 0 : textlen = 0;
1191 : } else {
1192 0 : start = (int)d;
1193 0 : text += start;
1194 0 : textlen -= start;
1195 : }
1196 : }
1197 : } else {
1198 92665 : start = 0;
1199 : }
1200 :
1201 92665 : int match = StringMatch(text, textlen, pat, patlen);
1202 92665 : args.rval() = Int32Value((match == -1) ? -1 : start + match);
1203 92665 : return true;
1204 : }
1205 :
1206 : static JSBool
1207 2411 : str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp)
1208 : {
1209 2411 : CallArgs args = CallArgsFromVp(argc, vp);
1210 2411 : JSString *textstr = ThisToStringForStringProto(cx, args);
1211 2411 : if (!textstr)
1212 0 : return false;
1213 :
1214 2411 : size_t textlen = textstr->length();
1215 2411 : const jschar *text = textstr->getChars(cx);
1216 2411 : if (!text)
1217 0 : return false;
1218 :
1219 2411 : JSLinearString *patstr = ArgToRootedString(cx, args, 0);
1220 2411 : if (!patstr)
1221 0 : return false;
1222 :
1223 2411 : size_t patlen = patstr->length();
1224 2411 : const jschar *pat = patstr->chars();
1225 :
1226 2411 : int i = textlen - patlen; // Start searching here
1227 2411 : if (i < 0) {
1228 0 : args.rval() = Int32Value(-1);
1229 0 : return true;
1230 : }
1231 :
1232 2411 : if (args.length() > 1) {
1233 0 : if (args[1].isInt32()) {
1234 0 : int j = args[1].toInt32();
1235 0 : if (j <= 0)
1236 0 : i = 0;
1237 0 : else if (j < i)
1238 0 : i = j;
1239 : } else {
1240 : double d;
1241 0 : if (!ToNumber(cx, args[1], &d))
1242 0 : return false;
1243 0 : if (!JSDOUBLE_IS_NaN(d)) {
1244 0 : d = js_DoubleToInteger(d);
1245 0 : if (d <= 0)
1246 0 : i = 0;
1247 0 : else if (d < i)
1248 0 : i = (int)d;
1249 : }
1250 : }
1251 : }
1252 :
1253 2411 : if (patlen == 0) {
1254 0 : args.rval() = Int32Value(i);
1255 0 : return true;
1256 : }
1257 :
1258 2411 : const jschar *t = text + i;
1259 2411 : const jschar *textend = text - 1;
1260 2411 : const jschar p0 = *pat;
1261 2411 : const jschar *patNext = pat + 1;
1262 2411 : const jschar *patEnd = pat + patlen;
1263 :
1264 22963 : for (; t != textend; --t) {
1265 22421 : if (*t == p0) {
1266 1869 : const jschar *t1 = t + 1;
1267 1869 : for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) {
1268 0 : if (*t1 != *p1)
1269 0 : goto break_continue;
1270 : }
1271 1869 : args.rval() = Int32Value(t - text);
1272 1869 : return true;
1273 : }
1274 : break_continue:;
1275 : }
1276 :
1277 542 : args.rval() = Int32Value(-1);
1278 542 : return true;
1279 : }
1280 :
1281 : static JSBool
1282 8418 : js_TrimString(JSContext *cx, Value *vp, JSBool trimLeft, JSBool trimRight)
1283 : {
1284 8418 : CallReceiver call = CallReceiverFromVp(vp);
1285 8418 : JSString *str = ThisToStringForStringProto(cx, call);
1286 8418 : if (!str)
1287 0 : return false;
1288 8418 : size_t length = str->length();
1289 8418 : const jschar *chars = str->getChars(cx);
1290 8418 : if (!chars)
1291 0 : return false;
1292 :
1293 8418 : size_t begin = 0;
1294 8418 : size_t end = length;
1295 :
1296 8418 : if (trimLeft) {
1297 21307 : while (begin < length && unicode::IsSpace(chars[begin]))
1298 4507 : ++begin;
1299 : }
1300 :
1301 8418 : if (trimRight) {
1302 18096 : while (end > begin && unicode::IsSpace(chars[end - 1]))
1303 1260 : --end;
1304 : }
1305 :
1306 8418 : str = js_NewDependentString(cx, str, begin, end - begin);
1307 8418 : if (!str)
1308 0 : return false;
1309 :
1310 8418 : call.rval() = StringValue(str);
1311 8418 : return true;
1312 : }
1313 :
1314 : static JSBool
1315 8400 : str_trim(JSContext *cx, unsigned argc, Value *vp)
1316 : {
1317 8400 : return js_TrimString(cx, vp, JS_TRUE, JS_TRUE);
1318 : }
1319 :
1320 : static JSBool
1321 0 : str_trimLeft(JSContext *cx, unsigned argc, Value *vp)
1322 : {
1323 0 : return js_TrimString(cx, vp, JS_TRUE, JS_FALSE);
1324 : }
1325 :
1326 : static JSBool
1327 18 : str_trimRight(JSContext *cx, unsigned argc, Value *vp)
1328 : {
1329 18 : return js_TrimString(cx, vp, JS_FALSE, JS_TRUE);
1330 : }
1331 :
1332 : /*
1333 : * Perl-inspired string functions.
1334 : */
1335 :
1336 : /* Result of a successfully performed flat match. */
1337 : class FlatMatch
1338 : {
1339 : JSAtom *patstr;
1340 : const jschar *pat;
1341 : size_t patlen;
1342 : int32_t match_;
1343 :
1344 : friend class StringRegExpGuard;
1345 :
1346 : public:
1347 1095161 : FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */
1348 3960 : JSLinearString *pattern() const { return patstr; }
1349 40158 : size_t patternLength() const { return patlen; }
1350 :
1351 : /*
1352 : * Note: The match is -1 when the match is performed successfully,
1353 : * but no match is found.
1354 : */
1355 542145 : int32_t match() const { return match_; }
1356 : };
1357 :
1358 : static inline bool
1359 825479 : IsRegExpMetaChar(jschar c)
1360 : {
1361 825479 : switch (c) {
1362 : /* Taken from the PatternCharacter production in 15.10.1. */
1363 : case '^': case '$': case '\\': case '.': case '*': case '+':
1364 : case '?': case '(': case ')': case '[': case ']': case '{':
1365 : case '}': case '|':
1366 21932 : return true;
1367 : default:
1368 803547 : return false;
1369 : }
1370 : }
1371 :
1372 : static inline bool
1373 510941 : HasRegExpMetaChars(const jschar *chars, size_t length)
1374 : {
1375 1310597 : for (size_t i = 0; i < length; ++i) {
1376 819503 : if (IsRegExpMetaChar(chars[i]))
1377 19847 : return true;
1378 : }
1379 491094 : return false;
1380 : }
1381 :
1382 : /*
1383 : * StringRegExpGuard factors logic out of String regexp operations.
1384 : *
1385 : * |optarg| indicates in which argument position RegExp flags will be found, if
1386 : * present. This is a Mozilla extension and not part of any ECMA spec.
1387 : */
1388 : class StringRegExpGuard
1389 1095161 : {
1390 : StringRegExpGuard(const StringRegExpGuard &) MOZ_DELETE;
1391 : void operator=(const StringRegExpGuard &) MOZ_DELETE;
1392 :
1393 : RegExpGuard re_;
1394 : FlatMatch fm;
1395 :
1396 : /*
1397 : * Upper bound on the number of characters we are willing to potentially
1398 : * waste on searching for RegExp meta-characters.
1399 : */
1400 : static const size_t MAX_FLAT_PAT_LEN = 256;
1401 :
1402 : static JSAtom *
1403 3872 : flattenPattern(JSContext *cx, JSAtom *patstr)
1404 : {
1405 7744 : StringBuffer sb(cx);
1406 3872 : if (!sb.reserve(patstr->length()))
1407 0 : return NULL;
1408 :
1409 : static const jschar ESCAPE_CHAR = '\\';
1410 3872 : const jschar *chars = patstr->chars();
1411 3872 : size_t len = patstr->length();
1412 9848 : for (const jschar *it = chars; it != chars + len; ++it) {
1413 5976 : if (IsRegExpMetaChar(*it)) {
1414 2085 : if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
1415 0 : return NULL;
1416 : } else {
1417 3891 : if (!sb.append(*it))
1418 0 : return NULL;
1419 : }
1420 : }
1421 3872 : return sb.finishAtom();
1422 : }
1423 :
1424 : public:
1425 1095161 : StringRegExpGuard() {}
1426 :
1427 : /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
1428 1095161 : bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
1429 : {
1430 1095161 : if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
1431 523561 : if (!RegExpToShared(cx, args[0].toObject(), &re_))
1432 0 : return false;
1433 : } else {
1434 571600 : if (convertVoid && !args.hasDefined(0)) {
1435 11556 : fm.patstr = cx->runtime->emptyString;
1436 11556 : return true;
1437 : }
1438 :
1439 560044 : JSString *arg = ArgToRootedString(cx, args, 0);
1440 560044 : if (!arg)
1441 54 : return false;
1442 :
1443 559990 : fm.patstr = js_AtomizeString(cx, arg);
1444 559990 : if (!fm.patstr)
1445 0 : return false;
1446 : }
1447 1083551 : return true;
1448 : }
1449 :
1450 : /*
1451 : * Attempt to match |patstr| to |textstr|. A flags argument, metachars in the
1452 : * pattern string, or a lengthy pattern string can thwart this process.
1453 : *
1454 : * |checkMetaChars| looks for regexp metachars in the pattern string.
1455 : *
1456 : * Return whether flat matching could be used.
1457 : *
1458 : * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending().
1459 : */
1460 : const FlatMatch *
1461 1095107 : tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc,
1462 : bool checkMetaChars = true)
1463 : {
1464 1095107 : if (re_.initialized())
1465 523561 : return NULL;
1466 :
1467 571546 : fm.pat = fm.patstr->chars();
1468 571546 : fm.patlen = fm.patstr->length();
1469 :
1470 571546 : if (optarg < argc)
1471 26912 : return NULL;
1472 :
1473 1055575 : if (checkMetaChars &&
1474 510941 : (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) {
1475 19847 : return NULL;
1476 : }
1477 :
1478 : /*
1479 : * textstr could be a rope, so we want to avoid flattening it for as
1480 : * long as possible.
1481 : */
1482 524787 : if (textstr->isRope()) {
1483 456558 : if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_))
1484 0 : return NULL;
1485 : } else {
1486 68229 : const jschar *text = textstr->asLinear().chars();
1487 68229 : size_t textlen = textstr->length();
1488 68229 : fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen);
1489 : }
1490 524787 : return &fm;
1491 : }
1492 :
1493 : /* If the pattern is not already a regular expression, make it so. */
1494 570320 : bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args)
1495 : {
1496 570320 : if (re_.initialized())
1497 523561 : return true;
1498 :
1499 : /* Build RegExp from pattern string. */
1500 : JSString *opt;
1501 46759 : if (optarg < args.length()) {
1502 26912 : opt = ToString(cx, args[optarg]);
1503 26912 : if (!opt)
1504 0 : return false;
1505 : } else {
1506 19847 : opt = NULL;
1507 : }
1508 :
1509 : JSAtom *patstr;
1510 46759 : if (flat) {
1511 3872 : patstr = flattenPattern(cx, fm.patstr);
1512 3872 : if (!patstr)
1513 0 : return false;
1514 : } else {
1515 42887 : patstr = fm.patstr;
1516 : }
1517 46759 : JS_ASSERT(patstr);
1518 :
1519 46759 : return cx->compartment->regExps.get(cx, patstr, opt, &re_);
1520 : }
1521 :
1522 610325 : RegExpShared ®Exp() { return *re_; }
1523 : };
1524 :
1525 : /* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
1526 : static JS_ALWAYS_INLINE bool
1527 2268495 : Matched(RegExpExecType type, const Value &v)
1528 : {
1529 2268495 : return (type == RegExpTest) ? v.isTrue() : !v.isNull();
1530 : }
1531 :
1532 : typedef bool (*DoMatchCallback)(JSContext *cx, RegExpStatics *res, size_t count, void *data);
1533 :
1534 : /*
1535 : * BitOR-ing these flags allows the DoMatch caller to control when how the
1536 : * RegExp engine is called and when callbacks are fired.
1537 : */
1538 : enum MatchControlFlags {
1539 : TEST_GLOBAL_BIT = 0x1, /* use RegExp.test for global regexps */
1540 : TEST_SINGLE_BIT = 0x2, /* use RegExp.test for non-global regexps */
1541 : CALLBACK_ON_SINGLE_BIT = 0x4, /* fire callback on non-global match */
1542 :
1543 : MATCH_ARGS = TEST_GLOBAL_BIT,
1544 : MATCHALL_ARGS = CALLBACK_ON_SINGLE_BIT,
1545 : REPLACE_ARGS = TEST_GLOBAL_BIT | TEST_SINGLE_BIT | CALLBACK_ON_SINGLE_BIT
1546 : };
1547 :
1548 : /* Factor out looping and matching logic. */
1549 : static bool
1550 550793 : DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
1551 : DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
1552 : {
1553 550793 : JSLinearString *linearStr = str->ensureLinear(cx);
1554 550793 : if (!linearStr)
1555 0 : return false;
1556 :
1557 550793 : const jschar *chars = linearStr->chars();
1558 550793 : size_t length = linearStr->length();
1559 :
1560 550793 : if (re.global()) {
1561 348900 : RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
1562 2135642 : for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
1563 2096743 : if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
1564 0 : return false;
1565 2096743 : if (!Matched(type, *rval))
1566 310001 : break;
1567 1786742 : if (!callback(cx, res, count, data))
1568 0 : return false;
1569 1786742 : if (!res->matched())
1570 77879 : ++i;
1571 : }
1572 : } else {
1573 201893 : RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
1574 201893 : bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
1575 201893 : size_t i = 0;
1576 201893 : if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
1577 0 : return false;
1578 201893 : if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
1579 9 : return false;
1580 : }
1581 550784 : return true;
1582 : }
1583 :
1584 : static bool
1585 25920 : BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, CallArgs *args)
1586 : {
1587 25920 : if (fm.match() < 0) {
1588 21960 : args->rval() = NullValue();
1589 21960 : return true;
1590 : }
1591 :
1592 : /* For this non-global match, produce a RegExp.exec-style array. */
1593 3960 : JSObject *obj = NewSlowEmptyArray(cx);
1594 3960 : if (!obj)
1595 0 : return false;
1596 :
1597 11880 : if (!obj->defineElement(cx, 0, StringValue(fm.pattern())) ||
1598 3960 : !obj->defineProperty(cx, cx->runtime->atomState.indexAtom, Int32Value(fm.match())) ||
1599 3960 : !obj->defineProperty(cx, cx->runtime->atomState.inputAtom, StringValue(textstr)))
1600 : {
1601 0 : return false;
1602 : }
1603 :
1604 3960 : args->rval() = ObjectValue(*obj);
1605 3960 : return true;
1606 : }
1607 :
1608 : typedef JSObject **MatchArgType;
1609 :
1610 : /*
1611 : * DoMatch will only callback on global matches, hence this function builds
1612 : * only the "array of matches" returned by match on global regexps.
1613 : */
1614 : static bool
1615 2187 : MatchCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
1616 : {
1617 2187 : JS_ASSERT(count <= JSID_INT_MAX); /* by max string length */
1618 :
1619 2187 : JSObject *&arrayobj = *static_cast<MatchArgType>(p);
1620 2187 : if (!arrayobj) {
1621 405 : arrayobj = NewDenseEmptyArray(cx);
1622 405 : if (!arrayobj)
1623 0 : return false;
1624 : }
1625 :
1626 : Value v;
1627 2187 : return res->createLastMatch(cx, &v) && arrayobj->defineElement(cx, count, v);
1628 : }
1629 :
1630 : JSBool
1631 65943 : js::str_match(JSContext *cx, unsigned argc, Value *vp)
1632 : {
1633 65943 : CallArgs args = CallArgsFromVp(argc, vp);
1634 65943 : JSString *str = ThisToStringForStringProto(cx, args);
1635 65943 : if (!str)
1636 0 : return false;
1637 :
1638 131886 : StringRegExpGuard g;
1639 65943 : if (!g.init(cx, args, true))
1640 18 : return false;
1641 :
1642 65925 : if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
1643 25920 : return BuildFlatMatchArray(cx, str, *fm, &args);
1644 :
1645 : /* Return if there was an error in tryFlatMatch. */
1646 40005 : if (cx->isExceptionPending())
1647 0 : return false;
1648 :
1649 40005 : if (!g.normalizeRegExp(cx, false, 1, args))
1650 0 : return false;
1651 :
1652 40005 : JSObject *array = NULL;
1653 40005 : MatchArgType arg = &array;
1654 40005 : RegExpStatics *res = cx->regExpStatics();
1655 : Value rval;
1656 40005 : if (!DoMatch(cx, res, str, g.regExp(), MatchCallback, arg, MATCH_ARGS, &rval))
1657 0 : return false;
1658 :
1659 40005 : if (g.regExp().global())
1660 9864 : args.rval() = ObjectOrNullValue(array);
1661 : else
1662 30141 : args.rval() = rval;
1663 40005 : return true;
1664 : }
1665 :
1666 : JSBool
1667 484719 : js::str_search(JSContext *cx, unsigned argc, Value *vp)
1668 : {
1669 484719 : CallArgs args = CallArgsFromVp(argc, vp);
1670 484719 : JSString *str = ThisToStringForStringProto(cx, args);
1671 484719 : if (!str)
1672 0 : return false;
1673 :
1674 969438 : StringRegExpGuard g;
1675 484719 : if (!g.init(cx, args, true))
1676 18 : return false;
1677 484701 : if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
1678 465174 : args.rval() = Int32Value(fm->match());
1679 465174 : return true;
1680 : }
1681 :
1682 19527 : if (cx->isExceptionPending()) /* from tryFlatMatch */
1683 0 : return false;
1684 :
1685 19527 : if (!g.normalizeRegExp(cx, false, 1, args))
1686 0 : return false;
1687 :
1688 19527 : JSLinearString *linearStr = str->ensureLinear(cx);
1689 19527 : if (!linearStr)
1690 0 : return false;
1691 :
1692 19527 : const jschar *chars = linearStr->chars();
1693 19527 : size_t length = linearStr->length();
1694 19527 : RegExpStatics *res = cx->regExpStatics();
1695 :
1696 : /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
1697 19527 : size_t i = 0;
1698 : Value result;
1699 19527 : if (!ExecuteRegExp(cx, res, g.regExp(), linearStr, chars, length, &i, RegExpTest, &result))
1700 0 : return false;
1701 :
1702 19527 : if (result.isTrue())
1703 76 : args.rval() = Int32Value(res->matchStart());
1704 : else
1705 19451 : args.rval() = Int32Value(-1);
1706 19527 : return true;
1707 : }
1708 :
1709 : struct ReplaceData
1710 544499 : {
1711 544499 : ReplaceData(JSContext *cx)
1712 544499 : : sb(cx)
1713 544499 : {}
1714 :
1715 : JSString *str; /* 'this' parameter object as a string */
1716 : StringRegExpGuard g; /* regexp parameter object and private data */
1717 : JSObject *lambda; /* replacement function object or null */
1718 : JSObject *elembase; /* object for function(a){return b[a]} replace */
1719 : JSLinearString *repstr; /* replacement string */
1720 : const jschar *dollar; /* null or pointer to first $ in repstr */
1721 : const jschar *dollarEnd; /* limit pointer for js_strchr_limit */
1722 : int leftIndex; /* left context index in str->chars */
1723 : JSSubString dollarStr; /* for "$$" InterpretDollar result */
1724 : bool calledBack; /* record whether callback has been called */
1725 : InvokeArgsGuard args; /* arguments for lambda call */
1726 : StringBuffer sb; /* buffer built during DoMatch */
1727 : };
1728 :
1729 : static bool
1730 8984 : InterpretDollar(JSContext *cx, RegExpStatics *res, const jschar *dp, const jschar *ep,
1731 : ReplaceData &rdata, JSSubString *out, size_t *skip)
1732 : {
1733 8984 : JS_ASSERT(*dp == '$');
1734 :
1735 : /* If there is only a dollar, bail now */
1736 8984 : if (dp + 1 >= ep)
1737 0 : return false;
1738 :
1739 : /* Interpret all Perl match-induced dollar variables. */
1740 8984 : jschar dc = dp[1];
1741 8984 : if (JS7_ISDEC(dc)) {
1742 : /* ECMA-262 Edition 3: 1-9 or 01-99 */
1743 8966 : unsigned num = JS7_UNDEC(dc);
1744 8966 : if (num > res->parenCount())
1745 0 : return false;
1746 :
1747 8966 : const jschar *cp = dp + 2;
1748 8966 : if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
1749 0 : unsigned tmp = 10 * num + JS7_UNDEC(dc);
1750 0 : if (tmp <= res->parenCount()) {
1751 0 : cp++;
1752 0 : num = tmp;
1753 : }
1754 : }
1755 8966 : if (num == 0)
1756 0 : return false;
1757 :
1758 8966 : *skip = cp - dp;
1759 :
1760 8966 : JS_ASSERT(num <= res->parenCount());
1761 :
1762 : /*
1763 : * Note: we index to get the paren with the (1-indexed) pair
1764 : * number, as opposed to a (0-indexed) paren number.
1765 : */
1766 8966 : res->getParen(num, out);
1767 8966 : return true;
1768 : }
1769 :
1770 18 : *skip = 2;
1771 18 : switch (dc) {
1772 : case '$':
1773 0 : rdata.dollarStr.chars = dp;
1774 0 : rdata.dollarStr.length = 1;
1775 0 : *out = rdata.dollarStr;
1776 0 : return true;
1777 : case '&':
1778 18 : res->getLastMatch(out);
1779 18 : return true;
1780 : case '+':
1781 0 : res->getLastParen(out);
1782 0 : return true;
1783 : case '`':
1784 0 : res->getLeftContext(out);
1785 0 : return true;
1786 : case '\'':
1787 0 : res->getRightContext(out);
1788 0 : return true;
1789 : }
1790 0 : return false;
1791 : }
1792 :
1793 : static bool
1794 1870144 : FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
1795 : {
1796 1870144 : JSObject *base = rdata.elembase;
1797 1870144 : if (base) {
1798 : /*
1799 : * The base object is used when replace was passed a lambda which looks like
1800 : * 'function(a) { return b[a]; }' for the base object b. b will not change
1801 : * in the course of the replace unless we end up making a scripted call due
1802 : * to accessing a scripted getter or a value with a scripted toString.
1803 : */
1804 733248 : JS_ASSERT(rdata.lambda);
1805 733248 : JS_ASSERT(!base->getOps()->lookupProperty);
1806 733248 : JS_ASSERT(!base->getOps()->getProperty);
1807 :
1808 : Value match;
1809 733248 : if (!res->createLastMatch(cx, &match))
1810 0 : return false;
1811 733248 : JSString *str = match.toString();
1812 :
1813 : JSAtom *atom;
1814 733248 : if (str->isAtom()) {
1815 733248 : atom = &str->asAtom();
1816 : } else {
1817 0 : atom = js_AtomizeString(cx, str);
1818 0 : if (!atom)
1819 0 : return false;
1820 : }
1821 733248 : jsid id = ATOM_TO_JSID(atom);
1822 :
1823 : JSObject *holder;
1824 733248 : JSProperty *prop = NULL;
1825 733248 : if (!LookupPropertyWithFlags(cx, base, id, JSRESOLVE_QUALIFIED, &holder, &prop))
1826 0 : return false;
1827 :
1828 : /* Only handle the case where the property exists and is on this object. */
1829 733248 : if (prop && holder == base) {
1830 733230 : Shape *shape = (Shape *) prop;
1831 733230 : if (shape->hasSlot() && shape->hasDefaultGetter()) {
1832 733230 : Value value = base->getSlot(shape->slot());
1833 733230 : if (value.isString()) {
1834 733230 : rdata.repstr = value.toString()->ensureLinear(cx);
1835 733230 : if (!rdata.repstr)
1836 0 : return false;
1837 733230 : *sizep = rdata.repstr->length();
1838 733230 : return true;
1839 : }
1840 : }
1841 : }
1842 :
1843 : /*
1844 : * Couldn't handle this property, fall through and despecialize to the
1845 : * general lambda case.
1846 : */
1847 18 : rdata.elembase = NULL;
1848 : }
1849 :
1850 1136914 : JSObject *lambda = rdata.lambda;
1851 1136914 : if (lambda) {
1852 56722 : PreserveRegExpStatics staticsGuard(res);
1853 28361 : if (!staticsGuard.init(cx))
1854 0 : return false;
1855 :
1856 : /*
1857 : * In the lambda case, not only do we find the replacement string's
1858 : * length, we compute repstr and return it via rdata for use within
1859 : * DoReplace. The lambda is called with arguments ($&, $1, $2, ...,
1860 : * index, input), i.e., all the properties of a regexp match array.
1861 : * For $&, etc., we must create string jsvals from cx->regExpStatics.
1862 : * We grab up stack space to keep the newborn strings GC-rooted.
1863 : */
1864 28361 : unsigned p = res->parenCount();
1865 28361 : unsigned argc = 1 + p + 2;
1866 :
1867 28361 : InvokeArgsGuard &args = rdata.args;
1868 28361 : if (!args.pushed() && !cx->stack.pushInvokeArgs(cx, argc, &args))
1869 0 : return false;
1870 :
1871 28361 : args.setCallee(ObjectValue(*lambda));
1872 28361 : args.thisv() = UndefinedValue();
1873 :
1874 : /* Push $&, $1, $2, ... */
1875 28361 : unsigned argi = 0;
1876 28361 : if (!res->createLastMatch(cx, &args[argi++]))
1877 0 : return false;
1878 :
1879 51363 : for (size_t i = 0; i < res->parenCount(); ++i) {
1880 23002 : if (!res->createParen(cx, i + 1, &args[argi++]))
1881 0 : return false;
1882 : }
1883 :
1884 : /* Push match index and input string. */
1885 28361 : args[argi++].setInt32(res->matchStart());
1886 28361 : args[argi].setString(rdata.str);
1887 :
1888 28361 : if (!Invoke(cx, args))
1889 9 : return false;
1890 :
1891 : /* root repstr: rdata is on the stack, so scanned by conservative gc. */
1892 28352 : JSString *repstr = ToString(cx, args.rval());
1893 28352 : if (!repstr)
1894 0 : return false;
1895 28352 : rdata.repstr = repstr->ensureLinear(cx);
1896 28352 : if (!rdata.repstr)
1897 0 : return false;
1898 28352 : *sizep = rdata.repstr->length();
1899 28352 : return true;
1900 : }
1901 :
1902 1108553 : JSString *repstr = rdata.repstr;
1903 1108553 : size_t replen = repstr->length();
1904 1113045 : for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp;
1905 : dp = js_strchr_limit(dp, '$', ep)) {
1906 : JSSubString sub;
1907 : size_t skip;
1908 4492 : if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
1909 4492 : replen += sub.length - skip;
1910 4492 : dp += skip;
1911 : } else {
1912 0 : dp++;
1913 : }
1914 : }
1915 1108553 : *sizep = replen;
1916 1108553 : return true;
1917 : }
1918 :
1919 : /*
1920 : * Precondition: |rdata.sb| already has necessary growth space reserved (as
1921 : * derived from FindReplaceLength).
1922 : */
1923 : static void
1924 1870135 : DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
1925 : {
1926 1870135 : JSLinearString *repstr = rdata.repstr;
1927 : const jschar *cp;
1928 1870135 : const jschar *bp = cp = repstr->chars();
1929 :
1930 1870135 : const jschar *dp = rdata.dollar;
1931 1870135 : const jschar *ep = rdata.dollarEnd;
1932 1874627 : for (; dp; dp = js_strchr_limit(dp, '$', ep)) {
1933 : /* Move one of the constant portions of the replacement value. */
1934 4492 : size_t len = dp - cp;
1935 4492 : rdata.sb.infallibleAppend(cp, len);
1936 4492 : cp = dp;
1937 :
1938 : JSSubString sub;
1939 : size_t skip;
1940 4492 : if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
1941 4492 : len = sub.length;
1942 4492 : rdata.sb.infallibleAppend(sub.chars, len);
1943 4492 : cp += skip;
1944 4492 : dp += skip;
1945 : } else {
1946 0 : dp++;
1947 : }
1948 : }
1949 1870135 : rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp));
1950 1870135 : }
1951 :
1952 : static bool
1953 1870144 : ReplaceRegExpCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
1954 : {
1955 1870144 : ReplaceData &rdata = *static_cast<ReplaceData *>(p);
1956 :
1957 1870144 : rdata.calledBack = true;
1958 1870144 : JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */
1959 1870144 : size_t leftoff = rdata.leftIndex;
1960 1870144 : const jschar *left = str.chars() + leftoff;
1961 1870144 : size_t leftlen = res->matchStart() - leftoff;
1962 1870144 : rdata.leftIndex = res->matchLimit();
1963 :
1964 1870144 : size_t replen = 0; /* silence 'unused' warning */
1965 1870144 : if (!FindReplaceLength(cx, res, rdata, &replen))
1966 9 : return false;
1967 :
1968 1870135 : size_t growth = leftlen + replen;
1969 1870135 : if (!rdata.sb.reserve(rdata.sb.length() + growth))
1970 0 : return false;
1971 1870135 : rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */
1972 1870135 : DoReplace(cx, res, rdata);
1973 1870135 : return true;
1974 : }
1975 :
1976 : static bool
1977 13380 : BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
1978 : const FlatMatch &fm, CallArgs *args)
1979 : {
1980 13380 : RopeBuilder builder(cx);
1981 13380 : size_t match = fm.match();
1982 13380 : size_t matchEnd = match + fm.patternLength();
1983 :
1984 13380 : if (textstr->isRope()) {
1985 : /*
1986 : * If we are replacing over a rope, avoid flattening it by iterating
1987 : * through it, building a new rope.
1988 : */
1989 18 : StringSegmentRange r(cx);
1990 9 : if (!r.init(textstr))
1991 0 : return false;
1992 9 : size_t pos = 0;
1993 36 : while (!r.empty()) {
1994 18 : JSString *str = r.front();
1995 18 : size_t len = str->length();
1996 18 : size_t strEnd = pos + len;
1997 18 : if (pos < matchEnd && strEnd > match) {
1998 : /*
1999 : * We need to special-case any part of the rope that overlaps
2000 : * with the replacement string.
2001 : */
2002 9 : if (match >= pos) {
2003 : /*
2004 : * If this part of the rope overlaps with the left side of
2005 : * the pattern, then it must be the only one to overlap with
2006 : * the first character in the pattern, so we include the
2007 : * replacement string here.
2008 : */
2009 9 : JSString *leftSide = js_NewDependentString(cx, str, 0, match - pos);
2010 27 : if (!leftSide ||
2011 9 : !builder.append(leftSide) ||
2012 9 : !builder.append(repstr)) {
2013 0 : return false;
2014 : }
2015 : }
2016 :
2017 : /*
2018 : * If str runs off the end of the matched string, append the
2019 : * last part of str.
2020 : */
2021 9 : if (strEnd > matchEnd) {
2022 : JSString *rightSide = js_NewDependentString(cx, str, matchEnd - pos,
2023 0 : strEnd - matchEnd);
2024 0 : if (!rightSide || !builder.append(rightSide))
2025 0 : return false;
2026 9 : }
2027 : } else {
2028 9 : if (!builder.append(str))
2029 0 : return false;
2030 : }
2031 18 : pos += str->length();
2032 18 : if (!r.popFront())
2033 0 : return false;
2034 : }
2035 : } else {
2036 13371 : JSString *leftSide = js_NewDependentString(cx, textstr, 0, match);
2037 13371 : if (!leftSide)
2038 0 : return false;
2039 13371 : JSString *rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(),
2040 26742 : textstr->length() - match - fm.patternLength());
2041 53484 : if (!rightSide ||
2042 13371 : !builder.append(leftSide) ||
2043 13371 : !builder.append(repstr) ||
2044 13371 : !builder.append(rightSide)) {
2045 0 : return false;
2046 : }
2047 : }
2048 :
2049 13380 : args->rval() = StringValue(builder.result());
2050 13380 : return true;
2051 : }
2052 :
2053 : /*
2054 : * Perform a linear-scan dollar substitution on the replacement text,
2055 : * constructing a result string that looks like:
2056 : *
2057 : * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
2058 : */
2059 : static inline bool
2060 18 : BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
2061 : const jschar *firstDollar, const FlatMatch &fm, CallArgs *args)
2062 : {
2063 18 : JSLinearString *textstr = textstrArg->ensureLinear(cx);
2064 18 : if (!textstr)
2065 0 : return NULL;
2066 :
2067 18 : JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length());
2068 18 : size_t matchStart = fm.match();
2069 18 : size_t matchLimit = matchStart + fm.patternLength();
2070 :
2071 : /*
2072 : * Most probably:
2073 : *
2074 : * len(newstr) >= len(orig) - len(match) + len(replacement)
2075 : *
2076 : * Note that dollar vars _could_ make the resulting text smaller than this.
2077 : */
2078 36 : StringBuffer newReplaceChars(cx);
2079 18 : if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
2080 0 : return false;
2081 :
2082 : /* Move the pre-dollar chunk in bulk. */
2083 18 : newReplaceChars.infallibleAppend(repstr->chars(), firstDollar);
2084 :
2085 : /* Move the rest char-by-char, interpreting dollars as we encounter them. */
2086 : #define ENSURE(__cond) if (!(__cond)) return false;
2087 18 : const jschar *repstrLimit = repstr->chars() + repstr->length();
2088 63 : for (const jschar *it = firstDollar; it < repstrLimit; ++it) {
2089 45 : if (*it != '$' || it == repstrLimit - 1) {
2090 27 : ENSURE(newReplaceChars.append(*it));
2091 27 : continue;
2092 : }
2093 :
2094 18 : switch (*(it + 1)) {
2095 : case '$': /* Eat one of the dollars. */
2096 0 : ENSURE(newReplaceChars.append(*it));
2097 0 : break;
2098 : case '&':
2099 18 : ENSURE(newReplaceChars.append(textstr->chars() + matchStart,
2100 : textstr->chars() + matchLimit));
2101 18 : break;
2102 : case '`':
2103 0 : ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart));
2104 0 : break;
2105 : case '\'':
2106 0 : ENSURE(newReplaceChars.append(textstr->chars() + matchLimit,
2107 : textstr->chars() + textstr->length()));
2108 0 : break;
2109 : default: /* The dollar we saw was not special (no matter what its mother told it). */
2110 0 : ENSURE(newReplaceChars.append(*it));
2111 0 : continue;
2112 : }
2113 18 : ++it; /* We always eat an extra char in the above switch. */
2114 : }
2115 :
2116 18 : JSString *leftSide = js_NewDependentString(cx, textstr, 0, matchStart);
2117 18 : ENSURE(leftSide);
2118 :
2119 18 : JSString *newReplace = newReplaceChars.finishString();
2120 18 : ENSURE(newReplace);
2121 :
2122 18 : JS_ASSERT(textstr->length() >= matchLimit);
2123 : JSString *rightSide = js_NewDependentString(cx, textstr, matchLimit,
2124 18 : textstr->length() - matchLimit);
2125 18 : ENSURE(rightSide);
2126 :
2127 18 : RopeBuilder builder(cx);
2128 18 : ENSURE(builder.append(leftSide) &&
2129 : builder.append(newReplace) &&
2130 : builder.append(rightSide));
2131 : #undef ENSURE
2132 :
2133 18 : args->rval() = StringValue(builder.result());
2134 18 : return true;
2135 : }
2136 :
2137 : static inline bool
2138 510788 : str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
2139 : {
2140 510788 : if (!rdata.g.normalizeRegExp(cx, true, 2, args))
2141 0 : return false;
2142 :
2143 510788 : rdata.leftIndex = 0;
2144 510788 : rdata.calledBack = false;
2145 :
2146 510788 : RegExpStatics *res = cx->regExpStatics();
2147 510788 : RegExpShared &re = rdata.g.regExp();
2148 :
2149 : Value tmp;
2150 510788 : if (!DoMatch(cx, res, rdata.str, re, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
2151 9 : return false;
2152 :
2153 510779 : if (!rdata.calledBack) {
2154 : /* Didn't match, so the string is unmodified. */
2155 300804 : args.rval() = StringValue(rdata.str);
2156 300804 : return true;
2157 : }
2158 :
2159 : JSSubString sub;
2160 209975 : res->getRightContext(&sub);
2161 209975 : if (!rdata.sb.append(sub.chars, sub.length))
2162 0 : return false;
2163 :
2164 209975 : JSString *retstr = rdata.sb.finishString();
2165 209975 : if (!retstr)
2166 0 : return false;
2167 :
2168 209975 : args.rval() = StringValue(retstr);
2169 209975 : return true;
2170 : }
2171 :
2172 : static inline bool
2173 0 : str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm)
2174 : {
2175 0 : JS_ASSERT(fm.match() >= 0);
2176 :
2177 0 : JSString *matchStr = js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength());
2178 0 : if (!matchStr)
2179 0 : return false;
2180 :
2181 : /* lambda(matchStr, matchStart, textstr) */
2182 : static const uint32_t lambdaArgc = 3;
2183 0 : if (!cx->stack.pushInvokeArgs(cx, lambdaArgc, &rdata.args))
2184 0 : return false;
2185 :
2186 0 : CallArgs &args = rdata.args;
2187 0 : args.calleev().setObject(*rdata.lambda);
2188 0 : args.thisv().setUndefined();
2189 :
2190 0 : Value *sp = args.array();
2191 0 : sp[0].setString(matchStr);
2192 0 : sp[1].setInt32(fm.match());
2193 0 : sp[2].setString(rdata.str);
2194 :
2195 0 : if (!Invoke(cx, rdata.args))
2196 0 : return false;
2197 :
2198 0 : JSString *repstr = ToString(cx, args.rval());
2199 0 : if (!repstr)
2200 0 : return false;
2201 :
2202 0 : JSString *leftSide = js_NewDependentString(cx, rdata.str, 0, fm.match());
2203 0 : if (!leftSide)
2204 0 : return false;
2205 :
2206 0 : size_t matchLimit = fm.match() + fm.patternLength();
2207 : JSString *rightSide = js_NewDependentString(cx, rdata.str, matchLimit,
2208 0 : rdata.str->length() - matchLimit);
2209 0 : if (!rightSide)
2210 0 : return false;
2211 :
2212 0 : RopeBuilder builder(cx);
2213 0 : if (!(builder.append(leftSide) &&
2214 0 : builder.append(repstr) &&
2215 0 : builder.append(rightSide))) {
2216 0 : return false;
2217 : }
2218 :
2219 0 : outerArgs.rval() = StringValue(builder.result());
2220 0 : return true;
2221 : }
2222 :
2223 : static const uint32_t ReplaceOptArg = 2;
2224 :
2225 : JSBool
2226 544499 : js::str_replace(JSContext *cx, unsigned argc, Value *vp)
2227 : {
2228 544499 : CallArgs args = CallArgsFromVp(argc, vp);
2229 :
2230 1088998 : ReplaceData rdata(cx);
2231 544499 : rdata.str = ThisToStringForStringProto(cx, args);
2232 544499 : if (!rdata.str)
2233 0 : return false;
2234 :
2235 544499 : if (!rdata.g.init(cx, args))
2236 18 : return false;
2237 :
2238 : /* Extract replacement string/function. */
2239 544481 : if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) {
2240 2832 : rdata.lambda = &args[1].toObject();
2241 2832 : rdata.elembase = NULL;
2242 2832 : rdata.repstr = NULL;
2243 2832 : rdata.dollar = rdata.dollarEnd = NULL;
2244 :
2245 2832 : if (rdata.lambda->isFunction()) {
2246 2832 : JSFunction *fun = rdata.lambda->toFunction();
2247 2832 : if (fun->isInterpreted()) {
2248 : /*
2249 : * Pattern match the script to check if it is is indexing into a
2250 : * particular object, e.g. 'function(a) { return b[a]; }'. Avoid
2251 : * calling the script in such cases, which are used by javascript
2252 : * packers (particularly the popular Dean Edwards packer) to efficiently
2253 : * encode large scripts. We only handle the code patterns generated
2254 : * by such packers here.
2255 : */
2256 2742 : JSScript *script = fun->script();
2257 2742 : jsbytecode *pc = script->code;
2258 :
2259 2742 : Value table = UndefinedValue();
2260 2742 : if (JSOp(*pc) == JSOP_GETFCSLOT) {
2261 261 : table = fun->getFlatClosureUpvar(GET_UINT16(pc));
2262 261 : pc += JSOP_GETFCSLOT_LENGTH;
2263 : }
2264 :
2265 3138 : if (table.isObject() &&
2266 198 : JSOp(*pc) == JSOP_GETARG && GET_SLOTNO(pc) == 0 &&
2267 99 : JSOp(pc[JSOP_GETARG_LENGTH]) == JSOP_GETELEM &&
2268 99 : JSOp(pc[JSOP_GETARG_LENGTH + JSOP_GETELEM_LENGTH]) == JSOP_RETURN) {
2269 99 : Class *clasp = table.toObject().getClass();
2270 297 : if (clasp->isNative() &&
2271 99 : !clasp->ops.lookupProperty &&
2272 99 : !clasp->ops.getProperty) {
2273 99 : rdata.elembase = &table.toObject();
2274 : }
2275 : }
2276 : }
2277 : }
2278 : } else {
2279 541649 : rdata.lambda = NULL;
2280 541649 : rdata.elembase = NULL;
2281 541649 : rdata.repstr = ArgToRootedString(cx, args, 1);
2282 541649 : if (!rdata.repstr)
2283 0 : return false;
2284 :
2285 : /* We're about to store pointers into the middle of our string. */
2286 541649 : JSFixedString *fixed = rdata.repstr->ensureFixed(cx);
2287 541649 : if (!fixed)
2288 0 : return false;
2289 541649 : rdata.dollarEnd = fixed->chars() + fixed->length();
2290 541649 : rdata.dollar = js_strchr_limit(fixed->chars(), '$', rdata.dollarEnd);
2291 : }
2292 :
2293 : /*
2294 : * Unlike its |String.prototype| brethren, |replace| doesn't convert
2295 : * its input to a regular expression. (Even if it contains metachars.)
2296 : *
2297 : * However, if the user invokes our (non-standard) |flags| argument
2298 : * extension then we revert to creating a regular expression. Note that
2299 : * this is observable behavior through the side-effect mutation of the
2300 : * |RegExp| statics.
2301 : */
2302 :
2303 544481 : const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false);
2304 544481 : if (!fm) {
2305 510788 : if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */
2306 0 : return false;
2307 510788 : return str_replace_regexp(cx, args, rdata);
2308 : }
2309 :
2310 33693 : if (fm->match() < 0) {
2311 20295 : args.rval() = StringValue(rdata.str);
2312 20295 : return true;
2313 : }
2314 :
2315 13398 : if (rdata.lambda)
2316 0 : return str_replace_flat_lambda(cx, args, rdata, *fm);
2317 :
2318 : /*
2319 : * Note: we could optimize the text.length == pattern.length case if we wanted,
2320 : * even in the presence of dollar metachars.
2321 : */
2322 13398 : if (rdata.dollar)
2323 18 : return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, &args);
2324 :
2325 13380 : return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, &args);
2326 : }
2327 :
2328 : class SplitMatchResult {
2329 : size_t endIndex_;
2330 : size_t length_;
2331 :
2332 : public:
2333 34592 : void setFailure() {
2334 : JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH);
2335 34592 : endIndex_ = SIZE_MAX;
2336 34592 : }
2337 1067570 : bool isFailure() const {
2338 1067570 : return (endIndex_ == SIZE_MAX);
2339 : }
2340 344326 : size_t endIndex() const {
2341 344326 : JS_ASSERT(!isFailure());
2342 344326 : return endIndex_;
2343 : }
2344 344326 : size_t length() const {
2345 344326 : JS_ASSERT(!isFailure());
2346 344326 : return length_;
2347 : }
2348 344326 : void setResult(size_t length, size_t endIndex) {
2349 344326 : length_ = length;
2350 344326 : endIndex_ = endIndex;
2351 344326 : }
2352 : };
2353 :
2354 : template<class Matcher>
2355 : static JSObject *
2356 44357 : SplitHelper(JSContext *cx, JSLinearString *str, uint32_t limit, Matcher splitMatch, TypeObject *type)
2357 : {
2358 44357 : size_t strLength = str->length();
2359 : SplitMatchResult result;
2360 :
2361 : /* Step 11. */
2362 44357 : if (strLength == 0) {
2363 3542 : if (!splitMatch(cx, str, 0, &result))
2364 0 : return NULL;
2365 :
2366 : /*
2367 : * NB: Unlike in the non-empty string case, it's perfectly fine
2368 : * (indeed the spec requires it) if we match at the end of the
2369 : * string. Thus these cases should hold:
2370 : *
2371 : * var a = "".split("");
2372 : * assertEq(a.length, 0);
2373 : * var b = "".split(/.?/);
2374 : * assertEq(b.length, 0);
2375 : */
2376 3542 : if (!result.isFailure())
2377 0 : return NewDenseEmptyArray(cx);
2378 :
2379 3542 : Value v = StringValue(str);
2380 3542 : return NewDenseCopiedArray(cx, 1, &v);
2381 : }
2382 :
2383 : /* Step 12. */
2384 40815 : size_t lastEndIndex = 0;
2385 40815 : size_t index = 0;
2386 :
2387 : /* Step 13. */
2388 81630 : AutoValueVector splits(cx);
2389 :
2390 425956 : while (index < strLength) {
2391 : /* Step 13(a). */
2392 375376 : if (!splitMatch(cx, str, index, &result))
2393 0 : return NULL;
2394 :
2395 : /*
2396 : * Step 13(b).
2397 : *
2398 : * Our match algorithm differs from the spec in that it returns the
2399 : * next index at which a match happens. If no match happens we're
2400 : * done.
2401 : *
2402 : * But what if the match is at the end of the string (and the string is
2403 : * not empty)? Per 13(c)(ii) this shouldn't be a match, so we have to
2404 : * specially exclude it. Thus this case should hold:
2405 : *
2406 : * var a = "abc".split(/\b/);
2407 : * assertEq(a.length, 1);
2408 : * assertEq(a[0], "abc");
2409 : */
2410 375376 : if (result.isFailure())
2411 31050 : break;
2412 :
2413 : /* Step 13(c)(i). */
2414 344326 : size_t sepLength = result.length();
2415 344326 : size_t endIndex = result.endIndex();
2416 344326 : if (sepLength == 0 && endIndex == strLength)
2417 0 : break;
2418 :
2419 : /* Step 13(c)(ii). */
2420 344326 : if (endIndex == lastEndIndex) {
2421 99298 : index++;
2422 99298 : continue;
2423 : }
2424 :
2425 : /* Step 13(c)(iii). */
2426 245028 : JS_ASSERT(lastEndIndex < endIndex);
2427 245028 : JS_ASSERT(sepLength <= strLength);
2428 245028 : JS_ASSERT(lastEndIndex + sepLength <= endIndex);
2429 :
2430 : /* Steps 13(c)(iii)(1-3). */
2431 245028 : size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
2432 245028 : JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength);
2433 245028 : if (!sub || !splits.append(StringValue(sub)))
2434 0 : return NULL;
2435 :
2436 : /* Step 13(c)(iii)(4). */
2437 245028 : if (splits.length() == limit)
2438 0 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2439 :
2440 : /* Step 13(c)(iii)(5). */
2441 245028 : lastEndIndex = endIndex;
2442 :
2443 : /* Step 13(c)(iii)(6-7). */
2444 : if (Matcher::returnsCaptures) {
2445 48648 : RegExpStatics *res = cx->regExpStatics();
2446 48648 : for (size_t i = 0; i < res->parenCount(); i++) {
2447 : /* Steps 13(c)(iii)(7)(a-c). */
2448 0 : if (res->pairIsPresent(i + 1)) {
2449 : JSSubString parsub;
2450 0 : res->getParen(i + 1, &parsub);
2451 0 : sub = js_NewStringCopyN(cx, parsub.chars, parsub.length);
2452 0 : if (!sub || !splits.append(StringValue(sub)))
2453 0 : return NULL;
2454 : } else {
2455 : /* Only string entries have been accounted for so far. */
2456 0 : AddTypeProperty(cx, type, NULL, UndefinedValue());
2457 0 : if (!splits.append(UndefinedValue()))
2458 0 : return NULL;
2459 : }
2460 :
2461 : /* Step 13(c)(iii)(7)(d). */
2462 0 : if (splits.length() == limit)
2463 0 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2464 : }
2465 : }
2466 :
2467 : /* Step 13(c)(iii)(8). */
2468 245028 : index = lastEndIndex;
2469 : }
2470 :
2471 : /* Steps 14-15. */
2472 40815 : JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
2473 40815 : if (!sub || !splits.append(StringValue(sub)))
2474 0 : return NULL;
2475 :
2476 : /* Step 16. */
2477 40815 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2478 : }
2479 :
2480 : /*
2481 : * The SplitMatch operation from ES5 15.5.4.14 is implemented using different
2482 : * paths for regular expression and string separators.
2483 : *
2484 : * The algorithm differs from the spec in that the we return the next index at
2485 : * which a match happens.
2486 : */
2487 : class SplitRegExpMatcher
2488 : {
2489 : RegExpShared &re;
2490 : RegExpStatics *res;
2491 :
2492 : public:
2493 20359 : SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
2494 :
2495 : static const bool returnsCaptures = true;
2496 :
2497 68882 : bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result)
2498 : {
2499 68882 : Value rval = UndefinedValue();
2500 68882 : const jschar *chars = str->chars();
2501 68882 : size_t length = str->length();
2502 68882 : if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
2503 0 : return false;
2504 68882 : if (!rval.isTrue()) {
2505 20234 : result->setFailure();
2506 20234 : return true;
2507 : }
2508 : JSSubString sep;
2509 48648 : res->getLastMatch(&sep);
2510 :
2511 48648 : result->setResult(sep.length, index);
2512 48648 : return true;
2513 : }
2514 : };
2515 :
2516 : class SplitStringMatcher
2517 : {
2518 : const jschar *sepChars;
2519 : size_t sepLength;
2520 :
2521 : public:
2522 23998 : SplitStringMatcher(JSLinearString *sep) {
2523 23998 : sepChars = sep->chars();
2524 23998 : sepLength = sep->length();
2525 23998 : }
2526 :
2527 : static const bool returnsCaptures = false;
2528 :
2529 310036 : bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res)
2530 : {
2531 310036 : JS_ASSERT(index == 0 || index < str->length());
2532 310036 : const jschar *chars = str->chars();
2533 310036 : int match = StringMatch(chars + index, str->length() - index, sepChars, sepLength);
2534 310036 : if (match == -1)
2535 14358 : res->setFailure();
2536 : else
2537 295678 : res->setResult(sepLength, index + match + sepLength);
2538 310036 : return true;
2539 : }
2540 : };
2541 :
2542 : /* ES5 15.5.4.14 */
2543 : JSBool
2544 44375 : js::str_split(JSContext *cx, unsigned argc, Value *vp)
2545 : {
2546 44375 : CallArgs args = CallArgsFromVp(argc, vp);
2547 :
2548 : /* Steps 1-2. */
2549 44375 : JSString *str = ThisToStringForStringProto(cx, args);
2550 44375 : if (!str)
2551 0 : return false;
2552 :
2553 44375 : TypeObject *type = GetTypeCallerInitObject(cx, JSProto_Array);
2554 44375 : if (!type)
2555 0 : return false;
2556 44375 : AddTypeProperty(cx, type, NULL, Type::StringType());
2557 :
2558 : /* Step 5: Use the second argument as the split limit, if given. */
2559 : uint32_t limit;
2560 44375 : if (args.hasDefined(1)) {
2561 : double d;
2562 0 : if (!ToNumber(cx, args[1], &d))
2563 0 : return false;
2564 0 : limit = js_DoubleToECMAUint32(d);
2565 : } else {
2566 44375 : limit = UINT32_MAX;
2567 : }
2568 :
2569 : /* Step 8. */
2570 88750 : RegExpGuard re;
2571 44375 : JSLinearString *sepstr = NULL;
2572 44375 : bool sepDefined = args.hasDefined(0);
2573 44375 : if (sepDefined) {
2574 44375 : if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
2575 20359 : if (!RegExpToShared(cx, args[0].toObject(), &re))
2576 0 : return false;
2577 : } else {
2578 24016 : sepstr = ArgToRootedString(cx, args, 0);
2579 24016 : if (!sepstr)
2580 18 : return false;
2581 : }
2582 : }
2583 :
2584 : /* Step 9. */
2585 44357 : if (limit == 0) {
2586 0 : JSObject *aobj = NewDenseEmptyArray(cx);
2587 0 : if (!aobj)
2588 0 : return false;
2589 0 : aobj->setType(type);
2590 0 : args.rval() = ObjectValue(*aobj);
2591 0 : return true;
2592 : }
2593 :
2594 : /* Step 10. */
2595 44357 : if (!sepDefined) {
2596 0 : Value v = StringValue(str);
2597 0 : JSObject *aobj = NewDenseCopiedArray(cx, 1, &v);
2598 0 : if (!aobj)
2599 0 : return false;
2600 0 : aobj->setType(type);
2601 0 : args.rval() = ObjectValue(*aobj);
2602 0 : return true;
2603 : }
2604 44357 : JSLinearString *strlin = str->ensureLinear(cx);
2605 44357 : if (!strlin)
2606 0 : return false;
2607 :
2608 : /* Steps 11-15. */
2609 : JSObject *aobj;
2610 44357 : if (!re.initialized())
2611 23998 : aobj = SplitHelper(cx, strlin, limit, SplitStringMatcher(sepstr), type);
2612 : else
2613 20359 : aobj = SplitHelper(cx, strlin, limit, SplitRegExpMatcher(*re, cx->regExpStatics()), type);
2614 44357 : if (!aobj)
2615 0 : return false;
2616 :
2617 : /* Step 16. */
2618 44357 : aobj->setType(type);
2619 44357 : args.rval() = ObjectValue(*aobj);
2620 44357 : return true;
2621 : }
2622 :
2623 : #if JS_HAS_PERL_SUBSTR
2624 : static JSBool
2625 32309 : str_substr(JSContext *cx, unsigned argc, Value *vp)
2626 : {
2627 32309 : CallArgs args = CallArgsFromVp(argc, vp);
2628 32309 : JSString *str = ThisToStringForStringProto(cx, args);
2629 32309 : if (!str)
2630 0 : return false;
2631 :
2632 : int32_t length, len, begin;
2633 32309 : if (args.length() > 0) {
2634 32309 : length = int32_t(str->length());
2635 32309 : if (!ValueToIntegerRange(cx, args[0], &begin))
2636 0 : return false;
2637 :
2638 32309 : if (begin >= length) {
2639 142 : str = cx->runtime->emptyString;
2640 142 : goto out;
2641 : }
2642 32167 : if (begin < 0) {
2643 332 : begin += length; /* length + INT_MIN will always be less than 0 */
2644 332 : if (begin < 0)
2645 9 : begin = 0;
2646 : }
2647 :
2648 32167 : if (args.hasDefined(1)) {
2649 29368 : if (!ValueToIntegerRange(cx, args[1], &len))
2650 0 : return false;
2651 :
2652 29368 : if (len <= 0) {
2653 3 : str = cx->runtime->emptyString;
2654 3 : goto out;
2655 : }
2656 :
2657 29365 : if (uint32_t(length) < uint32_t(begin + len))
2658 1854 : len = length - begin;
2659 : } else {
2660 2799 : len = length - begin;
2661 : }
2662 :
2663 32164 : str = js_NewDependentString(cx, str, size_t(begin), size_t(len));
2664 32164 : if (!str)
2665 0 : return false;
2666 : }
2667 :
2668 : out:
2669 32309 : args.rval() = StringValue(str);
2670 32309 : return true;
2671 : }
2672 : #endif /* JS_HAS_PERL_SUBSTR */
2673 :
2674 : /*
2675 : * Python-esque sequence operations.
2676 : */
2677 : static JSBool
2678 353 : str_concat(JSContext *cx, unsigned argc, Value *vp)
2679 : {
2680 353 : CallArgs args = CallArgsFromVp(argc, vp);
2681 353 : JSString *str = ThisToStringForStringProto(cx, args);
2682 353 : if (!str)
2683 0 : return false;
2684 :
2685 697 : for (unsigned i = 0; i < args.length(); i++) {
2686 353 : JSString *argStr = ToString(cx, args[i]);
2687 353 : if (!argStr)
2688 9 : return false;
2689 :
2690 344 : str = js_ConcatStrings(cx, str, argStr);
2691 344 : if (!str)
2692 0 : return false;
2693 : }
2694 :
2695 344 : args.rval() = StringValue(str);
2696 344 : return true;
2697 : }
2698 :
2699 : static JSBool
2700 40190 : str_slice(JSContext *cx, unsigned argc, Value *vp)
2701 : {
2702 40190 : CallArgs args = CallArgsFromVp(argc, vp);
2703 :
2704 40190 : if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) {
2705 : size_t begin, end, length;
2706 :
2707 37478 : JSString *str = args.thisv().toString();
2708 37478 : begin = args[0].toInt32();
2709 37478 : end = str->length();
2710 37478 : if (begin <= end) {
2711 3748 : length = end - begin;
2712 3748 : if (length == 0) {
2713 830 : str = cx->runtime->emptyString;
2714 : } else {
2715 : str = (length == 1)
2716 0 : ? cx->runtime->staticStrings.getUnitStringForElement(cx, str, begin)
2717 2918 : : js_NewDependentString(cx, str, begin, length);
2718 2918 : if (!str)
2719 0 : return false;
2720 : }
2721 3748 : args.rval() = StringValue(str);
2722 3748 : return true;
2723 : }
2724 : }
2725 :
2726 36442 : JSString *str = ThisToStringForStringProto(cx, args);
2727 36442 : if (!str)
2728 0 : return false;
2729 :
2730 36442 : if (args.length() != 0) {
2731 : double begin, end, length;
2732 :
2733 36442 : if (!ToInteger(cx, args[0], &begin))
2734 0 : return false;
2735 36442 : length = str->length();
2736 36442 : if (begin < 0) {
2737 33730 : begin += length;
2738 33730 : if (begin < 0)
2739 0 : begin = 0;
2740 2712 : } else if (begin > length) {
2741 0 : begin = length;
2742 : }
2743 :
2744 36442 : if (args.hasDefined(1)) {
2745 2712 : if (!ToInteger(cx, args[1], &end))
2746 0 : return false;
2747 2712 : if (end < 0) {
2748 690 : end += length;
2749 690 : if (end < 0)
2750 0 : end = 0;
2751 2022 : } else if (end > length) {
2752 2 : end = length;
2753 : }
2754 2712 : if (end < begin)
2755 0 : end = begin;
2756 : } else {
2757 33730 : end = length;
2758 : }
2759 :
2760 : str = js_NewDependentString(cx, str,
2761 : (size_t)begin,
2762 36442 : (size_t)(end - begin));
2763 36442 : if (!str)
2764 0 : return false;
2765 : }
2766 36442 : args.rval() = StringValue(str);
2767 36442 : return true;
2768 : }
2769 :
2770 : #if JS_HAS_STR_HTML_HELPERS
2771 : /*
2772 : * HTML composition aids.
2773 : */
2774 : static bool
2775 0 : tagify(JSContext *cx, const char *begin, JSLinearString *param, const char *end,
2776 : CallReceiver call)
2777 : {
2778 0 : JSString *thisstr = ThisToStringForStringProto(cx, call);
2779 0 : if (!thisstr)
2780 0 : return false;
2781 :
2782 0 : JSLinearString *str = thisstr->ensureLinear(cx);
2783 0 : if (!str)
2784 0 : return false;
2785 :
2786 0 : if (!end)
2787 0 : end = begin;
2788 :
2789 0 : size_t beglen = strlen(begin);
2790 0 : size_t taglen = 1 + beglen + 1; /* '<begin' + '>' */
2791 0 : size_t parlen = 0; /* Avoid warning. */
2792 0 : if (param) {
2793 0 : parlen = param->length();
2794 0 : taglen += 2 + parlen + 1; /* '="param"' */
2795 : }
2796 0 : size_t endlen = strlen(end);
2797 0 : taglen += str->length() + 2 + endlen + 1; /* 'str</end>' */
2798 :
2799 0 : if (taglen >= ~(size_t)0 / sizeof(jschar)) {
2800 0 : js_ReportAllocationOverflow(cx);
2801 0 : return false;
2802 : }
2803 :
2804 0 : jschar *tagbuf = (jschar *) cx->malloc_((taglen + 1) * sizeof(jschar));
2805 0 : if (!tagbuf)
2806 0 : return false;
2807 :
2808 0 : size_t j = 0;
2809 0 : tagbuf[j++] = '<';
2810 0 : for (size_t i = 0; i < beglen; i++)
2811 0 : tagbuf[j++] = (jschar)begin[i];
2812 0 : if (param) {
2813 0 : tagbuf[j++] = '=';
2814 0 : tagbuf[j++] = '"';
2815 0 : js_strncpy(&tagbuf[j], param->chars(), parlen);
2816 0 : j += parlen;
2817 0 : tagbuf[j++] = '"';
2818 : }
2819 0 : tagbuf[j++] = '>';
2820 :
2821 0 : js_strncpy(&tagbuf[j], str->chars(), str->length());
2822 0 : j += str->length();
2823 0 : tagbuf[j++] = '<';
2824 0 : tagbuf[j++] = '/';
2825 0 : for (size_t i = 0; i < endlen; i++)
2826 0 : tagbuf[j++] = (jschar)end[i];
2827 0 : tagbuf[j++] = '>';
2828 0 : JS_ASSERT(j == taglen);
2829 0 : tagbuf[j] = 0;
2830 :
2831 0 : JSString *retstr = js_NewString(cx, tagbuf, taglen);
2832 0 : if (!retstr) {
2833 0 : Foreground::free_((char *)tagbuf);
2834 0 : return false;
2835 : }
2836 0 : call.rval() = StringValue(retstr);
2837 0 : return true;
2838 : }
2839 :
2840 : static JSBool
2841 0 : tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end)
2842 : {
2843 0 : JSLinearString *param = ArgToRootedString(cx, args, 0);
2844 0 : if (!param)
2845 0 : return false;
2846 :
2847 0 : return tagify(cx, begin, param, end, args);
2848 : }
2849 :
2850 : static JSBool
2851 0 : str_bold(JSContext *cx, unsigned argc, Value *vp)
2852 : {
2853 0 : return tagify(cx, "b", NULL, NULL, CallReceiverFromVp(vp));
2854 : }
2855 :
2856 : static JSBool
2857 0 : str_italics(JSContext *cx, unsigned argc, Value *vp)
2858 : {
2859 0 : return tagify(cx, "i", NULL, NULL, CallReceiverFromVp(vp));
2860 : }
2861 :
2862 : static JSBool
2863 0 : str_fixed(JSContext *cx, unsigned argc, Value *vp)
2864 : {
2865 0 : return tagify(cx, "tt", NULL, NULL, CallReceiverFromVp(vp));
2866 : }
2867 :
2868 : static JSBool
2869 0 : str_fontsize(JSContext *cx, unsigned argc, Value *vp)
2870 : {
2871 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font");
2872 : }
2873 :
2874 : static JSBool
2875 0 : str_fontcolor(JSContext *cx, unsigned argc, Value *vp)
2876 : {
2877 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font");
2878 : }
2879 :
2880 : static JSBool
2881 0 : str_link(JSContext *cx, unsigned argc, Value *vp)
2882 : {
2883 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a");
2884 : }
2885 :
2886 : static JSBool
2887 0 : str_anchor(JSContext *cx, unsigned argc, Value *vp)
2888 : {
2889 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a");
2890 : }
2891 :
2892 : static JSBool
2893 0 : str_strike(JSContext *cx, unsigned argc, Value *vp)
2894 : {
2895 0 : return tagify(cx, "strike", NULL, NULL, CallReceiverFromVp(vp));
2896 : }
2897 :
2898 : static JSBool
2899 0 : str_small(JSContext *cx, unsigned argc, Value *vp)
2900 : {
2901 0 : return tagify(cx, "small", NULL, NULL, CallReceiverFromVp(vp));
2902 : }
2903 :
2904 : static JSBool
2905 0 : str_big(JSContext *cx, unsigned argc, Value *vp)
2906 : {
2907 0 : return tagify(cx, "big", NULL, NULL, CallReceiverFromVp(vp));
2908 : }
2909 :
2910 : static JSBool
2911 0 : str_blink(JSContext *cx, unsigned argc, Value *vp)
2912 : {
2913 0 : return tagify(cx, "blink", NULL, NULL, CallReceiverFromVp(vp));
2914 : }
2915 :
2916 : static JSBool
2917 0 : str_sup(JSContext *cx, unsigned argc, Value *vp)
2918 : {
2919 0 : return tagify(cx, "sup", NULL, NULL, CallReceiverFromVp(vp));
2920 : }
2921 :
2922 : static JSBool
2923 0 : str_sub(JSContext *cx, unsigned argc, Value *vp)
2924 : {
2925 0 : return tagify(cx, "sub", NULL, NULL, CallReceiverFromVp(vp));
2926 : }
2927 : #endif /* JS_HAS_STR_HTML_HELPERS */
2928 :
2929 : static JSFunctionSpec string_methods[] = {
2930 : #if JS_HAS_TOSOURCE
2931 : JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE),
2932 : JS_FN(js_toSource_str, str_toSource, 0,0),
2933 : #endif
2934 :
2935 : /* Java-like methods. */
2936 : JS_FN(js_toString_str, js_str_toString, 0,0),
2937 : JS_FN(js_valueOf_str, js_str_toString, 0,0),
2938 : JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE),
2939 : JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE),
2940 : JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE),
2941 : JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE),
2942 : JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE),
2943 : JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE),
2944 : JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
2945 : JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE),
2946 : JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE),
2947 : JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE),
2948 : JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE),
2949 : JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE),
2950 : JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE),
2951 :
2952 : /* Perl-ish methods (search is actually Python-esque). */
2953 : JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE),
2954 : JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE),
2955 : JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE),
2956 : JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE),
2957 : #if JS_HAS_PERL_SUBSTR
2958 : JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE),
2959 : #endif
2960 :
2961 : /* Python-esque sequence methods. */
2962 : JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE),
2963 : JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE),
2964 :
2965 : /* HTML string methods. */
2966 : #if JS_HAS_STR_HTML_HELPERS
2967 : JS_FN("bold", str_bold, 0,0),
2968 : JS_FN("italics", str_italics, 0,0),
2969 : JS_FN("fixed", str_fixed, 0,0),
2970 : JS_FN("fontsize", str_fontsize, 1,0),
2971 : JS_FN("fontcolor", str_fontcolor, 1,0),
2972 : JS_FN("link", str_link, 1,0),
2973 : JS_FN("anchor", str_anchor, 1,0),
2974 : JS_FN("strike", str_strike, 0,0),
2975 : JS_FN("small", str_small, 0,0),
2976 : JS_FN("big", str_big, 0,0),
2977 : JS_FN("blink", str_blink, 0,0),
2978 : JS_FN("sup", str_sup, 0,0),
2979 : JS_FN("sub", str_sub, 0,0),
2980 : #endif
2981 :
2982 : JS_FS_END
2983 : };
2984 :
2985 : JSBool
2986 350746 : js_String(JSContext *cx, unsigned argc, Value *vp)
2987 : {
2988 350746 : CallArgs args = CallArgsFromVp(argc, vp);
2989 :
2990 : JSString *str;
2991 350746 : if (args.length() > 0) {
2992 350304 : str = ToString(cx, args[0]);
2993 350304 : if (!str)
2994 0 : return false;
2995 : } else {
2996 442 : str = cx->runtime->emptyString;
2997 : }
2998 :
2999 350746 : if (IsConstructing(args)) {
3000 200551 : StringObject *strobj = StringObject::create(cx, str);
3001 200551 : if (!strobj)
3002 0 : return false;
3003 200551 : args.rval() = ObjectValue(*strobj);
3004 200551 : return true;
3005 : }
3006 :
3007 150195 : args.rval() = StringValue(str);
3008 150195 : return true;
3009 : }
3010 :
3011 : JSBool
3012 2759798 : js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp)
3013 : {
3014 2759798 : CallArgs args = CallArgsFromVp(argc, vp);
3015 :
3016 2759798 : JS_ASSERT(args.length() <= StackSpace::ARGS_LENGTH_MAX);
3017 2759798 : if (args.length() == 1) {
3018 : uint16_t code;
3019 1850378 : if (!ValueToUint16(cx, args[0], &code))
3020 9 : return JS_FALSE;
3021 1850369 : if (StaticStrings::hasUnit(code)) {
3022 673676 : args.rval() = StringValue(cx->runtime->staticStrings.getUnit(code));
3023 673676 : return JS_TRUE;
3024 : }
3025 1176693 : args[0].setInt32(code);
3026 : }
3027 2086113 : jschar *chars = (jschar *) cx->malloc_((args.length() + 1) * sizeof(jschar));
3028 2086113 : if (!chars)
3029 0 : return JS_FALSE;
3030 16256268 : for (unsigned i = 0; i < args.length(); i++) {
3031 : uint16_t code;
3032 14170155 : if (!ValueToUint16(cx, args[i], &code)) {
3033 0 : cx->free_(chars);
3034 0 : return JS_FALSE;
3035 : }
3036 14170155 : chars[i] = (jschar)code;
3037 : }
3038 2086113 : chars[args.length()] = 0;
3039 2086113 : JSString *str = js_NewString(cx, chars, args.length());
3040 2086113 : if (!str) {
3041 0 : cx->free_(chars);
3042 0 : return JS_FALSE;
3043 : }
3044 :
3045 2086113 : args.rval() = StringValue(str);
3046 2086113 : return JS_TRUE;
3047 : }
3048 :
3049 : static JSFunctionSpec string_static_methods[] = {
3050 : JS_FN("fromCharCode", js::str_fromCharCode, 1, 0),
3051 : JS_FS_END
3052 : };
3053 :
3054 : Shape *
3055 19784 : StringObject::assignInitialShape(JSContext *cx)
3056 : {
3057 19784 : JS_ASSERT(nativeEmpty());
3058 :
3059 19784 : return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom),
3060 19784 : LENGTH_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
3061 : }
3062 :
3063 : JSObject *
3064 7022 : js_InitStringClass(JSContext *cx, JSObject *obj)
3065 : {
3066 7022 : JS_ASSERT(obj->isNative());
3067 :
3068 7022 : GlobalObject *global = &obj->asGlobal();
3069 :
3070 7022 : JSObject *proto = global->createBlankPrototype(cx, &StringClass);
3071 7022 : if (!proto || !proto->asString().init(cx, cx->runtime->emptyString))
3072 0 : return NULL;
3073 :
3074 : /* Now create the String function. */
3075 : JSFunction *ctor = global->createConstructor(cx, js_String, &StringClass,
3076 7022 : CLASS_ATOM(cx, String), 1);
3077 7022 : if (!ctor)
3078 0 : return NULL;
3079 :
3080 7022 : if (!LinkConstructorAndPrototype(cx, ctor, proto))
3081 0 : return NULL;
3082 :
3083 14044 : if (!DefinePropertiesAndBrand(cx, proto, NULL, string_methods) ||
3084 7022 : !DefinePropertiesAndBrand(cx, ctor, NULL, string_static_methods))
3085 : {
3086 0 : return NULL;
3087 : }
3088 :
3089 : /* Capture normal data properties pregenerated for String objects. */
3090 7022 : TypeObject *type = proto->getNewType(cx);
3091 7022 : if (!type)
3092 0 : return NULL;
3093 7022 : AddTypeProperty(cx, type, "length", Type::Int32Type());
3094 :
3095 7022 : if (!DefineConstructorAndPrototype(cx, global, JSProto_String, ctor, proto))
3096 0 : return NULL;
3097 :
3098 : /*
3099 : * Define escape/unescape, the URI encode/decode functions, and maybe
3100 : * uneval on the global object.
3101 : */
3102 7022 : if (!JS_DefineFunctions(cx, global, string_functions))
3103 0 : return NULL;
3104 :
3105 7022 : return proto;
3106 : }
3107 :
3108 : JSFixedString *
3109 14485956 : js_NewString(JSContext *cx, jschar *chars, size_t length)
3110 : {
3111 14485956 : JSFixedString *s = JSFixedString::new_(cx, chars, length);
3112 14485956 : if (s)
3113 14485956 : Probes::createString(cx, s, length);
3114 14485956 : return s;
3115 : }
3116 :
3117 : static JSInlineString *
3118 146429 : NewShortString(JSContext *cx, const char *chars, size_t length)
3119 : {
3120 146429 : JS_ASSERT(JSShortString::lengthFits(length));
3121 146429 : JSInlineString *str = JSInlineString::lengthFits(length)
3122 : ? JSInlineString::new_(cx)
3123 146429 : : JSShortString::new_(cx);
3124 146429 : if (!str)
3125 0 : return NULL;
3126 :
3127 146429 : jschar *storage = str->init(length);
3128 146429 : if (js_CStringsAreUTF8) {
3129 : #ifdef DEBUG
3130 0 : size_t oldLength = length;
3131 : #endif
3132 0 : if (!InflateUTF8StringToBuffer(cx, chars, length, storage, &length))
3133 0 : return NULL;
3134 0 : JS_ASSERT(length <= oldLength);
3135 0 : storage[length] = 0;
3136 0 : str->resetLength(length);
3137 : } else {
3138 146429 : size_t n = length;
3139 146429 : jschar *p = storage;
3140 1115211 : while (n--)
3141 822353 : *p++ = (unsigned char)*chars++;
3142 146429 : *p = 0;
3143 : }
3144 146429 : Probes::createString(cx, str, length);
3145 146429 : return str;
3146 : }
3147 :
3148 : JSLinearString *
3149 2650466 : js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
3150 : {
3151 2650466 : if (length == 0)
3152 48157 : return cx->runtime->emptyString;
3153 :
3154 2602309 : JSLinearString *base = baseArg->ensureLinear(cx);
3155 2602309 : if (!base)
3156 0 : return NULL;
3157 :
3158 2602309 : if (start == 0 && length == base->length())
3159 130245 : return base;
3160 :
3161 2472064 : const jschar *chars = base->chars() + start;
3162 :
3163 2472064 : if (JSLinearString *staticStr = cx->runtime->staticStrings.lookup(chars, length))
3164 1471014 : return staticStr;
3165 :
3166 1001050 : JSLinearString *s = JSDependentString::new_(cx, base, chars, length);
3167 1001050 : Probes::createString(cx, s, length);
3168 1001050 : return s;
3169 : }
3170 :
3171 : JSFixedString *
3172 107692879 : js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n)
3173 : {
3174 107692879 : if (JSShortString::lengthFits(n))
3175 99631798 : return NewShortString(cx, s, n);
3176 :
3177 8061081 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
3178 8061081 : if (!news)
3179 0 : return NULL;
3180 8061081 : js_strncpy(news, s, n);
3181 8061081 : news[n] = 0;
3182 8061081 : JSFixedString *str = js_NewString(cx, news, n);
3183 8061081 : if (!str)
3184 0 : cx->free_(news);
3185 8061081 : return str;
3186 : }
3187 :
3188 : JSFixedString *
3189 318968 : js_NewStringCopyN(JSContext *cx, const char *s, size_t n)
3190 : {
3191 318968 : if (JSShortString::lengthFits(n))
3192 146429 : return NewShortString(cx, s, n);
3193 :
3194 172539 : jschar *chars = InflateString(cx, s, &n);
3195 172539 : if (!chars)
3196 0 : return NULL;
3197 172539 : JSFixedString *str = js_NewString(cx, chars, n);
3198 172539 : if (!str)
3199 0 : cx->free_(chars);
3200 172539 : return str;
3201 : }
3202 :
3203 : JSFixedString *
3204 31163 : js_NewStringCopyZ(JSContext *cx, const jschar *s)
3205 : {
3206 31163 : size_t n = js_strlen(s);
3207 31163 : if (JSShortString::lengthFits(n))
3208 18573 : return NewShortString(cx, s, n);
3209 :
3210 12590 : size_t m = (n + 1) * sizeof(jschar);
3211 12590 : jschar *news = (jschar *) cx->malloc_(m);
3212 12590 : if (!news)
3213 0 : return NULL;
3214 12590 : js_memcpy(news, s, m);
3215 12590 : JSFixedString *str = js_NewString(cx, news, n);
3216 12590 : if (!str)
3217 0 : cx->free_(news);
3218 12590 : return str;
3219 : }
3220 :
3221 : JSFixedString *
3222 298606 : js_NewStringCopyZ(JSContext *cx, const char *s)
3223 : {
3224 298606 : return js_NewStringCopyN(cx, s, strlen(s));
3225 : }
3226 :
3227 : const char *
3228 22021 : js_ValueToPrintable(JSContext *cx, const Value &v, JSAutoByteString *bytes, bool asSource)
3229 : {
3230 : JSString *str;
3231 :
3232 22021 : str = (asSource ? js_ValueToSource : ToString)(cx, v);
3233 22021 : if (!str)
3234 0 : return NULL;
3235 22021 : str = js_QuoteString(cx, str, 0);
3236 22021 : if (!str)
3237 0 : return NULL;
3238 22021 : return bytes->encode(cx, str);
3239 : }
3240 :
3241 : JSString *
3242 7796098 : js::ToStringSlow(JSContext *cx, const Value &arg)
3243 : {
3244 : /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
3245 7796098 : JS_ASSERT(!arg.isString());
3246 :
3247 7796098 : Value v = arg;
3248 7796098 : if (!ToPrimitive(cx, JSTYPE_STRING, &v))
3249 5430 : return NULL;
3250 :
3251 : JSString *str;
3252 7790668 : if (v.isString()) {
3253 74986 : str = v.toString();
3254 7715682 : } else if (v.isInt32()) {
3255 4591134 : str = js_IntToString(cx, v.toInt32());
3256 3124548 : } else if (v.isDouble()) {
3257 2887118 : str = js_NumberToString(cx, v.toDouble());
3258 237430 : } else if (v.isBoolean()) {
3259 121861 : str = js_BooleanToString(cx, v.toBoolean());
3260 115569 : } else if (v.isNull()) {
3261 38205 : str = cx->runtime->atomState.nullAtom;
3262 : } else {
3263 77364 : str = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
3264 : }
3265 7790668 : return str;
3266 : }
3267 :
3268 : JS_FRIEND_API(JSString *)
3269 10881 : js_ValueToSource(JSContext *cx, const Value &v)
3270 : {
3271 10881 : JS_CHECK_RECURSION(cx, return NULL);
3272 :
3273 10881 : if (v.isUndefined())
3274 386 : return cx->runtime->atomState.void0Atom;
3275 10495 : if (v.isString())
3276 2260 : return js_QuoteString(cx, v.toString(), '"');
3277 8235 : if (v.isPrimitive()) {
3278 : /* Special case to preserve negative zero, _contra_ toString. */
3279 3752 : if (v.isDouble() && JSDOUBLE_IS_NEGZERO(v.toDouble())) {
3280 : /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
3281 : static const jschar js_negzero_ucNstr[] = {'-', '0'};
3282 :
3283 9 : return js_NewStringCopyN(cx, js_negzero_ucNstr, 2);
3284 : }
3285 3743 : return ToString(cx, v);
3286 : }
3287 :
3288 4483 : Value rval = NullValue();
3289 : Value fval;
3290 4483 : jsid id = ATOM_TO_JSID(cx->runtime->atomState.toSourceAtom);
3291 4483 : if (!js_GetMethod(cx, &v.toObject(), id, JSGET_NO_METHOD_BARRIER, &fval))
3292 0 : return NULL;
3293 4483 : if (js_IsCallable(fval)) {
3294 4483 : if (!Invoke(cx, v, fval, 0, NULL, &rval))
3295 18 : return NULL;
3296 : }
3297 :
3298 4465 : return ToString(cx, rval);
3299 : }
3300 :
3301 : namespace js {
3302 :
3303 : bool
3304 4124011 : EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result)
3305 : {
3306 4124011 : if (str1 == str2) {
3307 266439 : *result = true;
3308 266439 : return true;
3309 : }
3310 :
3311 3857572 : size_t length1 = str1->length();
3312 3857572 : if (length1 != str2->length()) {
3313 1348800 : *result = false;
3314 1348800 : return true;
3315 : }
3316 :
3317 2508772 : JSLinearString *linear1 = str1->ensureLinear(cx);
3318 2508772 : if (!linear1)
3319 0 : return false;
3320 2508772 : JSLinearString *linear2 = str2->ensureLinear(cx);
3321 2508772 : if (!linear2)
3322 0 : return false;
3323 :
3324 2508772 : *result = PodEqual(linear1->chars(), linear2->chars(), length1);
3325 2508772 : return true;
3326 : }
3327 :
3328 : bool
3329 239094 : EqualStrings(JSLinearString *str1, JSLinearString *str2)
3330 : {
3331 239094 : if (str1 == str2)
3332 433 : return true;
3333 :
3334 238661 : size_t length1 = str1->length();
3335 238661 : if (length1 != str2->length())
3336 133380 : return false;
3337 :
3338 105281 : return PodEqual(str1->chars(), str2->chars(), length1);
3339 : }
3340 :
3341 : } /* namespace js */
3342 :
3343 : namespace js {
3344 :
3345 : static bool
3346 334574 : CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
3347 : {
3348 334574 : JS_ASSERT(str1);
3349 334574 : JS_ASSERT(str2);
3350 :
3351 334574 : if (str1 == str2) {
3352 2557 : *result = 0;
3353 2557 : return true;
3354 : }
3355 :
3356 332017 : const jschar *s1 = str1->getChars(cx);
3357 332017 : if (!s1)
3358 0 : return false;
3359 :
3360 332017 : const jschar *s2 = str2->getChars(cx);
3361 332017 : if (!s2)
3362 0 : return false;
3363 :
3364 332017 : return CompareChars(s1, str1->length(), s2, str2->length(), result);
3365 : }
3366 :
3367 : bool
3368 334574 : CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
3369 : {
3370 334574 : return CompareStringsImpl(cx, str1, str2, result);
3371 : }
3372 :
3373 : } /* namespace js */
3374 :
3375 : namespace js {
3376 :
3377 : bool
3378 295540 : StringEqualsAscii(JSLinearString *str, const char *asciiBytes)
3379 : {
3380 295540 : size_t length = strlen(asciiBytes);
3381 : #ifdef DEBUG
3382 1646953 : for (size_t i = 0; i != length; ++i)
3383 1351413 : JS_ASSERT(unsigned(asciiBytes[i]) <= 127);
3384 : #endif
3385 295540 : if (length != str->length())
3386 188596 : return false;
3387 106944 : const jschar *chars = str->chars();
3388 724974 : for (size_t i = 0; i != length; ++i) {
3389 618041 : if (unsigned(asciiBytes[i]) != unsigned(chars[i]))
3390 11 : return false;
3391 : }
3392 106933 : return true;
3393 : }
3394 :
3395 : } /* namespacejs */
3396 :
3397 : size_t
3398 96760 : js_strlen(const jschar *s)
3399 : {
3400 : const jschar *t;
3401 :
3402 1860563 : for (t = s; *t != 0; t++)
3403 1763803 : continue;
3404 96760 : return (size_t)(t - s);
3405 : }
3406 :
3407 : jschar *
3408 27130 : js_strchr(const jschar *s, jschar c)
3409 : {
3410 1091641 : while (*s != 0) {
3411 1062292 : if (*s == c)
3412 24911 : return (jschar *)s;
3413 1037381 : s++;
3414 : }
3415 2219 : return NULL;
3416 : }
3417 :
3418 : jschar *
3419 551916 : js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
3420 : {
3421 4417333 : while (s < limit) {
3422 3317988 : if (*s == c)
3423 4487 : return (jschar *)s;
3424 3313501 : s++;
3425 : }
3426 547429 : return NULL;
3427 : }
3428 :
3429 : namespace js {
3430 :
3431 : jschar *
3432 3022415 : InflateString(JSContext *cx, const char *bytes, size_t *lengthp, FlationCoding fc)
3433 : {
3434 : size_t nchars;
3435 : jschar *chars;
3436 3022415 : size_t nbytes = *lengthp;
3437 :
3438 3022415 : if (js_CStringsAreUTF8 || fc == CESU8Encoding) {
3439 13184 : if (!InflateUTF8StringToBuffer(cx, bytes, nbytes, NULL, &nchars, fc))
3440 0 : goto bad;
3441 13184 : chars = (jschar *) cx->malloc_((nchars + 1) * sizeof (jschar));
3442 13184 : if (!chars)
3443 0 : goto bad;
3444 13184 : JS_ALWAYS_TRUE(InflateUTF8StringToBuffer(cx, bytes, nbytes, chars, &nchars, fc));
3445 : } else {
3446 3009231 : nchars = nbytes;
3447 3009231 : chars = (jschar *) cx->malloc_((nchars + 1) * sizeof(jschar));
3448 3009231 : if (!chars)
3449 0 : goto bad;
3450 1038547974 : for (size_t i = 0; i < nchars; i++)
3451 1035538743 : chars[i] = (unsigned char) bytes[i];
3452 : }
3453 3022415 : *lengthp = nchars;
3454 3022415 : chars[nchars] = 0;
3455 3022415 : return chars;
3456 :
3457 : bad:
3458 : /*
3459 : * For compatibility with callers of JS_DecodeBytes we must zero lengthp
3460 : * on errors.
3461 : */
3462 0 : *lengthp = 0;
3463 0 : return NULL;
3464 : }
3465 :
3466 : /*
3467 : * May be called with null cx.
3468 : */
3469 : char *
3470 837044 : DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
3471 : {
3472 : size_t nbytes, i;
3473 : char *bytes;
3474 :
3475 837044 : if (js_CStringsAreUTF8) {
3476 20 : nbytes = GetDeflatedStringLength(cx, chars, nchars);
3477 20 : if (nbytes == (size_t) -1)
3478 0 : return NULL;
3479 20 : bytes = (char *) (cx ? cx->malloc_(nbytes + 1) : OffTheBooks::malloc_(nbytes + 1));
3480 20 : if (!bytes)
3481 0 : return NULL;
3482 20 : JS_ALWAYS_TRUE(DeflateStringToBuffer(cx, chars, nchars, bytes, &nbytes));
3483 : } else {
3484 837024 : nbytes = nchars;
3485 837024 : bytes = (char *) (cx ? cx->malloc_(nbytes + 1) : OffTheBooks::malloc_(nbytes + 1));
3486 837024 : if (!bytes)
3487 0 : return NULL;
3488 46627627 : for (i = 0; i < nbytes; i++)
3489 45790603 : bytes[i] = (char) chars[i];
3490 : }
3491 837044 : bytes[nbytes] = 0;
3492 837044 : return bytes;
3493 : }
3494 :
3495 : size_t
3496 2434611 : GetDeflatedStringLength(JSContext *cx, const jschar *chars, size_t nchars)
3497 : {
3498 2434611 : if (!js_CStringsAreUTF8)
3499 2434590 : return nchars;
3500 :
3501 21 : return GetDeflatedUTF8StringLength(cx, chars, nchars);
3502 : }
3503 :
3504 : /*
3505 : * May be called with null cx through public API, see below.
3506 : */
3507 : size_t
3508 540 : GetDeflatedUTF8StringLength(JSContext *cx, const jschar *chars,
3509 : size_t nchars, FlationCoding fc)
3510 : {
3511 : size_t nbytes;
3512 : const jschar *end;
3513 : unsigned c, c2;
3514 : char buffer[10];
3515 540 : bool useCESU8 = fc == CESU8Encoding;
3516 :
3517 540 : nbytes = nchars;
3518 217483 : for (end = chars + nchars; chars != end; chars++) {
3519 216943 : c = *chars;
3520 216943 : if (c < 0x80)
3521 216920 : continue;
3522 23 : if (0xD800 <= c && c <= 0xDFFF && !useCESU8) {
3523 : /* Surrogate pair. */
3524 1 : chars++;
3525 :
3526 : /* nbytes sets 1 length since this is surrogate pair. */
3527 1 : nbytes--;
3528 1 : if (c >= 0xDC00 || chars == end)
3529 : goto bad_surrogate;
3530 1 : c2 = *chars;
3531 1 : if (c2 < 0xDC00 || c2 > 0xDFFF)
3532 : goto bad_surrogate;
3533 1 : c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3534 : }
3535 23 : c >>= 11;
3536 23 : nbytes++;
3537 48 : while (c) {
3538 2 : c >>= 5;
3539 2 : nbytes++;
3540 : }
3541 : }
3542 540 : return nbytes;
3543 :
3544 : bad_surrogate:
3545 0 : if (cx) {
3546 0 : JS_snprintf(buffer, 10, "0x%x", c);
3547 : JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
3548 0 : NULL, JSMSG_BAD_SURROGATE_CHAR, buffer);
3549 : }
3550 0 : return (size_t) -1;
3551 : }
3552 :
3553 : bool
3554 2426778 : DeflateStringToBuffer(JSContext *cx, const jschar *src, size_t srclen,
3555 : char *dst, size_t *dstlenp)
3556 : {
3557 : size_t dstlen, i;
3558 :
3559 2426778 : dstlen = *dstlenp;
3560 2426778 : if (!js_CStringsAreUTF8) {
3561 2426757 : if (srclen > dstlen) {
3562 0 : for (i = 0; i < dstlen; i++)
3563 0 : dst[i] = (char) src[i];
3564 0 : if (cx) {
3565 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3566 0 : JSMSG_BUFFER_TOO_SMALL);
3567 : }
3568 0 : return JS_FALSE;
3569 : }
3570 247952340 : for (i = 0; i < srclen; i++)
3571 245525583 : dst[i] = (char) src[i];
3572 2426757 : *dstlenp = srclen;
3573 2426757 : return JS_TRUE;
3574 : }
3575 :
3576 21 : return DeflateStringToUTF8Buffer(cx, src, srclen, dst, dstlenp);
3577 : }
3578 :
3579 : bool
3580 282 : DeflateStringToUTF8Buffer(JSContext *cx, const jschar *src, size_t srclen,
3581 : char *dst, size_t *dstlenp, FlationCoding fc)
3582 : {
3583 : size_t i, utf8Len;
3584 : jschar c, c2;
3585 : uint32_t v;
3586 : uint8_t utf8buf[6];
3587 :
3588 282 : bool useCESU8 = fc == CESU8Encoding;
3589 282 : size_t dstlen = *dstlenp;
3590 282 : size_t origDstlen = dstlen;
3591 :
3592 109304 : while (srclen) {
3593 108740 : c = *src++;
3594 108740 : srclen--;
3595 108740 : if ((c >= 0xDC00) && (c <= 0xDFFF) && !useCESU8)
3596 0 : goto badSurrogate;
3597 108740 : if (c < 0xD800 || c > 0xDBFF || useCESU8) {
3598 108739 : v = c;
3599 : } else {
3600 1 : if (srclen < 1)
3601 0 : goto badSurrogate;
3602 1 : c2 = *src;
3603 1 : if ((c2 < 0xDC00) || (c2 > 0xDFFF))
3604 : goto badSurrogate;
3605 1 : src++;
3606 1 : srclen--;
3607 1 : v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3608 : }
3609 108740 : if (v < 0x0080) {
3610 : /* no encoding necessary - performance hack */
3611 108728 : if (dstlen == 0)
3612 0 : goto bufferTooSmall;
3613 108728 : *dst++ = (char) v;
3614 108728 : utf8Len = 1;
3615 : } else {
3616 12 : utf8Len = js_OneUcs4ToUtf8Char(utf8buf, v);
3617 12 : if (utf8Len > dstlen)
3618 0 : goto bufferTooSmall;
3619 38 : for (i = 0; i < utf8Len; i++)
3620 26 : *dst++ = (char) utf8buf[i];
3621 : }
3622 108740 : dstlen -= utf8Len;
3623 : }
3624 282 : *dstlenp = (origDstlen - dstlen);
3625 282 : return JS_TRUE;
3626 :
3627 : badSurrogate:
3628 0 : *dstlenp = (origDstlen - dstlen);
3629 : /* Delegate error reporting to the measurement function. */
3630 0 : if (cx)
3631 0 : GetDeflatedStringLength(cx, src - 1, srclen + 1);
3632 0 : return JS_FALSE;
3633 :
3634 : bufferTooSmall:
3635 0 : *dstlenp = (origDstlen - dstlen);
3636 0 : if (cx) {
3637 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3638 0 : JSMSG_BUFFER_TOO_SMALL);
3639 : }
3640 0 : return JS_FALSE;
3641 : }
3642 :
3643 : bool
3644 33811732 : InflateStringToBuffer(JSContext *cx, const char *src, size_t srclen,
3645 : jschar *dst, size_t *dstlenp)
3646 : {
3647 : size_t dstlen, i;
3648 :
3649 33811732 : if (js_CStringsAreUTF8)
3650 53202 : return InflateUTF8StringToBuffer(cx, src, srclen, dst, dstlenp);
3651 :
3652 33758530 : if (dst) {
3653 33758530 : dstlen = *dstlenp;
3654 33758530 : if (srclen > dstlen) {
3655 0 : for (i = 0; i < dstlen; i++)
3656 0 : dst[i] = (unsigned char) src[i];
3657 0 : if (cx) {
3658 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3659 0 : JSMSG_BUFFER_TOO_SMALL);
3660 : }
3661 0 : return JS_FALSE;
3662 : }
3663 298514436 : for (i = 0; i < srclen; i++)
3664 264755906 : dst[i] = (unsigned char) src[i];
3665 : }
3666 33758530 : *dstlenp = srclen;
3667 33758530 : return JS_TRUE;
3668 : }
3669 :
3670 : bool
3671 122322 : InflateUTF8StringToBuffer(JSContext *cx, const char *src, size_t srclen,
3672 : jschar *dst, size_t *dstlenp, FlationCoding fc)
3673 : {
3674 : size_t dstlen, origDstlen, offset, j, n;
3675 : uint32_t v;
3676 :
3677 122322 : dstlen = dst ? *dstlenp : (size_t) -1;
3678 122322 : origDstlen = dstlen;
3679 122322 : offset = 0;
3680 122322 : bool useCESU8 = fc == CESU8Encoding;
3681 :
3682 341397162 : while (srclen) {
3683 341152519 : v = (uint8_t) *src;
3684 341152519 : n = 1;
3685 341152519 : if (v & 0x80) {
3686 3878 : while (v & (0x80 >> n))
3687 1812 : n++;
3688 1033 : if (n > srclen)
3689 0 : goto bufferTooSmall;
3690 1033 : if (n == 1 || n > 4)
3691 : goto badCharacter;
3692 2844 : for (j = 1; j < n; j++) {
3693 1812 : if ((src[j] & 0xC0) != 0x80)
3694 0 : goto badCharacter;
3695 : }
3696 1032 : v = Utf8ToOneUcs4Char((uint8_t *)src, n);
3697 1032 : if (v >= 0x10000 && !useCESU8) {
3698 0 : v -= 0x10000;
3699 0 : if (v > 0xFFFFF || dstlen < 2) {
3700 0 : *dstlenp = (origDstlen - dstlen);
3701 0 : if (cx) {
3702 : char buffer[10];
3703 0 : JS_snprintf(buffer, 10, "0x%x", v + 0x10000);
3704 : JS_ReportErrorFlagsAndNumber(cx,
3705 : JSREPORT_ERROR,
3706 : js_GetErrorMessage, NULL,
3707 : JSMSG_UTF8_CHAR_TOO_LARGE,
3708 0 : buffer);
3709 : }
3710 0 : return JS_FALSE;
3711 : }
3712 0 : if (dst) {
3713 0 : *dst++ = (jschar)((v >> 10) + 0xD800);
3714 0 : v = (jschar)((v & 0x3FF) + 0xDC00);
3715 : }
3716 0 : dstlen--;
3717 : }
3718 : }
3719 341152518 : if (!dstlen)
3720 0 : goto bufferTooSmall;
3721 341152518 : if (dst)
3722 340876355 : *dst++ = (jschar) v;
3723 341152518 : dstlen--;
3724 341152518 : offset += n;
3725 341152518 : src += n;
3726 341152518 : srclen -= n;
3727 : }
3728 122321 : *dstlenp = (origDstlen - dstlen);
3729 122321 : return JS_TRUE;
3730 :
3731 : badCharacter:
3732 1 : *dstlenp = (origDstlen - dstlen);
3733 1 : if (cx) {
3734 : char buffer[10];
3735 1 : JS_snprintf(buffer, 10, "%d", offset);
3736 : JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR,
3737 : js_GetErrorMessage, NULL,
3738 : JSMSG_MALFORMED_UTF8_CHAR,
3739 1 : buffer);
3740 : }
3741 1 : return JS_FALSE;
3742 :
3743 : bufferTooSmall:
3744 0 : *dstlenp = (origDstlen - dstlen);
3745 0 : if (cx) {
3746 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3747 0 : JSMSG_BUFFER_TOO_SMALL);
3748 : }
3749 0 : return JS_FALSE;
3750 : }
3751 :
3752 : } /* namepsace js */
3753 :
3754 : const jschar js_uriReservedPlusPound_ucstr[] =
3755 : {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#', 0};
3756 : const jschar js_uriUnescaped_ucstr[] =
3757 : {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
3758 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3759 : 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3760 : 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3761 : 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3762 : '-', '_', '.', '!', '~', '*', '\'', '(', ')', 0};
3763 :
3764 : #define ____ false
3765 :
3766 : /*
3767 : * Identifier start chars:
3768 : * - 36: $
3769 : * - 65..90: A..Z
3770 : * - 95: _
3771 : * - 97..122: a..z
3772 : */
3773 : const bool js_isidstart[] = {
3774 : /* 0 1 2 3 4 5 6 7 8 9 */
3775 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3776 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3777 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3778 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
3779 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3780 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3781 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
3782 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
3783 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
3784 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
3785 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
3786 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
3787 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
3788 : };
3789 :
3790 : /*
3791 : * Identifier chars:
3792 : * - 36: $
3793 : * - 48..57: 0..9
3794 : * - 65..90: A..Z
3795 : * - 95: _
3796 : * - 97..122: a..z
3797 : */
3798 : const bool js_isident[] = {
3799 : /* 0 1 2 3 4 5 6 7 8 9 */
3800 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3801 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3802 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3803 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
3804 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
3805 : /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
3806 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
3807 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
3808 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
3809 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
3810 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
3811 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
3812 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
3813 : };
3814 :
3815 : /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
3816 : const bool js_isspace[] = {
3817 : /* 0 1 2 3 4 5 6 7 8 9 */
3818 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
3819 : /* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
3820 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3821 : /* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
3822 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3823 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3824 : /* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3825 : /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3826 : /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3827 : /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3828 : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3829 : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3830 : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
3831 : };
3832 :
3833 : #undef ____
3834 :
3835 : #define URI_CHUNK 64U
3836 :
3837 : static inline bool
3838 3065 : TransferBufferToString(JSContext *cx, StringBuffer &sb, Value *rval)
3839 : {
3840 3065 : JSString *str = sb.finishString();
3841 3065 : if (!str)
3842 0 : return false;
3843 3065 : rval->setString(str);
3844 3065 : return true;
3845 : }
3846 :
3847 : /*
3848 : * ECMA 3, 15.1.3 URI Handling Function Properties
3849 : *
3850 : * The following are implementations of the algorithms
3851 : * given in the ECMA specification for the hidden functions
3852 : * 'Encode' and 'Decode'.
3853 : */
3854 : static JSBool
3855 1547 : Encode(JSContext *cx, JSString *str, const jschar *unescapedSet,
3856 : const jschar *unescapedSet2, Value *rval)
3857 : {
3858 : static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
3859 :
3860 1547 : size_t length = str->length();
3861 1547 : const jschar *chars = str->getChars(cx);
3862 1547 : if (!chars)
3863 0 : return JS_FALSE;
3864 :
3865 1547 : if (length == 0) {
3866 100 : rval->setString(cx->runtime->emptyString);
3867 100 : return JS_TRUE;
3868 : }
3869 :
3870 2894 : StringBuffer sb(cx);
3871 : jschar hexBuf[4];
3872 1447 : hexBuf[0] = '%';
3873 1447 : hexBuf[3] = 0;
3874 28507 : for (size_t k = 0; k < length; k++) {
3875 27060 : jschar c = chars[k];
3876 27104 : if (js_strchr(unescapedSet, c) ||
3877 44 : (unescapedSet2 && js_strchr(unescapedSet2, c))) {
3878 24910 : if (!sb.append(c))
3879 0 : return JS_FALSE;
3880 : } else {
3881 2150 : if ((c >= 0xDC00) && (c <= 0xDFFF)) {
3882 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3883 0 : JSMSG_BAD_URI, NULL);
3884 0 : return JS_FALSE;
3885 : }
3886 : uint32_t v;
3887 2150 : if (c < 0xD800 || c > 0xDBFF) {
3888 2150 : v = c;
3889 : } else {
3890 0 : k++;
3891 0 : if (k == length) {
3892 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3893 0 : JSMSG_BAD_URI, NULL);
3894 0 : return JS_FALSE;
3895 : }
3896 0 : jschar c2 = chars[k];
3897 0 : if ((c2 < 0xDC00) || (c2 > 0xDFFF)) {
3898 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3899 0 : JSMSG_BAD_URI, NULL);
3900 0 : return JS_FALSE;
3901 : }
3902 0 : v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3903 : }
3904 : uint8_t utf8buf[4];
3905 2150 : size_t L = js_OneUcs4ToUtf8Char(utf8buf, v);
3906 4426 : for (size_t j = 0; j < L; j++) {
3907 2276 : hexBuf[1] = HexDigits[utf8buf[j] >> 4];
3908 2276 : hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
3909 2276 : if (!sb.append(hexBuf, 3))
3910 0 : return JS_FALSE;
3911 : }
3912 : }
3913 : }
3914 :
3915 1447 : return TransferBufferToString(cx, sb, rval);
3916 : }
3917 :
3918 : static JSBool
3919 1632 : Decode(JSContext *cx, JSString *str, const jschar *reservedSet, Value *rval)
3920 : {
3921 1632 : size_t length = str->length();
3922 1632 : const jschar *chars = str->getChars(cx);
3923 1632 : if (!chars)
3924 0 : return JS_FALSE;
3925 :
3926 1632 : if (length == 0) {
3927 3 : rval->setString(cx->runtime->emptyString);
3928 3 : return JS_TRUE;
3929 : }
3930 :
3931 3258 : StringBuffer sb(cx);
3932 27849 : for (size_t k = 0; k < length; k++) {
3933 26231 : jschar c = chars[k];
3934 26231 : if (c == '%') {
3935 37 : size_t start = k;
3936 37 : if ((k + 2) >= length)
3937 9 : goto report_bad_uri;
3938 28 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
3939 : goto report_bad_uri;
3940 26 : uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
3941 26 : k += 2;
3942 26 : if (!(B & 0x80)) {
3943 26 : c = (jschar)B;
3944 : } else {
3945 0 : int n = 1;
3946 0 : while (B & (0x80 >> n))
3947 0 : n++;
3948 0 : if (n == 1 || n > 4)
3949 : goto report_bad_uri;
3950 : uint8_t octets[4];
3951 0 : octets[0] = (uint8_t)B;
3952 0 : if (k + 3 * (n - 1) >= length)
3953 0 : goto report_bad_uri;
3954 0 : for (int j = 1; j < n; j++) {
3955 0 : k++;
3956 0 : if (chars[k] != '%')
3957 0 : goto report_bad_uri;
3958 0 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
3959 : goto report_bad_uri;
3960 0 : B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
3961 0 : if ((B & 0xC0) != 0x80)
3962 0 : goto report_bad_uri;
3963 0 : k += 2;
3964 0 : octets[j] = (char)B;
3965 : }
3966 0 : uint32_t v = Utf8ToOneUcs4Char(octets, n);
3967 0 : if (v >= 0x10000) {
3968 0 : v -= 0x10000;
3969 0 : if (v > 0xFFFFF)
3970 0 : goto report_bad_uri;
3971 0 : c = (jschar)((v & 0x3FF) + 0xDC00);
3972 0 : jschar H = (jschar)((v >> 10) + 0xD800);
3973 0 : if (!sb.append(H))
3974 0 : return JS_FALSE;
3975 : } else {
3976 0 : c = (jschar)v;
3977 : }
3978 : }
3979 26 : if (js_strchr(reservedSet, c)) {
3980 1 : if (!sb.append(chars + start, k - start + 1))
3981 0 : return JS_FALSE;
3982 : } else {
3983 25 : if (!sb.append(c))
3984 0 : return JS_FALSE;
3985 : }
3986 : } else {
3987 26194 : if (!sb.append(c))
3988 0 : return JS_FALSE;
3989 : }
3990 : }
3991 :
3992 1618 : return TransferBufferToString(cx, sb, rval);
3993 :
3994 : report_bad_uri:
3995 11 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_URI);
3996 : /* FALL THROUGH */
3997 :
3998 11 : return JS_FALSE;
3999 : }
4000 :
4001 : static JSBool
4002 1144 : str_decodeURI(JSContext *cx, unsigned argc, Value *vp)
4003 : {
4004 1144 : CallArgs args = CallArgsFromVp(argc, vp);
4005 1144 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4006 1144 : if (!str)
4007 0 : return false;
4008 :
4009 : Value result;
4010 1144 : if (!Decode(cx, str, js_uriReservedPlusPound_ucstr, &result))
4011 9 : return false;
4012 :
4013 1135 : args.rval() = result;
4014 1135 : return true;
4015 : }
4016 :
4017 : static JSBool
4018 488 : str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
4019 : {
4020 488 : CallArgs args = CallArgsFromVp(argc, vp);
4021 488 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4022 488 : if (!str)
4023 0 : return false;
4024 :
4025 : Value result;
4026 488 : if (!Decode(cx, str, js_empty_ucstr, &result))
4027 2 : return false;
4028 :
4029 486 : args.rval() = result;
4030 486 : return true;
4031 : }
4032 :
4033 : static JSBool
4034 1 : str_encodeURI(JSContext *cx, unsigned argc, Value *vp)
4035 : {
4036 1 : CallArgs args = CallArgsFromVp(argc, vp);
4037 1 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4038 1 : if (!str)
4039 0 : return false;
4040 :
4041 : Value result;
4042 1 : if (!Encode(cx, str, js_uriReservedPlusPound_ucstr, js_uriUnescaped_ucstr, &result))
4043 0 : return false;
4044 :
4045 1 : args.rval() = result;
4046 1 : return true;
4047 : }
4048 :
4049 : static JSBool
4050 1546 : str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
4051 : {
4052 1546 : CallArgs args = CallArgsFromVp(argc, vp);
4053 1546 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4054 1546 : if (!str)
4055 0 : return false;
4056 :
4057 : Value result;
4058 1546 : if (!Encode(cx, str, js_uriUnescaped_ucstr, NULL, &result))
4059 0 : return false;
4060 :
4061 1546 : args.rval() = result;
4062 1546 : return true;
4063 : }
4064 :
4065 : /*
4066 : * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
4067 : * least 4 bytes long. Return the number of UTF-8 bytes of data written.
4068 : */
4069 : int
4070 2162 : js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char)
4071 : {
4072 2162 : int utf8Length = 1;
4073 :
4074 2162 : JS_ASSERT(ucs4Char <= 0x10FFFF);
4075 2162 : if (ucs4Char < 0x80) {
4076 2087 : *utf8Buffer = (uint8_t)ucs4Char;
4077 : } else {
4078 : int i;
4079 75 : uint32_t a = ucs4Char >> 11;
4080 75 : utf8Length = 2;
4081 215 : while (a) {
4082 65 : a >>= 5;
4083 65 : utf8Length++;
4084 : }
4085 75 : i = utf8Length;
4086 290 : while (--i) {
4087 140 : utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
4088 140 : ucs4Char >>= 6;
4089 : }
4090 75 : *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
4091 : }
4092 2162 : return utf8Length;
4093 : }
4094 :
4095 : /*
4096 : * Convert a utf8 character sequence into a UCS-4 character and return that
4097 : * character. It is assumed that the caller already checked that the sequence
4098 : * is valid.
4099 : */
4100 : static uint32_t
4101 1032 : Utf8ToOneUcs4Char(const uint8_t *utf8Buffer, int utf8Length)
4102 : {
4103 1032 : JS_ASSERT(1 <= utf8Length && utf8Length <= 4);
4104 :
4105 1032 : if (utf8Length == 1) {
4106 0 : JS_ASSERT(!(*utf8Buffer & 0x80));
4107 0 : return *utf8Buffer;
4108 : }
4109 :
4110 : /* from Unicode 3.1, non-shortest form is illegal */
4111 : static const uint32_t minucs4Table[] = { 0x80, 0x800, 0x10000 };
4112 :
4113 0 : JS_ASSERT((*utf8Buffer & (0x100 - (1 << (7 - utf8Length)))) ==
4114 1032 : (0x100 - (1 << (8 - utf8Length))));
4115 1032 : uint32_t ucs4Char = *utf8Buffer++ & ((1 << (7 - utf8Length)) - 1);
4116 1032 : uint32_t minucs4Char = minucs4Table[utf8Length - 2];
4117 3876 : while (--utf8Length) {
4118 1812 : JS_ASSERT((*utf8Buffer & 0xC0) == 0x80);
4119 1812 : ucs4Char = (ucs4Char << 6) | (*utf8Buffer++ & 0x3F);
4120 : }
4121 :
4122 1032 : if (JS_UNLIKELY(ucs4Char < minucs4Char || (ucs4Char >= 0xD800 && ucs4Char <= 0xDFFF)))
4123 0 : return INVALID_UTF8;
4124 :
4125 1032 : return ucs4Char;
4126 : }
4127 :
4128 : namespace js {
4129 :
4130 : size_t
4131 436519 : PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str, uint32_t quote)
4132 : {
4133 : enum {
4134 : STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
4135 : } state;
4136 :
4137 436519 : JS_ASSERT(quote == 0 || quote == '\'' || quote == '"');
4138 436519 : JS_ASSERT_IF(!buffer, bufferSize == 0);
4139 436519 : JS_ASSERT_IF(fp, !buffer);
4140 :
4141 436519 : if (bufferSize == 0)
4142 0 : buffer = NULL;
4143 : else
4144 436519 : bufferSize--;
4145 :
4146 436519 : const jschar *chars = str->chars();
4147 436519 : const jschar *charsEnd = chars + str->length();
4148 436519 : size_t n = 0;
4149 436519 : state = FIRST_QUOTE;
4150 436519 : unsigned shift = 0;
4151 436519 : unsigned hex = 0;
4152 436519 : unsigned u = 0;
4153 436519 : char c = 0; /* to quell GCC warnings */
4154 :
4155 4380607 : for (;;) {
4156 4817126 : switch (state) {
4157 : case STOP:
4158 436519 : goto stop;
4159 : case FIRST_QUOTE:
4160 436519 : state = CHARS;
4161 436519 : goto do_quote;
4162 : case LAST_QUOTE:
4163 436519 : state = STOP;
4164 : do_quote:
4165 873038 : if (quote == 0)
4166 873038 : continue;
4167 0 : c = (char)quote;
4168 0 : break;
4169 : case CHARS:
4170 3506273 : if (chars == charsEnd) {
4171 436519 : state = LAST_QUOTE;
4172 436519 : continue;
4173 : }
4174 3069754 : u = *chars++;
4175 3069754 : if (u < ' ') {
4176 360 : if (u != 0) {
4177 350 : const char *escape = strchr(js_EscapeMap, (int)u);
4178 350 : if (escape) {
4179 100 : u = escape[1];
4180 100 : goto do_escape;
4181 : }
4182 : }
4183 260 : goto do_hex_escape;
4184 : }
4185 3069394 : if (u < 127) {
4186 3069384 : if (u == quote || u == '\\')
4187 : goto do_escape;
4188 3069376 : c = (char)u;
4189 10 : } else if (u < 0x100) {
4190 10 : goto do_hex_escape;
4191 : } else {
4192 0 : shift = 16;
4193 0 : hex = u;
4194 0 : u = 'u';
4195 0 : goto do_escape;
4196 : }
4197 3069376 : break;
4198 : do_hex_escape:
4199 270 : shift = 8;
4200 270 : hex = u;
4201 270 : u = 'x';
4202 : do_escape:
4203 378 : c = '\\';
4204 378 : state = ESCAPE_START;
4205 378 : break;
4206 : case ESCAPE_START:
4207 378 : JS_ASSERT(' ' <= u && u < 127);
4208 378 : c = (char)u;
4209 378 : state = ESCAPE_MORE;
4210 378 : break;
4211 : case ESCAPE_MORE:
4212 918 : if (shift == 0) {
4213 378 : state = CHARS;
4214 378 : continue;
4215 : }
4216 540 : shift -= 4;
4217 540 : u = 0xF & (hex >> shift);
4218 540 : c = (char)(u + (u < 10 ? '0' : 'A' - 10));
4219 540 : break;
4220 : }
4221 3070672 : if (buffer) {
4222 3070672 : JS_ASSERT(n <= bufferSize);
4223 3070672 : if (n != bufferSize) {
4224 3070672 : buffer[n] = c;
4225 : } else {
4226 0 : buffer[n] = '\0';
4227 0 : buffer = NULL;
4228 : }
4229 0 : } else if (fp) {
4230 0 : if (fputc(c, fp) < 0)
4231 0 : return size_t(-1);
4232 : }
4233 3070672 : n++;
4234 : }
4235 : stop:
4236 436519 : if (buffer)
4237 436519 : buffer[n] = '\0';
4238 436519 : return n;
4239 : }
4240 :
4241 : } /* namespace js */
|