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 : #include "vm/StringBuffer.h"
80 :
81 : #include "jsinferinlines.h"
82 : #include "jsobjinlines.h"
83 : #include "jsstrinlines.h"
84 : #include "jsautooplen.h" // generated headers last
85 :
86 : #include "vm/MethodGuard-inl.h"
87 : #include "vm/RegExpObject-inl.h"
88 : #include "vm/RegExpStatics-inl.h"
89 : #include "vm/StringObject-inl.h"
90 : #include "vm/String-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 920970 : ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno)
99 : {
100 920970 : if (argno >= args.length())
101 27 : return cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
102 :
103 920943 : Value &arg = args[argno];
104 920943 : JSString *str = ToString(cx, arg);
105 920943 : if (!str)
106 72 : return NULL;
107 :
108 920871 : arg = StringValue(str);
109 920871 : 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 18 : str_escape(JSContext *cx, unsigned argc, Value *vp)
140 : {
141 18 : CallArgs args = CallArgsFromVp(argc, vp);
142 :
143 : const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
144 18 : '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
145 :
146 18 : JSLinearString *str = ArgToRootedString(cx, args, 0);
147 18 : if (!str)
148 0 : return false;
149 :
150 18 : size_t length = str->length();
151 18 : 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 18 : size_t count = 0;
167 4626 : for (size_t i = 0; i < sizeof(shouldPassThrough); i++) {
168 4608 : if (shouldPassThrough[i]) {
169 1242 : count++;
170 : }
171 : }
172 18 : 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 18 : size_t newlength = length;
178 36 : for (size_t i = 0; i < length; i++) {
179 18 : jschar ch = chars[i];
180 18 : if (ch < 128 && shouldPassThrough[ch])
181 18 : continue;
182 :
183 : /* The character will be encoded as %XX or %uXXXX. */
184 0 : 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 0 : if (newlength < length) {
191 0 : js_ReportAllocationOverflow(cx);
192 0 : return false;
193 : }
194 : }
195 :
196 18 : if (newlength >= ~(size_t)0 / sizeof(jschar)) {
197 0 : js_ReportAllocationOverflow(cx);
198 0 : return false;
199 : }
200 :
201 18 : jschar *newchars = (jschar *) cx->malloc_((newlength + 1) * sizeof(jschar));
202 18 : if (!newchars)
203 0 : return false;
204 : size_t i, ni;
205 36 : for (i = 0, ni = 0; i < length; i++) {
206 18 : jschar ch = chars[i];
207 18 : if (ch < 128 && shouldPassThrough[ch]) {
208 18 : newchars[ni++] = ch;
209 0 : } else if (ch < 256) {
210 0 : newchars[ni++] = '%';
211 0 : newchars[ni++] = digits[ch >> 4];
212 0 : newchars[ni++] = digits[ch & 0xF];
213 : } else {
214 0 : newchars[ni++] = '%';
215 0 : newchars[ni++] = 'u';
216 0 : newchars[ni++] = digits[ch >> 12];
217 0 : newchars[ni++] = digits[(ch & 0xF00) >> 8];
218 0 : newchars[ni++] = digits[(ch & 0xF0) >> 4];
219 0 : newchars[ni++] = digits[ch & 0xF];
220 : }
221 : }
222 18 : JS_ASSERT(ni == newlength);
223 18 : newchars[newlength] = 0;
224 :
225 18 : JSString *retstr = js_NewString(cx, newchars, newlength);
226 18 : if (!retstr) {
227 0 : cx->free_(newchars);
228 0 : return false;
229 : }
230 :
231 18 : args.rval() = StringValue(retstr);
232 18 : 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 5031 : str_uneval(JSContext *cx, unsigned argc, Value *vp)
362 : {
363 5031 : CallArgs args = CallArgsFromVp(argc, vp);
364 5031 : JSString *str = js_ValueToSource(cx, args.length() != 0 ? args[0] : UndefinedValue());
365 5031 : if (!str)
366 0 : return false;
367 :
368 5031 : args.rval() = StringValue(str);
369 5031 : 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 324 : str_enumerate(JSContext *cx, JSObject *obj)
404 : {
405 324 : JSString *str = obj->asString().unbox();
406 1692 : for (size_t i = 0, length = str->length(); i < length; i++) {
407 1368 : JSString *str1 = js_NewDependentString(cx, str, i, 1);
408 1368 : if (!str1)
409 0 : return false;
410 1368 : if (!obj->defineElement(cx, i, StringValue(str1),
411 : JS_PropertyStub, JS_StrictPropertyStub,
412 1368 : STRING_ELEMENT_ATTRS)) {
413 0 : return false;
414 : }
415 : }
416 :
417 324 : return true;
418 : }
419 :
420 : static JSBool
421 99369 : str_resolve(JSContext *cx, JSObject *obj, jsid id, unsigned flags,
422 : JSObject **objp)
423 : {
424 99369 : if (!JSID_IS_INT(id))
425 93340 : return JS_TRUE;
426 :
427 6029 : JSString *str = obj->asString().unbox();
428 :
429 6029 : int32_t slot = JSID_TO_INT(id);
430 6029 : if ((size_t)slot < str->length()) {
431 189 : JSString *str1 = cx->runtime->staticStrings.getUnitStringForElement(cx, str, size_t(slot));
432 189 : if (!str1)
433 0 : return JS_FALSE;
434 189 : if (!obj->defineElement(cx, uint32_t(slot), StringValue(str1), NULL, NULL,
435 189 : STRING_ELEMENT_ATTRS)) {
436 0 : return JS_FALSE;
437 : }
438 189 : *objp = obj;
439 : }
440 6029 : 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 1233169 : ThisToStringForStringProto(JSContext *cx, CallReceiver call)
464 : {
465 1233169 : JS_CHECK_RECURSION(cx, return NULL);
466 :
467 1233169 : if (call.thisv().isString())
468 1229236 : return call.thisv().toString();
469 :
470 3933 : if (call.thisv().isObject()) {
471 3933 : JSObject *obj = &call.thisv().toObject();
472 6678 : if (obj->isString() &&
473 : ClassMethodIsNative(cx, obj,
474 : &StringClass,
475 2745 : ATOM_TO_JSID(cx->runtime->atomState.toStringAtom),
476 2745 : js_str_toString))
477 : {
478 2745 : JSString *str = obj->asString().unbox();
479 2745 : call.thisv().setString(str);
480 2745 : 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 126 : str_toSource(JSContext *cx, unsigned argc, Value *vp)
518 : {
519 126 : CallArgs args = CallArgsFromVp(argc, vp);
520 :
521 : JSString *str;
522 : bool ok;
523 126 : if (!BoxedPrimitiveMethodGuard(cx, args, str_toSource, &str, &ok))
524 63 : return ok;
525 :
526 63 : str = js_QuoteString(cx, str, '"');
527 63 : if (!str)
528 0 : return false;
529 :
530 126 : StringBuffer sb(cx);
531 63 : if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
532 0 : return false;
533 :
534 63 : str = sb.finishString();
535 63 : if (!str)
536 0 : return false;
537 63 : args.rval() = StringValue(str);
538 63 : return true;
539 : }
540 :
541 : #endif /* JS_HAS_TOSOURCE */
542 :
543 : JSBool
544 144 : js_str_toString(JSContext *cx, unsigned argc, Value *vp)
545 : {
546 144 : CallArgs args = CallArgsFromVp(argc, vp);
547 :
548 : JSString *str;
549 : bool ok;
550 144 : if (!BoxedPrimitiveMethodGuard(cx, args, js_str_toString, &str, &ok))
551 63 : return ok;
552 :
553 81 : args.rval() = StringValue(str);
554 81 : return true;
555 : }
556 :
557 : /*
558 : * Java-like string native methods.
559 : */
560 :
561 : JS_ALWAYS_INLINE bool
562 509427 : ValueToIntegerRange(JSContext *cx, const Value &v, int32_t *out)
563 : {
564 509427 : if (v.isInt32()) {
565 509427 : *out = v.toInt32();
566 : } else {
567 : double d;
568 0 : if (!ToInteger(cx, v, &d))
569 0 : return false;
570 0 : if (d > INT32_MAX)
571 0 : *out = INT32_MAX;
572 0 : else if (d < INT32_MIN)
573 0 : *out = INT32_MIN;
574 : else
575 0 : *out = int32_t(d);
576 : }
577 :
578 509427 : return true;
579 : }
580 :
581 : static JSBool
582 254151 : str_substring(JSContext *cx, unsigned argc, Value *vp)
583 : {
584 254151 : CallArgs args = CallArgsFromVp(argc, vp);
585 :
586 254151 : JSString *str = ThisToStringForStringProto(cx, args);
587 254151 : if (!str)
588 0 : return false;
589 :
590 : int32_t length, begin, end;
591 254151 : if (args.length() > 0) {
592 254151 : end = length = int32_t(str->length());
593 :
594 254151 : if (!ValueToIntegerRange(cx, args[0], &begin))
595 0 : return false;
596 :
597 254151 : if (begin < 0)
598 0 : begin = 0;
599 254151 : else if (begin > length)
600 45 : begin = length;
601 :
602 254151 : if (args.hasDefined(1)) {
603 210204 : if (!ValueToIntegerRange(cx, args[1], &end))
604 0 : return false;
605 :
606 210204 : if (end > length) {
607 0 : end = length;
608 : } else {
609 210204 : if (end < 0)
610 0 : end = 0;
611 210204 : if (end < begin) {
612 0 : int32_t tmp = begin;
613 0 : begin = end;
614 0 : end = tmp;
615 : }
616 : }
617 : }
618 :
619 254151 : str = js_NewDependentString(cx, str, size_t(begin), size_t(end - begin));
620 254151 : if (!str)
621 0 : return false;
622 : }
623 :
624 254151 : args.rval() = StringValue(str);
625 254151 : return true;
626 : }
627 :
628 : JSString* JS_FASTCALL
629 23400 : js_toLowerCase(JSContext *cx, JSString *str)
630 : {
631 23400 : size_t n = str->length();
632 23400 : const jschar *s = str->getChars(cx);
633 23400 : if (!s)
634 0 : return NULL;
635 :
636 23400 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
637 23400 : if (!news)
638 0 : return NULL;
639 269442 : for (size_t i = 0; i < n; i++)
640 246042 : news[i] = unicode::ToLowerCase(s[i]);
641 23400 : news[n] = 0;
642 23400 : str = js_NewString(cx, news, n);
643 23400 : if (!str) {
644 0 : cx->free_(news);
645 0 : return NULL;
646 : }
647 23400 : return str;
648 : }
649 :
650 : static inline bool
651 23400 : ToLowerCaseHelper(JSContext *cx, CallReceiver call)
652 : {
653 23400 : JSString *str = ThisToStringForStringProto(cx, call);
654 23400 : if (!str)
655 0 : return false;
656 :
657 23400 : str = js_toLowerCase(cx, str);
658 23400 : if (!str)
659 0 : return false;
660 :
661 23400 : call.rval() = StringValue(str);
662 23400 : return true;
663 : }
664 :
665 : static JSBool
666 23400 : str_toLowerCase(JSContext *cx, unsigned argc, Value *vp)
667 : {
668 23400 : 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 900 : js_toUpperCase(JSContext *cx, JSString *str)
698 : {
699 900 : size_t n = str->length();
700 900 : const jschar *s = str->getChars(cx);
701 900 : if (!s)
702 0 : return NULL;
703 900 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
704 900 : if (!news)
705 0 : return NULL;
706 5400 : for (size_t i = 0; i < n; i++)
707 4500 : news[i] = unicode::ToUpperCase(s[i]);
708 900 : news[n] = 0;
709 900 : str = js_NewString(cx, news, n);
710 900 : if (!str) {
711 0 : cx->free_(news);
712 0 : return NULL;
713 : }
714 900 : return str;
715 : }
716 :
717 : static JSBool
718 900 : ToUpperCaseHelper(JSContext *cx, CallReceiver call)
719 : {
720 900 : JSString *str = ThisToStringForStringProto(cx, call);
721 900 : if (!str)
722 0 : return false;
723 :
724 900 : str = js_toUpperCase(cx, str);
725 900 : if (!str)
726 0 : return false;
727 :
728 900 : call.rval() = StringValue(str);
729 900 : return true;
730 : }
731 :
732 : static JSBool
733 900 : str_toUpperCase(JSContext *cx, unsigned argc, Value *vp)
734 : {
735 900 : 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 63 : str_localeCompare(JSContext *cx, unsigned argc, Value *vp)
765 : {
766 63 : CallArgs args = CallArgsFromVp(argc, vp);
767 63 : JSString *str = ThisToStringForStringProto(cx, args);
768 63 : if (!str)
769 0 : return false;
770 :
771 63 : if (args.length() == 0) {
772 9 : args.rval() = Int32Value(0);
773 : } else {
774 54 : JSString *thatStr = ToString(cx, args[0]);
775 54 : if (!thatStr)
776 0 : return false;
777 :
778 54 : if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) {
779 0 : args[0].setString(thatStr);
780 :
781 : Value result;
782 0 : if (!cx->localeCallbacks->localeCompare(cx, str, thatStr, &result))
783 0 : return true;
784 :
785 0 : args.rval() = result;
786 0 : 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 5625 : js_str_charAt(JSContext *cx, unsigned argc, Value *vp)
800 : {
801 5625 : CallArgs args = CallArgsFromVp(argc, vp);
802 :
803 : JSString *str;
804 : size_t i;
805 5625 : if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
806 5444 : str = args.thisv().toString();
807 5444 : i = size_t(args[0].toInt32());
808 5444 : if (i >= str->length())
809 54 : 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 5454 : str = cx->runtime->staticStrings.getUnitStringForElement(cx, str, i);
825 5454 : if (!str)
826 0 : return false;
827 5454 : args.rval() = StringValue(str);
828 5454 : return true;
829 :
830 : out_of_range:
831 171 : args.rval() = StringValue(cx->runtime->emptyString);
832 171 : return true;
833 : }
834 :
835 : JSBool
836 690878 : js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp)
837 : {
838 690878 : CallArgs args = CallArgsFromVp(argc, vp);
839 :
840 : JSString *str;
841 : size_t i;
842 690878 : if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
843 690423 : str = args.thisv().toString();
844 690423 : i = size_t(args[0].toInt32());
845 690423 : if (i >= str->length())
846 135 : goto out_of_range;
847 : } else {
848 455 : str = ThisToStringForStringProto(cx, args);
849 455 : if (!str)
850 0 : return false;
851 :
852 455 : double d = 0.0;
853 455 : if (args.length() > 0 && !ToInteger(cx, args[0], &d))
854 0 : return false;
855 :
856 455 : if (d < 0 || str->length() <= d)
857 306 : goto out_of_range;
858 149 : i = size_t(d);
859 : }
860 :
861 : const jschar *chars;
862 690437 : chars = str->getChars(cx);
863 690437 : if (!chars)
864 0 : return false;
865 :
866 690437 : args.rval() = Int32Value(chars[i]);
867 690437 : 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 0 : js_BoyerMooreHorspool(const jschar *text, uint32_t textlen,
886 : const jschar *pat, uint32_t patlen)
887 : {
888 : uint8_t skip[sBMHCharSetSize];
889 :
890 0 : JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax);
891 0 : for (uint32_t i = 0; i < sBMHCharSetSize; i++)
892 0 : skip[i] = (uint8_t)patlen;
893 0 : uint32_t m = patlen - 1;
894 0 : for (uint32_t i = 0; i < m; i++) {
895 0 : jschar c = pat[i];
896 0 : if (c >= sBMHCharSetSize)
897 0 : return sBMHBadPattern;
898 0 : skip[c] = (uint8_t)(m - i);
899 : }
900 : jschar c;
901 0 : for (uint32_t k = m;
902 : k < textlen;
903 0 : k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) {
904 0 : for (uint32_t i = k, j = m; ; i--, j--) {
905 0 : if (text[i] != pat[j])
906 : break;
907 0 : if (j == 0)
908 0 : return static_cast<int>(i); /* safe: max string size */
909 : }
910 : }
911 0 : 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 1755 : static JS_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) {
927 1755 : return pat + patlen;
928 : }
929 1728 : static JS_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
930 11169 : for (; p != extent; ++p, ++t) {
931 9441 : if (*p != *t)
932 0 : return false;
933 : }
934 1728 : return true;
935 : }
936 : };
937 :
938 : template <class InnerMatch>
939 : static int
940 1755 : UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen)
941 : {
942 1755 : JS_ASSERT(patlen > 0 && textlen > 0);
943 1755 : const jschar *textend = text + textlen - (patlen - 1);
944 1755 : const jschar p0 = *pat;
945 1755 : const jschar *const patNext = pat + 1;
946 1755 : const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
947 : uint8_t fixup;
948 :
949 1755 : const jschar *t = text;
950 1755 : switch ((textend - t) & 7) {
951 72 : case 0: if (*t++ == p0) { fixup = 8; goto match; }
952 153 : case 7: if (*t++ == p0) { fixup = 7; goto match; }
953 369 : case 6: if (*t++ == p0) { fixup = 6; goto match; }
954 252 : case 5: if (*t++ == p0) { fixup = 5; goto match; }
955 270 : case 4: if (*t++ == p0) { fixup = 4; goto match; }
956 306 : case 3: if (*t++ == p0) { fixup = 3; goto match; }
957 1476 : case 2: if (*t++ == p0) { fixup = 2; goto match; }
958 1539 : case 1: if (*t++ == p0) { fixup = 1; goto match; }
959 : }
960 1098 : while (t != textend) {
961 729 : if (t[0] == p0) { t += 1; fixup = 8; goto match; }
962 729 : if (t[1] == p0) { t += 2; fixup = 7; goto match; }
963 693 : if (t[2] == p0) { t += 3; fixup = 6; goto match; }
964 675 : if (t[3] == p0) { t += 4; fixup = 5; goto match; }
965 639 : if (t[4] == p0) { t += 5; fixup = 4; goto match; }
966 621 : if (t[5] == p0) { t += 6; fixup = 3; goto match; }
967 621 : if (t[6] == p0) { t += 7; fixup = 2; goto match; }
968 468 : if (t[7] == p0) { t += 8; fixup = 1; goto match; }
969 414 : t += 8;
970 414 : continue;
971 0 : do {
972 0 : if (*t++ == p0) {
973 : match:
974 1728 : if (!InnerMatch::match(patNext, t, extent))
975 0 : goto failed_match;
976 1728 : return t - text - 1;
977 : }
978 : failed_match:;
979 : } while (--fixup > 0);
980 : }
981 27 : return -1;
982 : }
983 :
984 : static JS_ALWAYS_INLINE int
985 18239178 : StringMatch(const jschar *text, uint32_t textlen,
986 : const jschar *pat, uint32_t patlen)
987 : {
988 18239178 : if (patlen == 0)
989 200601 : return 0;
990 18038577 : if (textlen < patlen)
991 18072 : 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 18020505 : if (patlen == 1) {
999 18018750 : const jschar p0 = *pat;
1000 1144042176 : for (const jschar *c = text, *end = text + textlen; c != end; ++c) {
1001 1126565769 : if (*c == p0)
1002 542343 : return c - text;
1003 : }
1004 17476407 : 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 1755 : if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) {
1022 0 : int index = js_BoyerMooreHorspool(text, textlen, pat, patlen);
1023 0 : if (index != sBMHBadPattern)
1024 0 : 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 1755 : 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 450018 : RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match)
1050 : {
1051 450018 : JS_ASSERT(textstr->isRope());
1052 :
1053 450018 : if (patlen == 0) {
1054 0 : *match = 0;
1055 0 : return true;
1056 : }
1057 450018 : 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 900036 : 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 450018 : size_t textstrlen = textstr->length();
1077 450018 : size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
1078 900036 : StringSegmentRange r(cx);
1079 450018 : if (!r.init(textstr))
1080 0 : return false;
1081 37192383 : while (!r.empty()) {
1082 36309006 : if (threshold-- == 0 || !strs.append(r.front())) {
1083 16659 : const jschar *chars = textstr->getChars(cx);
1084 16659 : if (!chars)
1085 0 : return false;
1086 16659 : *match = StringMatch(chars, textstrlen, pat, patlen);
1087 16659 : 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 1530 : str_indexOf(JSContext *cx, unsigned argc, Value *vp)
1150 : {
1151 1530 : CallArgs args = CallArgsFromVp(argc, vp);
1152 1530 : JSString *str = ThisToStringForStringProto(cx, args);
1153 1530 : if (!str)
1154 0 : return false;
1155 :
1156 1530 : JSLinearString *patstr = ArgToRootedString(cx, args, 0);
1157 1530 : if (!patstr)
1158 0 : return false;
1159 :
1160 1530 : uint32_t textlen = str->length();
1161 1530 : const jschar *text = str->getChars(cx);
1162 1530 : if (!text)
1163 0 : return false;
1164 :
1165 1530 : uint32_t patlen = patstr->length();
1166 1530 : const jschar *pat = patstr->chars();
1167 :
1168 : uint32_t start;
1169 1530 : 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 1530 : start = 0;
1199 : }
1200 :
1201 1530 : int match = StringMatch(text, textlen, pat, patlen);
1202 1530 : args.rval() = Int32Value((match == -1) ? -1 : start + match);
1203 1530 : return true;
1204 : }
1205 :
1206 : static JSBool
1207 0 : str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp)
1208 : {
1209 0 : CallArgs args = CallArgsFromVp(argc, vp);
1210 0 : JSString *textstr = ThisToStringForStringProto(cx, args);
1211 0 : if (!textstr)
1212 0 : return false;
1213 :
1214 0 : size_t textlen = textstr->length();
1215 0 : const jschar *text = textstr->getChars(cx);
1216 0 : if (!text)
1217 0 : return false;
1218 :
1219 0 : JSLinearString *patstr = ArgToRootedString(cx, args, 0);
1220 0 : if (!patstr)
1221 0 : return false;
1222 :
1223 0 : size_t patlen = patstr->length();
1224 0 : const jschar *pat = patstr->chars();
1225 :
1226 0 : int i = textlen - patlen; // Start searching here
1227 0 : if (i < 0) {
1228 0 : args.rval() = Int32Value(-1);
1229 0 : return true;
1230 : }
1231 :
1232 0 : 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 0 : if (patlen == 0) {
1254 0 : args.rval() = Int32Value(i);
1255 0 : return true;
1256 : }
1257 :
1258 0 : const jschar *t = text + i;
1259 0 : const jschar *textend = text - 1;
1260 0 : const jschar p0 = *pat;
1261 0 : const jschar *patNext = pat + 1;
1262 0 : const jschar *patEnd = pat + patlen;
1263 :
1264 0 : for (; t != textend; --t) {
1265 0 : if (*t == p0) {
1266 0 : const jschar *t1 = t + 1;
1267 0 : for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) {
1268 0 : if (*t1 != *p1)
1269 0 : goto break_continue;
1270 : }
1271 0 : args.rval() = Int32Value(t - text);
1272 0 : return true;
1273 : }
1274 : break_continue:;
1275 : }
1276 :
1277 0 : args.rval() = Int32Value(-1);
1278 0 : return true;
1279 : }
1280 :
1281 : static JSBool
1282 180 : js_TrimString(JSContext *cx, Value *vp, JSBool trimLeft, JSBool trimRight)
1283 : {
1284 180 : CallReceiver call = CallReceiverFromVp(vp);
1285 180 : JSString *str = ThisToStringForStringProto(cx, call);
1286 180 : if (!str)
1287 0 : return false;
1288 180 : size_t length = str->length();
1289 180 : const jschar *chars = str->getChars(cx);
1290 180 : if (!chars)
1291 0 : return false;
1292 :
1293 180 : size_t begin = 0;
1294 180 : size_t end = length;
1295 :
1296 180 : if (trimLeft) {
1297 3168 : while (begin < length && unicode::IsSpace(chars[begin]))
1298 2844 : ++begin;
1299 : }
1300 :
1301 180 : if (trimRight) {
1302 387 : while (end > begin && unicode::IsSpace(chars[end - 1]))
1303 27 : --end;
1304 : }
1305 :
1306 180 : str = js_NewDependentString(cx, str, begin, end - begin);
1307 180 : if (!str)
1308 0 : return false;
1309 :
1310 180 : call.rval() = StringValue(str);
1311 180 : return true;
1312 : }
1313 :
1314 : static JSBool
1315 162 : str_trim(JSContext *cx, unsigned argc, Value *vp)
1316 : {
1317 162 : 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 907957 : FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */
1348 3960 : JSLinearString *pattern() const { return patstr; }
1349 10332 : 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 522261 : int32_t match() const { return match_; }
1356 : };
1357 :
1358 : static inline bool
1359 820998 : IsRegExpMetaChar(jschar c)
1360 : {
1361 820998 : 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 20349 : return true;
1367 : default:
1368 800649 : return false;
1369 : }
1370 : }
1371 :
1372 : static inline bool
1373 510939 : HasRegExpMetaChars(const jschar *chars, size_t length)
1374 : {
1375 1310589 : for (size_t i = 0; i < length; ++i) {
1376 819495 : if (IsRegExpMetaChar(chars[i]))
1377 19845 : 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 907957 : {
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 423 : flattenPattern(JSContext *cx, JSAtom *patstr)
1404 : {
1405 846 : StringBuffer sb(cx);
1406 423 : if (!sb.reserve(patstr->length()))
1407 0 : return NULL;
1408 :
1409 : static const jschar ESCAPE_CHAR = '\\';
1410 423 : const jschar *chars = patstr->chars();
1411 423 : size_t len = patstr->length();
1412 1926 : for (const jschar *it = chars; it != chars + len; ++it) {
1413 1503 : if (IsRegExpMetaChar(*it)) {
1414 504 : if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
1415 0 : return NULL;
1416 : } else {
1417 999 : if (!sb.append(*it))
1418 0 : return NULL;
1419 : }
1420 : }
1421 423 : return sb.finishAtom();
1422 : }
1423 :
1424 : public:
1425 907957 : StringRegExpGuard() {}
1426 :
1427 : /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
1428 907957 : bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
1429 : {
1430 907957 : if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
1431 349750 : if (!RegExpToShared(cx, args[0].toObject(), &re_))
1432 0 : return false;
1433 : } else {
1434 558207 : if (convertVoid && !args.hasDefined(0)) {
1435 11556 : fm.patstr = cx->runtime->emptyString;
1436 11556 : return true;
1437 : }
1438 :
1439 546651 : JSString *arg = ArgToRootedString(cx, args, 0);
1440 546651 : if (!arg)
1441 54 : return false;
1442 :
1443 546597 : fm.patstr = js_AtomizeString(cx, arg);
1444 546597 : if (!fm.patstr)
1445 0 : return false;
1446 : }
1447 896347 : 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 907903 : tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc,
1462 : bool checkMetaChars = true)
1463 : {
1464 907903 : if (re_.initialized())
1465 349750 : return NULL;
1466 :
1467 558153 : fm.pat = fm.patstr->chars();
1468 558153 : fm.patlen = fm.patstr->length();
1469 :
1470 558153 : if (optarg < argc)
1471 23463 : return NULL;
1472 :
1473 1045629 : if (checkMetaChars &&
1474 510939 : (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) {
1475 19845 : 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 514845 : if (textstr->isRope()) {
1483 450018 : if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_))
1484 0 : return NULL;
1485 : } else {
1486 64827 : const jschar *text = textstr->asLinear().chars();
1487 64827 : size_t textlen = textstr->length();
1488 64827 : fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen);
1489 : }
1490 514845 : return &fm;
1491 : }
1492 :
1493 : /* If the pattern is not already a regular expression, make it so. */
1494 393058 : bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args)
1495 : {
1496 393058 : if (re_.initialized())
1497 349750 : return true;
1498 :
1499 : /* Build RegExp from pattern string. */
1500 : JSString *opt;
1501 43308 : if (optarg < args.length()) {
1502 23463 : opt = ToString(cx, args[optarg]);
1503 23463 : if (!opt)
1504 0 : return false;
1505 : } else {
1506 19845 : opt = NULL;
1507 : }
1508 :
1509 : JSAtom *patstr;
1510 43308 : if (flat) {
1511 423 : patstr = flattenPattern(cx, fm.patstr);
1512 423 : if (!patstr)
1513 0 : return false;
1514 : } else {
1515 42885 : patstr = fm.patstr;
1516 : }
1517 43308 : JS_ASSERT(patstr);
1518 :
1519 43308 : return cx->compartment->regExps.get(cx, patstr, opt, &re_);
1520 : }
1521 :
1522 429158 : 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 2018898 : Matched(RegExpExecType type, const Value &v)
1528 : {
1529 2018898 : 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 377857 : DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
1551 : DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
1552 : {
1553 377857 : JSLinearString *linearStr = str->ensureLinear(cx);
1554 377857 : if (!linearStr)
1555 0 : return false;
1556 :
1557 377857 : const jschar *chars = linearStr->chars();
1558 377857 : size_t length = linearStr->length();
1559 :
1560 377857 : if (re.global()) {
1561 265527 : RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
1562 1971702 : for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
1563 1932804 : if (!JS_CHECK_OPERATION_LIMIT(cx))
1564 0 : return false;
1565 1932804 : if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
1566 0 : return false;
1567 1932804 : if (!Matched(type, *rval))
1568 226629 : break;
1569 1706175 : if (!callback(cx, res, count, data))
1570 0 : return false;
1571 1706175 : if (!res->matched())
1572 77850 : ++i;
1573 : }
1574 : } else {
1575 112330 : RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
1576 112330 : bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
1577 112330 : size_t i = 0;
1578 112330 : if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
1579 0 : return false;
1580 112330 : if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
1581 9 : return false;
1582 : }
1583 377848 : return true;
1584 : }
1585 :
1586 : static bool
1587 25920 : BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, CallArgs *args)
1588 : {
1589 25920 : if (fm.match() < 0) {
1590 21960 : args->rval() = NullValue();
1591 21960 : return true;
1592 : }
1593 :
1594 : /* For this non-global match, produce a RegExp.exec-style array. */
1595 3960 : JSObject *obj = NewSlowEmptyArray(cx);
1596 3960 : if (!obj)
1597 0 : return false;
1598 :
1599 11880 : if (!obj->defineElement(cx, 0, StringValue(fm.pattern())) ||
1600 3960 : !obj->defineProperty(cx, cx->runtime->atomState.indexAtom, Int32Value(fm.match())) ||
1601 3960 : !obj->defineProperty(cx, cx->runtime->atomState.inputAtom, StringValue(textstr)))
1602 : {
1603 0 : return false;
1604 : }
1605 :
1606 3960 : args->rval() = ObjectValue(*obj);
1607 3960 : return true;
1608 : }
1609 :
1610 : typedef JSObject **MatchArgType;
1611 :
1612 : /*
1613 : * DoMatch will only callback on global matches, hence this function builds
1614 : * only the "array of matches" returned by match on global regexps.
1615 : */
1616 : static bool
1617 2187 : MatchCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
1618 : {
1619 2187 : JS_ASSERT(count <= JSID_INT_MAX); /* by max string length */
1620 :
1621 2187 : JSObject *&arrayobj = *static_cast<MatchArgType>(p);
1622 2187 : if (!arrayobj) {
1623 405 : arrayobj = NewDenseEmptyArray(cx);
1624 405 : if (!arrayobj)
1625 0 : return false;
1626 : }
1627 :
1628 : Value v;
1629 2187 : return res->createLastMatch(cx, &v) && arrayobj->defineElement(cx, count, v);
1630 : }
1631 :
1632 : JSBool
1633 62038 : js::str_match(JSContext *cx, unsigned argc, Value *vp)
1634 : {
1635 62038 : CallArgs args = CallArgsFromVp(argc, vp);
1636 62038 : JSString *str = ThisToStringForStringProto(cx, args);
1637 62038 : if (!str)
1638 0 : return false;
1639 :
1640 124076 : StringRegExpGuard g;
1641 62038 : if (!g.init(cx, args, true))
1642 18 : return false;
1643 :
1644 62020 : if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
1645 25920 : return BuildFlatMatchArray(cx, str, *fm, &args);
1646 :
1647 : /* Return if there was an error in tryFlatMatch. */
1648 36100 : if (cx->isExceptionPending())
1649 0 : return false;
1650 :
1651 36100 : if (!g.normalizeRegExp(cx, false, 1, args))
1652 0 : return false;
1653 :
1654 36100 : JSObject *array = NULL;
1655 36100 : MatchArgType arg = &array;
1656 36100 : RegExpStatics *res = cx->regExpStatics();
1657 : Value rval;
1658 36100 : if (!DoMatch(cx, res, str, g.regExp(), MatchCallback, arg, MATCH_ARGS, &rval))
1659 0 : return false;
1660 :
1661 36100 : if (g.regExp().global())
1662 9864 : args.rval() = ObjectOrNullValue(array);
1663 : else
1664 26236 : args.rval() = rval;
1665 36100 : return true;
1666 : }
1667 :
1668 : JSBool
1669 480393 : js::str_search(JSContext *cx, unsigned argc, Value *vp)
1670 : {
1671 480393 : CallArgs args = CallArgsFromVp(argc, vp);
1672 480393 : JSString *str = ThisToStringForStringProto(cx, args);
1673 480393 : if (!str)
1674 0 : return false;
1675 :
1676 960786 : StringRegExpGuard g;
1677 480393 : if (!g.init(cx, args, true))
1678 18 : return false;
1679 480375 : if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
1680 465174 : args.rval() = Int32Value(fm->match());
1681 465174 : return true;
1682 : }
1683 :
1684 15201 : if (cx->isExceptionPending()) /* from tryFlatMatch */
1685 0 : return false;
1686 :
1687 15201 : if (!g.normalizeRegExp(cx, false, 1, args))
1688 0 : return false;
1689 :
1690 15201 : JSLinearString *linearStr = str->ensureLinear(cx);
1691 15201 : if (!linearStr)
1692 0 : return false;
1693 :
1694 15201 : const jschar *chars = linearStr->chars();
1695 15201 : size_t length = linearStr->length();
1696 15201 : RegExpStatics *res = cx->regExpStatics();
1697 :
1698 : /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
1699 15201 : size_t i = 0;
1700 : Value result;
1701 15201 : if (!ExecuteRegExp(cx, res, g.regExp(), linearStr, chars, length, &i, RegExpTest, &result))
1702 0 : return false;
1703 :
1704 15201 : if (result.isTrue())
1705 63 : args.rval() = Int32Value(res->matchStart());
1706 : else
1707 15138 : args.rval() = Int32Value(-1);
1708 15201 : return true;
1709 : }
1710 :
1711 : struct ReplaceData
1712 365526 : {
1713 365526 : ReplaceData(JSContext *cx)
1714 365526 : : sb(cx)
1715 365526 : {}
1716 :
1717 : JSString *str; /* 'this' parameter object as a string */
1718 : StringRegExpGuard g; /* regexp parameter object and private data */
1719 : JSObject *lambda; /* replacement function object or null */
1720 : JSObject *elembase; /* object for function(a){return b[a]} replace */
1721 : JSLinearString *repstr; /* replacement string */
1722 : const jschar *dollar; /* null or pointer to first $ in repstr */
1723 : const jschar *dollarEnd; /* limit pointer for js_strchr_limit */
1724 : int leftIndex; /* left context index in str->chars */
1725 : JSSubString dollarStr; /* for "$$" InterpretDollar result */
1726 : bool calledBack; /* record whether callback has been called */
1727 : InvokeArgsGuard args; /* arguments for lambda call */
1728 : StringBuffer sb; /* buffer built during DoMatch */
1729 : };
1730 :
1731 : static bool
1732 1476 : InterpretDollar(JSContext *cx, RegExpStatics *res, const jschar *dp, const jschar *ep,
1733 : ReplaceData &rdata, JSSubString *out, size_t *skip)
1734 : {
1735 1476 : JS_ASSERT(*dp == '$');
1736 :
1737 : /* If there is only a dollar, bail now */
1738 1476 : if (dp + 1 >= ep)
1739 0 : return false;
1740 :
1741 : /* Interpret all Perl match-induced dollar variables. */
1742 1476 : jschar dc = dp[1];
1743 1476 : if (JS7_ISDEC(dc)) {
1744 : /* ECMA-262 Edition 3: 1-9 or 01-99 */
1745 1458 : unsigned num = JS7_UNDEC(dc);
1746 1458 : if (num > res->parenCount())
1747 0 : return false;
1748 :
1749 1458 : const jschar *cp = dp + 2;
1750 1458 : if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
1751 0 : unsigned tmp = 10 * num + JS7_UNDEC(dc);
1752 0 : if (tmp <= res->parenCount()) {
1753 0 : cp++;
1754 0 : num = tmp;
1755 : }
1756 : }
1757 1458 : if (num == 0)
1758 0 : return false;
1759 :
1760 1458 : *skip = cp - dp;
1761 :
1762 1458 : JS_ASSERT(num <= res->parenCount());
1763 :
1764 : /*
1765 : * Note: we index to get the paren with the (1-indexed) pair
1766 : * number, as opposed to a (0-indexed) paren number.
1767 : */
1768 1458 : res->getParen(num, out);
1769 1458 : return true;
1770 : }
1771 :
1772 18 : *skip = 2;
1773 18 : switch (dc) {
1774 : case '$':
1775 0 : rdata.dollarStr.chars = dp;
1776 0 : rdata.dollarStr.length = 1;
1777 0 : *out = rdata.dollarStr;
1778 0 : return true;
1779 : case '&':
1780 18 : res->getLastMatch(out);
1781 18 : return true;
1782 : case '+':
1783 0 : res->getLastParen(out);
1784 0 : return true;
1785 : case '`':
1786 0 : res->getLeftContext(out);
1787 0 : return true;
1788 : case '\'':
1789 0 : res->getRightContext(out);
1790 0 : return true;
1791 : }
1792 0 : return false;
1793 : }
1794 :
1795 : static bool
1796 1765404 : FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
1797 : {
1798 1765404 : if (JSObject *base = rdata.elembase) {
1799 : /*
1800 : * The base object is used when replace was passed a lambda which looks like
1801 : * 'function(a) { return b[a]; }' for the base object b. b will not change
1802 : * in the course of the replace unless we end up making a scripted call due
1803 : * to accessing a scripted getter or a value with a scripted toString.
1804 : */
1805 733383 : JS_ASSERT(rdata.lambda);
1806 733383 : JS_ASSERT(!base->getOps()->lookupProperty);
1807 733383 : JS_ASSERT(!base->getOps()->getProperty);
1808 :
1809 : Value match;
1810 733383 : if (!res->createLastMatch(cx, &match))
1811 0 : return false;
1812 733383 : JSString *str = match.toString();
1813 :
1814 : JSAtom *atom;
1815 733383 : if (str->isAtom()) {
1816 733383 : atom = &str->asAtom();
1817 : } else {
1818 0 : atom = js_AtomizeString(cx, str);
1819 0 : if (!atom)
1820 0 : return false;
1821 : }
1822 :
1823 : Value v;
1824 733383 : if (HasDataProperty(cx, base, atom, &v) && v.isString()) {
1825 733347 : rdata.repstr = v.toString()->ensureLinear(cx);
1826 733347 : if (!rdata.repstr)
1827 0 : return false;
1828 733347 : *sizep = rdata.repstr->length();
1829 733347 : return true;
1830 : }
1831 :
1832 : /*
1833 : * Couldn't handle this property, fall through and despecialize to the
1834 : * general lambda case.
1835 : */
1836 36 : rdata.elembase = NULL;
1837 : }
1838 :
1839 1032057 : if (JSObject *lambda = rdata.lambda) {
1840 55818 : PreserveRegExpStatics staticsGuard(res);
1841 27909 : if (!staticsGuard.init(cx))
1842 0 : return false;
1843 :
1844 : /*
1845 : * In the lambda case, not only do we find the replacement string's
1846 : * length, we compute repstr and return it via rdata for use within
1847 : * DoReplace. The lambda is called with arguments ($&, $1, $2, ...,
1848 : * index, input), i.e., all the properties of a regexp match array.
1849 : * For $&, etc., we must create string jsvals from cx->regExpStatics.
1850 : * We grab up stack space to keep the newborn strings GC-rooted.
1851 : */
1852 27909 : unsigned p = res->parenCount();
1853 27909 : unsigned argc = 1 + p + 2;
1854 :
1855 27909 : InvokeArgsGuard &args = rdata.args;
1856 27909 : if (!args.pushed() && !cx->stack.pushInvokeArgs(cx, argc, &args))
1857 0 : return false;
1858 :
1859 27909 : args.setCallee(ObjectValue(*lambda));
1860 27909 : args.thisv() = UndefinedValue();
1861 :
1862 : /* Push $&, $1, $2, ... */
1863 27909 : unsigned argi = 0;
1864 27909 : if (!res->createLastMatch(cx, &args[argi++]))
1865 0 : return false;
1866 :
1867 50427 : for (size_t i = 0; i < res->parenCount(); ++i) {
1868 22518 : if (!res->createParen(cx, i + 1, &args[argi++]))
1869 0 : return false;
1870 : }
1871 :
1872 : /* Push match index and input string. */
1873 27909 : args[argi++].setInt32(res->matchStart());
1874 27909 : args[argi].setString(rdata.str);
1875 :
1876 27909 : if (!Invoke(cx, args))
1877 9 : return false;
1878 :
1879 : /* root repstr: rdata is on the stack, so scanned by conservative gc. */
1880 27900 : JSString *repstr = ToString(cx, args.rval());
1881 27900 : if (!repstr)
1882 0 : return false;
1883 27900 : rdata.repstr = repstr->ensureLinear(cx);
1884 27900 : if (!rdata.repstr)
1885 0 : return false;
1886 27900 : *sizep = rdata.repstr->length();
1887 27900 : return true;
1888 : }
1889 :
1890 1004148 : JSString *repstr = rdata.repstr;
1891 1004148 : size_t replen = repstr->length();
1892 1004886 : for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp;
1893 : dp = js_strchr_limit(dp, '$', ep)) {
1894 : JSSubString sub;
1895 : size_t skip;
1896 738 : if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
1897 738 : replen += sub.length - skip;
1898 738 : dp += skip;
1899 : } else {
1900 0 : dp++;
1901 : }
1902 : }
1903 1004148 : *sizep = replen;
1904 1004148 : return true;
1905 : }
1906 :
1907 : /*
1908 : * Precondition: |rdata.sb| already has necessary growth space reserved (as
1909 : * derived from FindReplaceLength).
1910 : */
1911 : static void
1912 1765395 : DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
1913 : {
1914 1765395 : JSLinearString *repstr = rdata.repstr;
1915 : const jschar *cp;
1916 1765395 : const jschar *bp = cp = repstr->chars();
1917 :
1918 1765395 : const jschar *dp = rdata.dollar;
1919 1765395 : const jschar *ep = rdata.dollarEnd;
1920 1766133 : for (; dp; dp = js_strchr_limit(dp, '$', ep)) {
1921 : /* Move one of the constant portions of the replacement value. */
1922 738 : size_t len = dp - cp;
1923 738 : rdata.sb.infallibleAppend(cp, len);
1924 738 : cp = dp;
1925 :
1926 : JSSubString sub;
1927 : size_t skip;
1928 738 : if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
1929 738 : len = sub.length;
1930 738 : rdata.sb.infallibleAppend(sub.chars, len);
1931 738 : cp += skip;
1932 738 : dp += skip;
1933 : } else {
1934 0 : dp++;
1935 : }
1936 : }
1937 1765395 : rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp));
1938 1765395 : }
1939 :
1940 : static bool
1941 1765404 : ReplaceRegExpCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
1942 : {
1943 1765404 : ReplaceData &rdata = *static_cast<ReplaceData *>(p);
1944 :
1945 1765404 : rdata.calledBack = true;
1946 1765404 : JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */
1947 1765404 : size_t leftoff = rdata.leftIndex;
1948 1765404 : const jschar *left = str.chars() + leftoff;
1949 1765404 : size_t leftlen = res->matchStart() - leftoff;
1950 1765404 : rdata.leftIndex = res->matchLimit();
1951 :
1952 1765404 : size_t replen = 0; /* silence 'unused' warning */
1953 1765404 : if (!FindReplaceLength(cx, res, rdata, &replen))
1954 9 : return false;
1955 :
1956 1765395 : size_t growth = leftlen + replen;
1957 1765395 : if (!rdata.sb.reserve(rdata.sb.length() + growth))
1958 0 : return false;
1959 1765395 : rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */
1960 1765395 : DoReplace(cx, res, rdata);
1961 1765395 : return true;
1962 : }
1963 :
1964 : static bool
1965 3438 : BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
1966 : const FlatMatch &fm, CallArgs *args)
1967 : {
1968 3438 : RopeBuilder builder(cx);
1969 3438 : size_t match = fm.match();
1970 3438 : size_t matchEnd = match + fm.patternLength();
1971 :
1972 3438 : if (textstr->isRope()) {
1973 : /*
1974 : * If we are replacing over a rope, avoid flattening it by iterating
1975 : * through it, building a new rope.
1976 : */
1977 18 : StringSegmentRange r(cx);
1978 9 : if (!r.init(textstr))
1979 0 : return false;
1980 9 : size_t pos = 0;
1981 36 : while (!r.empty()) {
1982 18 : JSString *str = r.front();
1983 18 : size_t len = str->length();
1984 18 : size_t strEnd = pos + len;
1985 18 : if (pos < matchEnd && strEnd > match) {
1986 : /*
1987 : * We need to special-case any part of the rope that overlaps
1988 : * with the replacement string.
1989 : */
1990 9 : if (match >= pos) {
1991 : /*
1992 : * If this part of the rope overlaps with the left side of
1993 : * the pattern, then it must be the only one to overlap with
1994 : * the first character in the pattern, so we include the
1995 : * replacement string here.
1996 : */
1997 9 : JSString *leftSide = js_NewDependentString(cx, str, 0, match - pos);
1998 27 : if (!leftSide ||
1999 9 : !builder.append(leftSide) ||
2000 9 : !builder.append(repstr)) {
2001 0 : return false;
2002 : }
2003 : }
2004 :
2005 : /*
2006 : * If str runs off the end of the matched string, append the
2007 : * last part of str.
2008 : */
2009 9 : if (strEnd > matchEnd) {
2010 : JSString *rightSide = js_NewDependentString(cx, str, matchEnd - pos,
2011 0 : strEnd - matchEnd);
2012 0 : if (!rightSide || !builder.append(rightSide))
2013 0 : return false;
2014 9 : }
2015 : } else {
2016 9 : if (!builder.append(str))
2017 0 : return false;
2018 : }
2019 18 : pos += str->length();
2020 18 : if (!r.popFront())
2021 0 : return false;
2022 : }
2023 : } else {
2024 3429 : JSString *leftSide = js_NewDependentString(cx, textstr, 0, match);
2025 3429 : if (!leftSide)
2026 0 : return false;
2027 3429 : JSString *rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(),
2028 6858 : textstr->length() - match - fm.patternLength());
2029 13716 : if (!rightSide ||
2030 3429 : !builder.append(leftSide) ||
2031 3429 : !builder.append(repstr) ||
2032 3429 : !builder.append(rightSide)) {
2033 0 : return false;
2034 : }
2035 : }
2036 :
2037 3438 : args->rval() = StringValue(builder.result());
2038 3438 : return true;
2039 : }
2040 :
2041 : /*
2042 : * Perform a linear-scan dollar substitution on the replacement text,
2043 : * constructing a result string that looks like:
2044 : *
2045 : * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
2046 : */
2047 : static inline bool
2048 18 : BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
2049 : const jschar *firstDollar, const FlatMatch &fm, CallArgs *args)
2050 : {
2051 18 : JSLinearString *textstr = textstrArg->ensureLinear(cx);
2052 18 : if (!textstr)
2053 0 : return NULL;
2054 :
2055 18 : JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length());
2056 18 : size_t matchStart = fm.match();
2057 18 : size_t matchLimit = matchStart + fm.patternLength();
2058 :
2059 : /*
2060 : * Most probably:
2061 : *
2062 : * len(newstr) >= len(orig) - len(match) + len(replacement)
2063 : *
2064 : * Note that dollar vars _could_ make the resulting text smaller than this.
2065 : */
2066 36 : StringBuffer newReplaceChars(cx);
2067 18 : if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
2068 0 : return false;
2069 :
2070 : /* Move the pre-dollar chunk in bulk. */
2071 18 : newReplaceChars.infallibleAppend(repstr->chars(), firstDollar);
2072 :
2073 : /* Move the rest char-by-char, interpreting dollars as we encounter them. */
2074 : #define ENSURE(__cond) if (!(__cond)) return false;
2075 18 : const jschar *repstrLimit = repstr->chars() + repstr->length();
2076 63 : for (const jschar *it = firstDollar; it < repstrLimit; ++it) {
2077 45 : if (*it != '$' || it == repstrLimit - 1) {
2078 27 : ENSURE(newReplaceChars.append(*it));
2079 27 : continue;
2080 : }
2081 :
2082 18 : switch (*(it + 1)) {
2083 : case '$': /* Eat one of the dollars. */
2084 0 : ENSURE(newReplaceChars.append(*it));
2085 0 : break;
2086 : case '&':
2087 18 : ENSURE(newReplaceChars.append(textstr->chars() + matchStart,
2088 : textstr->chars() + matchLimit));
2089 18 : break;
2090 : case '`':
2091 0 : ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart));
2092 0 : break;
2093 : case '\'':
2094 0 : ENSURE(newReplaceChars.append(textstr->chars() + matchLimit,
2095 : textstr->chars() + textstr->length()));
2096 0 : break;
2097 : default: /* The dollar we saw was not special (no matter what its mother told it). */
2098 0 : ENSURE(newReplaceChars.append(*it));
2099 0 : continue;
2100 : }
2101 18 : ++it; /* We always eat an extra char in the above switch. */
2102 : }
2103 :
2104 18 : JSString *leftSide = js_NewDependentString(cx, textstr, 0, matchStart);
2105 18 : ENSURE(leftSide);
2106 :
2107 18 : JSString *newReplace = newReplaceChars.finishString();
2108 18 : ENSURE(newReplace);
2109 :
2110 18 : JS_ASSERT(textstr->length() >= matchLimit);
2111 : JSString *rightSide = js_NewDependentString(cx, textstr, matchLimit,
2112 18 : textstr->length() - matchLimit);
2113 18 : ENSURE(rightSide);
2114 :
2115 18 : RopeBuilder builder(cx);
2116 18 : ENSURE(builder.append(leftSide) &&
2117 : builder.append(newReplace) &&
2118 : builder.append(rightSide));
2119 : #undef ENSURE
2120 :
2121 18 : args->rval() = StringValue(builder.result());
2122 18 : return true;
2123 : }
2124 :
2125 : static inline bool
2126 341757 : str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
2127 : {
2128 341757 : if (!rdata.g.normalizeRegExp(cx, true, 2, args))
2129 0 : return false;
2130 :
2131 341757 : rdata.leftIndex = 0;
2132 341757 : rdata.calledBack = false;
2133 :
2134 341757 : RegExpStatics *res = cx->regExpStatics();
2135 341757 : RegExpShared &re = rdata.g.regExp();
2136 :
2137 : Value tmp;
2138 341757 : if (!DoMatch(cx, res, rdata.str, re, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
2139 9 : return false;
2140 :
2141 341748 : if (!rdata.calledBack) {
2142 : /* Didn't match, so the string is unmodified. */
2143 185805 : args.rval() = StringValue(rdata.str);
2144 185805 : return true;
2145 : }
2146 :
2147 : JSSubString sub;
2148 155943 : res->getRightContext(&sub);
2149 155943 : if (!rdata.sb.append(sub.chars, sub.length))
2150 0 : return false;
2151 :
2152 155943 : JSString *retstr = rdata.sb.finishString();
2153 155943 : if (!retstr)
2154 0 : return false;
2155 :
2156 155943 : args.rval() = StringValue(retstr);
2157 155943 : return true;
2158 : }
2159 :
2160 : static inline bool
2161 0 : str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm)
2162 : {
2163 0 : JS_ASSERT(fm.match() >= 0);
2164 :
2165 0 : JSString *matchStr = js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength());
2166 0 : if (!matchStr)
2167 0 : return false;
2168 :
2169 : /* lambda(matchStr, matchStart, textstr) */
2170 : static const uint32_t lambdaArgc = 3;
2171 0 : if (!cx->stack.pushInvokeArgs(cx, lambdaArgc, &rdata.args))
2172 0 : return false;
2173 :
2174 0 : CallArgs &args = rdata.args;
2175 0 : args.calleev().setObject(*rdata.lambda);
2176 0 : args.thisv().setUndefined();
2177 :
2178 0 : Value *sp = args.array();
2179 0 : sp[0].setString(matchStr);
2180 0 : sp[1].setInt32(fm.match());
2181 0 : sp[2].setString(rdata.str);
2182 :
2183 0 : if (!Invoke(cx, rdata.args))
2184 0 : return false;
2185 :
2186 0 : JSString *repstr = ToString(cx, args.rval());
2187 0 : if (!repstr)
2188 0 : return false;
2189 :
2190 0 : JSString *leftSide = js_NewDependentString(cx, rdata.str, 0, fm.match());
2191 0 : if (!leftSide)
2192 0 : return false;
2193 :
2194 0 : size_t matchLimit = fm.match() + fm.patternLength();
2195 : JSString *rightSide = js_NewDependentString(cx, rdata.str, matchLimit,
2196 0 : rdata.str->length() - matchLimit);
2197 0 : if (!rightSide)
2198 0 : return false;
2199 :
2200 0 : RopeBuilder builder(cx);
2201 0 : if (!(builder.append(leftSide) &&
2202 0 : builder.append(repstr) &&
2203 0 : builder.append(rightSide))) {
2204 0 : return false;
2205 : }
2206 :
2207 0 : outerArgs.rval() = StringValue(builder.result());
2208 0 : return true;
2209 : }
2210 :
2211 : static const uint32_t ReplaceOptArg = 2;
2212 :
2213 : /*
2214 : * Pattern match the script to check if it is is indexing into a particular
2215 : * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
2216 : * such cases, which are used by javascript packers (particularly the popular
2217 : * Dean Edwards packer) to efficiently encode large scripts. We only handle the
2218 : * code patterns generated by such packers here.
2219 : */
2220 : static JSObject *
2221 2241 : LambdaIsGetElem(JSObject &lambda, JSContext *cx)
2222 : {
2223 2241 : if (!lambda.isFunction())
2224 0 : return NULL;
2225 :
2226 2241 : JSFunction *fun = lambda.toFunction();
2227 2241 : if (!fun->isInterpreted())
2228 90 : return NULL;
2229 :
2230 2151 : JSScript *script = fun->script();
2231 2151 : jsbytecode *pc = script->code;
2232 :
2233 : /* Look for an access to 'b' in the enclosing scope. */
2234 2151 : if (JSOp(*pc) != JSOP_NAME)
2235 1737 : return NULL;
2236 : PropertyName *bname;
2237 414 : GET_NAME_FROM_BYTECODE(script, pc, 0, bname);
2238 414 : pc += JSOP_NAME_LENGTH;
2239 :
2240 : /*
2241 : * Do a conservative search for 'b' in the enclosing scope. Avoid using a
2242 : * real name lookup since this can trigger observable effects.
2243 : */
2244 : Value b;
2245 414 : JSObject *scope = cx->stack.currentScriptedScopeChain();
2246 0 : while (true) {
2247 414 : if (scope->isCall()) {
2248 387 : if (scope->asCall().containsVarOrArg(bname, &b, cx))
2249 387 : break;
2250 27 : } else if (scope->isBlock()) {
2251 18 : if (scope->asClonedBlock().containsVar(bname, &b, cx))
2252 18 : break;
2253 : } else {
2254 9 : return NULL;
2255 : }
2256 0 : scope = &scope->asScope().enclosingScope();
2257 : }
2258 :
2259 : /* Look for 'a' to be the lambda's first argument. */
2260 405 : if (JSOp(*pc) != JSOP_GETARG || GET_SLOTNO(pc) != 0)
2261 234 : return NULL;
2262 171 : pc += JSOP_GETARG_LENGTH;
2263 :
2264 : /* 'b[a]' */
2265 171 : if (JSOp(*pc) != JSOP_GETELEM)
2266 0 : return NULL;
2267 171 : pc += JSOP_GETELEM_LENGTH;
2268 :
2269 : /* 'return b[a]' */
2270 171 : if (JSOp(*pc) != JSOP_RETURN)
2271 0 : return NULL;
2272 :
2273 : /* 'b' must behave like a normal object. */
2274 171 : if (!b.isObject())
2275 0 : return NULL;
2276 :
2277 171 : JSObject &bobj = b.toObject();
2278 171 : Class *clasp = bobj.getClass();
2279 171 : if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty)
2280 0 : return NULL;
2281 :
2282 171 : return &bobj;
2283 : }
2284 :
2285 : JSBool
2286 365526 : js::str_replace(JSContext *cx, unsigned argc, Value *vp)
2287 : {
2288 365526 : CallArgs args = CallArgsFromVp(argc, vp);
2289 :
2290 731052 : ReplaceData rdata(cx);
2291 365526 : rdata.str = ThisToStringForStringProto(cx, args);
2292 365526 : if (!rdata.str)
2293 0 : return false;
2294 :
2295 365526 : if (!rdata.g.init(cx, args))
2296 18 : return false;
2297 :
2298 : /* Extract replacement string/function. */
2299 365508 : if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) {
2300 2241 : rdata.lambda = &args[1].toObject();
2301 2241 : rdata.elembase = NULL;
2302 2241 : rdata.repstr = NULL;
2303 2241 : rdata.dollar = rdata.dollarEnd = NULL;
2304 :
2305 2241 : if (JSObject *base = LambdaIsGetElem(*rdata.lambda, cx))
2306 171 : rdata.elembase = base;
2307 : } else {
2308 363267 : rdata.lambda = NULL;
2309 363267 : rdata.elembase = NULL;
2310 363267 : rdata.repstr = ArgToRootedString(cx, args, 1);
2311 363267 : if (!rdata.repstr)
2312 0 : return false;
2313 :
2314 : /* We're about to store pointers into the middle of our string. */
2315 363267 : JSFixedString *fixed = rdata.repstr->ensureFixed(cx);
2316 363267 : if (!fixed)
2317 0 : return false;
2318 363267 : rdata.dollarEnd = fixed->chars() + fixed->length();
2319 363267 : rdata.dollar = js_strchr_limit(fixed->chars(), '$', rdata.dollarEnd);
2320 : }
2321 :
2322 : /*
2323 : * Unlike its |String.prototype| brethren, |replace| doesn't convert
2324 : * its input to a regular expression. (Even if it contains metachars.)
2325 : *
2326 : * However, if the user invokes our (non-standard) |flags| argument
2327 : * extension then we revert to creating a regular expression. Note that
2328 : * this is observable behavior through the side-effect mutation of the
2329 : * |RegExp| statics.
2330 : */
2331 :
2332 365508 : const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false);
2333 365508 : if (!fm) {
2334 341757 : if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */
2335 0 : return false;
2336 341757 : return str_replace_regexp(cx, args, rdata);
2337 : }
2338 :
2339 23751 : if (fm->match() < 0) {
2340 20295 : args.rval() = StringValue(rdata.str);
2341 20295 : return true;
2342 : }
2343 :
2344 3456 : if (rdata.lambda)
2345 0 : return str_replace_flat_lambda(cx, args, rdata, *fm);
2346 :
2347 : /*
2348 : * Note: we could optimize the text.length == pattern.length case if we wanted,
2349 : * even in the presence of dollar metachars.
2350 : */
2351 3456 : if (rdata.dollar)
2352 18 : return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, &args);
2353 :
2354 3438 : return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, &args);
2355 : }
2356 :
2357 : class SplitMatchResult {
2358 : size_t endIndex_;
2359 : size_t length_;
2360 :
2361 : public:
2362 11997 : void setFailure() {
2363 : JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH);
2364 11997 : endIndex_ = SIZE_MAX;
2365 11997 : }
2366 937863 : bool isFailure() const {
2367 937863 : return (endIndex_ == SIZE_MAX);
2368 : }
2369 308622 : size_t endIndex() const {
2370 308622 : JS_ASSERT(!isFailure());
2371 308622 : return endIndex_;
2372 : }
2373 308622 : size_t length() const {
2374 308622 : JS_ASSERT(!isFailure());
2375 308622 : return length_;
2376 : }
2377 308622 : void setResult(size_t length, size_t endIndex) {
2378 308622 : length_ = length;
2379 308622 : endIndex_ = endIndex;
2380 308622 : }
2381 : };
2382 :
2383 : template<class Matcher>
2384 : static JSObject *
2385 21096 : SplitHelper(JSContext *cx, JSLinearString *str, uint32_t limit, Matcher splitMatch, TypeObject *type)
2386 : {
2387 21096 : size_t strLength = str->length();
2388 : SplitMatchResult result;
2389 :
2390 : /* Step 11. */
2391 21096 : if (strLength == 0) {
2392 90 : if (!splitMatch(cx, str, 0, &result))
2393 0 : return NULL;
2394 :
2395 : /*
2396 : * NB: Unlike in the non-empty string case, it's perfectly fine
2397 : * (indeed the spec requires it) if we match at the end of the
2398 : * string. Thus these cases should hold:
2399 : *
2400 : * var a = "".split("");
2401 : * assertEq(a.length, 0);
2402 : * var b = "".split(/.?/);
2403 : * assertEq(b.length, 0);
2404 : */
2405 90 : if (!result.isFailure())
2406 0 : return NewDenseEmptyArray(cx);
2407 :
2408 90 : Value v = StringValue(str);
2409 90 : return NewDenseCopiedArray(cx, 1, &v);
2410 : }
2411 :
2412 : /* Step 12. */
2413 21006 : size_t lastEndIndex = 0;
2414 21006 : size_t index = 0;
2415 :
2416 : /* Step 13. */
2417 42012 : AutoValueVector splits(cx);
2418 :
2419 350634 : while (index < strLength) {
2420 : /* Step 13(a). */
2421 320529 : if (!splitMatch(cx, str, index, &result))
2422 0 : return NULL;
2423 :
2424 : /*
2425 : * Step 13(b).
2426 : *
2427 : * Our match algorithm differs from the spec in that it returns the
2428 : * next index at which a match happens. If no match happens we're
2429 : * done.
2430 : *
2431 : * But what if the match is at the end of the string (and the string is
2432 : * not empty)? Per 13(c)(ii) this shouldn't be a match, so we have to
2433 : * specially exclude it. Thus this case should hold:
2434 : *
2435 : * var a = "abc".split(/\b/);
2436 : * assertEq(a.length, 1);
2437 : * assertEq(a[0], "abc");
2438 : */
2439 320529 : if (result.isFailure())
2440 11907 : break;
2441 :
2442 : /* Step 13(c)(i). */
2443 308622 : size_t sepLength = result.length();
2444 308622 : size_t endIndex = result.endIndex();
2445 308622 : if (sepLength == 0 && endIndex == strLength)
2446 0 : break;
2447 :
2448 : /* Step 13(c)(ii). */
2449 308622 : if (endIndex == lastEndIndex) {
2450 99000 : index++;
2451 99000 : continue;
2452 : }
2453 :
2454 : /* Step 13(c)(iii). */
2455 209622 : JS_ASSERT(lastEndIndex < endIndex);
2456 209622 : JS_ASSERT(sepLength <= strLength);
2457 209622 : JS_ASSERT(lastEndIndex + sepLength <= endIndex);
2458 :
2459 : /* Steps 13(c)(iii)(1-3). */
2460 209622 : size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
2461 209622 : JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength);
2462 209622 : if (!sub || !splits.append(StringValue(sub)))
2463 0 : return NULL;
2464 :
2465 : /* Step 13(c)(iii)(4). */
2466 209622 : if (splits.length() == limit)
2467 0 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2468 :
2469 : /* Step 13(c)(iii)(5). */
2470 209622 : lastEndIndex = endIndex;
2471 :
2472 : /* Step 13(c)(iii)(6-7). */
2473 : if (Matcher::returnsCaptures) {
2474 30483 : RegExpStatics *res = cx->regExpStatics();
2475 30483 : for (size_t i = 0; i < res->parenCount(); i++) {
2476 : /* Steps 13(c)(iii)(7)(a-c). */
2477 0 : if (res->pairIsPresent(i + 1)) {
2478 : JSSubString parsub;
2479 0 : res->getParen(i + 1, &parsub);
2480 0 : sub = js_NewStringCopyN(cx, parsub.chars, parsub.length);
2481 0 : if (!sub || !splits.append(StringValue(sub)))
2482 0 : return NULL;
2483 : } else {
2484 : /* Only string entries have been accounted for so far. */
2485 0 : AddTypeProperty(cx, type, NULL, UndefinedValue());
2486 0 : if (!splits.append(UndefinedValue()))
2487 0 : return NULL;
2488 : }
2489 :
2490 : /* Step 13(c)(iii)(7)(d). */
2491 0 : if (splits.length() == limit)
2492 0 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2493 : }
2494 : }
2495 :
2496 : /* Step 13(c)(iii)(8). */
2497 209622 : index = lastEndIndex;
2498 : }
2499 :
2500 : /* Steps 14-15. */
2501 21006 : JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
2502 21006 : if (!sub || !splits.append(StringValue(sub)))
2503 0 : return NULL;
2504 :
2505 : /* Step 16. */
2506 21006 : return NewDenseCopiedArray(cx, splits.length(), splits.begin());
2507 : }
2508 :
2509 : /*
2510 : * The SplitMatch operation from ES5 15.5.4.14 is implemented using different
2511 : * paths for regular expression and string separators.
2512 : *
2513 : * The algorithm differs from the spec in that the we return the next index at
2514 : * which a match happens.
2515 : */
2516 : class SplitRegExpMatcher
2517 : {
2518 : RegExpShared &re;
2519 : RegExpStatics *res;
2520 :
2521 : public:
2522 11781 : SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
2523 :
2524 : static const bool returnsCaptures = true;
2525 :
2526 42165 : bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result)
2527 : {
2528 42165 : Value rval = UndefinedValue();
2529 42165 : const jschar *chars = str->chars();
2530 42165 : size_t length = str->length();
2531 42165 : if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
2532 0 : return false;
2533 42165 : if (!rval.isTrue()) {
2534 11682 : result->setFailure();
2535 11682 : return true;
2536 : }
2537 : JSSubString sep;
2538 30483 : res->getLastMatch(&sep);
2539 :
2540 30483 : result->setResult(sep.length, index);
2541 30483 : return true;
2542 : }
2543 : };
2544 :
2545 : class SplitStringMatcher
2546 : {
2547 : const jschar *sepChars;
2548 : size_t sepLength;
2549 :
2550 : public:
2551 9315 : SplitStringMatcher(JSLinearString *sep) {
2552 9315 : sepChars = sep->chars();
2553 9315 : sepLength = sep->length();
2554 9315 : }
2555 :
2556 : static const bool returnsCaptures = false;
2557 :
2558 278454 : bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res)
2559 : {
2560 278454 : JS_ASSERT(index == 0 || index < str->length());
2561 278454 : const jschar *chars = str->chars();
2562 278454 : int match = StringMatch(chars + index, str->length() - index, sepChars, sepLength);
2563 278454 : if (match == -1)
2564 315 : res->setFailure();
2565 : else
2566 278139 : res->setResult(sepLength, index + match + sepLength);
2567 278454 : return true;
2568 : }
2569 : };
2570 :
2571 : /* ES5 15.5.4.14 */
2572 : JSBool
2573 21114 : js::str_split(JSContext *cx, unsigned argc, Value *vp)
2574 : {
2575 21114 : CallArgs args = CallArgsFromVp(argc, vp);
2576 :
2577 : /* Steps 1-2. */
2578 21114 : JSString *str = ThisToStringForStringProto(cx, args);
2579 21114 : if (!str)
2580 0 : return false;
2581 :
2582 21114 : TypeObject *type = GetTypeCallerInitObject(cx, JSProto_Array);
2583 21114 : if (!type)
2584 0 : return false;
2585 21114 : AddTypeProperty(cx, type, NULL, Type::StringType());
2586 :
2587 : /* Step 5: Use the second argument as the split limit, if given. */
2588 : uint32_t limit;
2589 21114 : if (args.hasDefined(1)) {
2590 : double d;
2591 0 : if (!ToNumber(cx, args[1], &d))
2592 0 : return false;
2593 0 : limit = js_DoubleToECMAUint32(d);
2594 : } else {
2595 21114 : limit = UINT32_MAX;
2596 : }
2597 :
2598 : /* Step 8. */
2599 42228 : RegExpGuard re;
2600 21114 : JSLinearString *sepstr = NULL;
2601 21114 : bool sepDefined = args.hasDefined(0);
2602 21114 : if (sepDefined) {
2603 21114 : if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
2604 11781 : if (!RegExpToShared(cx, args[0].toObject(), &re))
2605 0 : return false;
2606 : } else {
2607 9333 : sepstr = ArgToRootedString(cx, args, 0);
2608 9333 : if (!sepstr)
2609 18 : return false;
2610 : }
2611 : }
2612 :
2613 : /* Step 9. */
2614 21096 : if (limit == 0) {
2615 0 : JSObject *aobj = NewDenseEmptyArray(cx);
2616 0 : if (!aobj)
2617 0 : return false;
2618 0 : aobj->setType(type);
2619 0 : args.rval() = ObjectValue(*aobj);
2620 0 : return true;
2621 : }
2622 :
2623 : /* Step 10. */
2624 21096 : if (!sepDefined) {
2625 0 : Value v = StringValue(str);
2626 0 : JSObject *aobj = NewDenseCopiedArray(cx, 1, &v);
2627 0 : if (!aobj)
2628 0 : return false;
2629 0 : aobj->setType(type);
2630 0 : args.rval() = ObjectValue(*aobj);
2631 0 : return true;
2632 : }
2633 21096 : JSLinearString *strlin = str->ensureLinear(cx);
2634 21096 : if (!strlin)
2635 0 : return false;
2636 :
2637 : /* Steps 11-15. */
2638 : JSObject *aobj;
2639 21096 : if (!re.initialized())
2640 9315 : aobj = SplitHelper(cx, strlin, limit, SplitStringMatcher(sepstr), type);
2641 : else
2642 11781 : aobj = SplitHelper(cx, strlin, limit, SplitRegExpMatcher(*re, cx->regExpStatics()), type);
2643 21096 : if (!aobj)
2644 0 : return false;
2645 :
2646 : /* Step 16. */
2647 21096 : aobj->setType(type);
2648 21096 : args.rval() = ObjectValue(*aobj);
2649 21096 : return true;
2650 : }
2651 :
2652 : static JSBool
2653 22563 : str_substr(JSContext *cx, unsigned argc, Value *vp)
2654 : {
2655 22563 : CallArgs args = CallArgsFromVp(argc, vp);
2656 22563 : JSString *str = ThisToStringForStringProto(cx, args);
2657 22563 : if (!str)
2658 0 : return false;
2659 :
2660 : int32_t length, len, begin;
2661 22563 : if (args.length() > 0) {
2662 22563 : length = int32_t(str->length());
2663 22563 : if (!ValueToIntegerRange(cx, args[0], &begin))
2664 0 : return false;
2665 :
2666 22563 : if (begin >= length) {
2667 0 : str = cx->runtime->emptyString;
2668 0 : goto out;
2669 : }
2670 22563 : if (begin < 0) {
2671 36 : begin += length; /* length + INT_MIN will always be less than 0 */
2672 36 : if (begin < 0)
2673 9 : begin = 0;
2674 : }
2675 :
2676 22563 : if (args.hasDefined(1)) {
2677 22509 : if (!ValueToIntegerRange(cx, args[1], &len))
2678 0 : return false;
2679 :
2680 22509 : if (len <= 0) {
2681 0 : str = cx->runtime->emptyString;
2682 0 : goto out;
2683 : }
2684 :
2685 22509 : if (uint32_t(length) < uint32_t(begin + len))
2686 27 : len = length - begin;
2687 : } else {
2688 54 : len = length - begin;
2689 : }
2690 :
2691 22563 : str = js_NewDependentString(cx, str, size_t(begin), size_t(len));
2692 22563 : if (!str)
2693 0 : return false;
2694 : }
2695 :
2696 : out:
2697 22563 : args.rval() = StringValue(str);
2698 22563 : return true;
2699 : }
2700 :
2701 : /*
2702 : * Python-esque sequence operations.
2703 : */
2704 : static JSBool
2705 108 : str_concat(JSContext *cx, unsigned argc, Value *vp)
2706 : {
2707 108 : CallArgs args = CallArgsFromVp(argc, vp);
2708 108 : JSString *str = ThisToStringForStringProto(cx, args);
2709 108 : if (!str)
2710 0 : return false;
2711 :
2712 207 : for (unsigned i = 0; i < args.length(); i++) {
2713 108 : JSString *argStr = ToString(cx, args[i]);
2714 108 : if (!argStr)
2715 9 : return false;
2716 :
2717 99 : str = js_ConcatStrings(cx, str, argStr);
2718 99 : if (!str)
2719 0 : return false;
2720 : }
2721 :
2722 99 : args.rval() = StringValue(str);
2723 99 : return true;
2724 : }
2725 :
2726 : static JSBool
2727 567 : str_slice(JSContext *cx, unsigned argc, Value *vp)
2728 : {
2729 567 : CallArgs args = CallArgsFromVp(argc, vp);
2730 :
2731 567 : if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) {
2732 : size_t begin, end, length;
2733 :
2734 0 : JSString *str = args.thisv().toString();
2735 0 : begin = args[0].toInt32();
2736 0 : end = str->length();
2737 0 : if (begin <= end) {
2738 0 : length = end - begin;
2739 0 : if (length == 0) {
2740 0 : str = cx->runtime->emptyString;
2741 : } else {
2742 : str = (length == 1)
2743 0 : ? cx->runtime->staticStrings.getUnitStringForElement(cx, str, begin)
2744 0 : : js_NewDependentString(cx, str, begin, length);
2745 0 : if (!str)
2746 0 : return false;
2747 : }
2748 0 : args.rval() = StringValue(str);
2749 0 : return true;
2750 : }
2751 : }
2752 :
2753 567 : JSString *str = ThisToStringForStringProto(cx, args);
2754 567 : if (!str)
2755 0 : return false;
2756 :
2757 567 : if (args.length() != 0) {
2758 : double begin, end, length;
2759 :
2760 567 : if (!ToInteger(cx, args[0], &begin))
2761 0 : return false;
2762 567 : length = str->length();
2763 567 : if (begin < 0) {
2764 0 : begin += length;
2765 0 : if (begin < 0)
2766 0 : begin = 0;
2767 567 : } else if (begin > length) {
2768 0 : begin = length;
2769 : }
2770 :
2771 567 : if (args.hasDefined(1)) {
2772 567 : if (!ToInteger(cx, args[1], &end))
2773 0 : return false;
2774 567 : if (end < 0) {
2775 567 : end += length;
2776 567 : if (end < 0)
2777 0 : end = 0;
2778 0 : } else if (end > length) {
2779 0 : end = length;
2780 : }
2781 567 : if (end < begin)
2782 0 : end = begin;
2783 : } else {
2784 0 : end = length;
2785 : }
2786 :
2787 : str = js_NewDependentString(cx, str,
2788 : (size_t)begin,
2789 567 : (size_t)(end - begin));
2790 567 : if (!str)
2791 0 : return false;
2792 : }
2793 567 : args.rval() = StringValue(str);
2794 567 : return true;
2795 : }
2796 :
2797 : #if JS_HAS_STR_HTML_HELPERS
2798 : /*
2799 : * HTML composition aids.
2800 : */
2801 : static bool
2802 0 : tagify(JSContext *cx, const char *begin, JSLinearString *param, const char *end,
2803 : CallReceiver call)
2804 : {
2805 0 : JSString *thisstr = ThisToStringForStringProto(cx, call);
2806 0 : if (!thisstr)
2807 0 : return false;
2808 :
2809 0 : JSLinearString *str = thisstr->ensureLinear(cx);
2810 0 : if (!str)
2811 0 : return false;
2812 :
2813 0 : if (!end)
2814 0 : end = begin;
2815 :
2816 0 : size_t beglen = strlen(begin);
2817 0 : size_t taglen = 1 + beglen + 1; /* '<begin' + '>' */
2818 0 : size_t parlen = 0; /* Avoid warning. */
2819 0 : if (param) {
2820 0 : parlen = param->length();
2821 0 : taglen += 2 + parlen + 1; /* '="param"' */
2822 : }
2823 0 : size_t endlen = strlen(end);
2824 0 : taglen += str->length() + 2 + endlen + 1; /* 'str</end>' */
2825 :
2826 0 : if (taglen >= ~(size_t)0 / sizeof(jschar)) {
2827 0 : js_ReportAllocationOverflow(cx);
2828 0 : return false;
2829 : }
2830 :
2831 0 : jschar *tagbuf = (jschar *) cx->malloc_((taglen + 1) * sizeof(jschar));
2832 0 : if (!tagbuf)
2833 0 : return false;
2834 :
2835 0 : size_t j = 0;
2836 0 : tagbuf[j++] = '<';
2837 0 : for (size_t i = 0; i < beglen; i++)
2838 0 : tagbuf[j++] = (jschar)begin[i];
2839 0 : if (param) {
2840 0 : tagbuf[j++] = '=';
2841 0 : tagbuf[j++] = '"';
2842 0 : js_strncpy(&tagbuf[j], param->chars(), parlen);
2843 0 : j += parlen;
2844 0 : tagbuf[j++] = '"';
2845 : }
2846 0 : tagbuf[j++] = '>';
2847 :
2848 0 : js_strncpy(&tagbuf[j], str->chars(), str->length());
2849 0 : j += str->length();
2850 0 : tagbuf[j++] = '<';
2851 0 : tagbuf[j++] = '/';
2852 0 : for (size_t i = 0; i < endlen; i++)
2853 0 : tagbuf[j++] = (jschar)end[i];
2854 0 : tagbuf[j++] = '>';
2855 0 : JS_ASSERT(j == taglen);
2856 0 : tagbuf[j] = 0;
2857 :
2858 0 : JSString *retstr = js_NewString(cx, tagbuf, taglen);
2859 0 : if (!retstr) {
2860 0 : Foreground::free_((char *)tagbuf);
2861 0 : return false;
2862 : }
2863 0 : call.rval() = StringValue(retstr);
2864 0 : return true;
2865 : }
2866 :
2867 : static JSBool
2868 0 : tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end)
2869 : {
2870 0 : JSLinearString *param = ArgToRootedString(cx, args, 0);
2871 0 : if (!param)
2872 0 : return false;
2873 :
2874 0 : return tagify(cx, begin, param, end, args);
2875 : }
2876 :
2877 : static JSBool
2878 0 : str_bold(JSContext *cx, unsigned argc, Value *vp)
2879 : {
2880 0 : return tagify(cx, "b", NULL, NULL, CallReceiverFromVp(vp));
2881 : }
2882 :
2883 : static JSBool
2884 0 : str_italics(JSContext *cx, unsigned argc, Value *vp)
2885 : {
2886 0 : return tagify(cx, "i", NULL, NULL, CallReceiverFromVp(vp));
2887 : }
2888 :
2889 : static JSBool
2890 0 : str_fixed(JSContext *cx, unsigned argc, Value *vp)
2891 : {
2892 0 : return tagify(cx, "tt", NULL, NULL, CallReceiverFromVp(vp));
2893 : }
2894 :
2895 : static JSBool
2896 0 : str_fontsize(JSContext *cx, unsigned argc, Value *vp)
2897 : {
2898 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font");
2899 : }
2900 :
2901 : static JSBool
2902 0 : str_fontcolor(JSContext *cx, unsigned argc, Value *vp)
2903 : {
2904 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font");
2905 : }
2906 :
2907 : static JSBool
2908 0 : str_link(JSContext *cx, unsigned argc, Value *vp)
2909 : {
2910 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a");
2911 : }
2912 :
2913 : static JSBool
2914 0 : str_anchor(JSContext *cx, unsigned argc, Value *vp)
2915 : {
2916 0 : return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a");
2917 : }
2918 :
2919 : static JSBool
2920 0 : str_strike(JSContext *cx, unsigned argc, Value *vp)
2921 : {
2922 0 : return tagify(cx, "strike", NULL, NULL, CallReceiverFromVp(vp));
2923 : }
2924 :
2925 : static JSBool
2926 0 : str_small(JSContext *cx, unsigned argc, Value *vp)
2927 : {
2928 0 : return tagify(cx, "small", NULL, NULL, CallReceiverFromVp(vp));
2929 : }
2930 :
2931 : static JSBool
2932 0 : str_big(JSContext *cx, unsigned argc, Value *vp)
2933 : {
2934 0 : return tagify(cx, "big", NULL, NULL, CallReceiverFromVp(vp));
2935 : }
2936 :
2937 : static JSBool
2938 0 : str_blink(JSContext *cx, unsigned argc, Value *vp)
2939 : {
2940 0 : return tagify(cx, "blink", NULL, NULL, CallReceiverFromVp(vp));
2941 : }
2942 :
2943 : static JSBool
2944 0 : str_sup(JSContext *cx, unsigned argc, Value *vp)
2945 : {
2946 0 : return tagify(cx, "sup", NULL, NULL, CallReceiverFromVp(vp));
2947 : }
2948 :
2949 : static JSBool
2950 0 : str_sub(JSContext *cx, unsigned argc, Value *vp)
2951 : {
2952 0 : return tagify(cx, "sub", NULL, NULL, CallReceiverFromVp(vp));
2953 : }
2954 : #endif /* JS_HAS_STR_HTML_HELPERS */
2955 :
2956 : static JSFunctionSpec string_methods[] = {
2957 : #if JS_HAS_TOSOURCE
2958 : JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE),
2959 : JS_FN(js_toSource_str, str_toSource, 0,0),
2960 : #endif
2961 :
2962 : /* Java-like methods. */
2963 : JS_FN(js_toString_str, js_str_toString, 0,0),
2964 : JS_FN(js_valueOf_str, js_str_toString, 0,0),
2965 : JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE),
2966 : JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE),
2967 : JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE),
2968 : JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE),
2969 : JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE),
2970 : JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE),
2971 : JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
2972 : JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE),
2973 : JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE),
2974 : JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE),
2975 : JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE),
2976 : JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE),
2977 : JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE),
2978 :
2979 : /* Perl-ish methods (search is actually Python-esque). */
2980 : JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE),
2981 : JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE),
2982 : JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE),
2983 : JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE),
2984 : JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE),
2985 :
2986 : /* Python-esque sequence methods. */
2987 : JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE),
2988 : JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE),
2989 :
2990 : /* HTML string methods. */
2991 : #if JS_HAS_STR_HTML_HELPERS
2992 : JS_FN("bold", str_bold, 0,0),
2993 : JS_FN("italics", str_italics, 0,0),
2994 : JS_FN("fixed", str_fixed, 0,0),
2995 : JS_FN("fontsize", str_fontsize, 1,0),
2996 : JS_FN("fontcolor", str_fontcolor, 1,0),
2997 : JS_FN("link", str_link, 1,0),
2998 : JS_FN("anchor", str_anchor, 1,0),
2999 : JS_FN("strike", str_strike, 0,0),
3000 : JS_FN("small", str_small, 0,0),
3001 : JS_FN("big", str_big, 0,0),
3002 : JS_FN("blink", str_blink, 0,0),
3003 : JS_FN("sup", str_sup, 0,0),
3004 : JS_FN("sub", str_sub, 0,0),
3005 : #endif
3006 :
3007 : JS_FS_END
3008 : };
3009 :
3010 : JSBool
3011 349096 : js_String(JSContext *cx, unsigned argc, Value *vp)
3012 : {
3013 349096 : CallArgs args = CallArgsFromVp(argc, vp);
3014 :
3015 : JSString *str;
3016 349096 : if (args.length() > 0) {
3017 348664 : str = ToString(cx, args[0]);
3018 348664 : if (!str)
3019 0 : return false;
3020 : } else {
3021 432 : str = cx->runtime->emptyString;
3022 : }
3023 :
3024 349096 : if (IsConstructing(args)) {
3025 199206 : StringObject *strobj = StringObject::create(cx, str);
3026 199206 : if (!strobj)
3027 0 : return false;
3028 199206 : args.rval() = ObjectValue(*strobj);
3029 199206 : return true;
3030 : }
3031 :
3032 149890 : args.rval() = StringValue(str);
3033 149890 : return true;
3034 : }
3035 :
3036 : JSBool
3037 1219476 : js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp)
3038 : {
3039 1219476 : CallArgs args = CallArgsFromVp(argc, vp);
3040 :
3041 1219476 : JS_ASSERT(args.length() <= StackSpace::ARGS_LENGTH_MAX);
3042 1219476 : if (args.length() == 1) {
3043 : uint16_t code;
3044 1219476 : if (!ValueToUint16(cx, args[0], &code))
3045 9 : return JS_FALSE;
3046 1219467 : if (StaticStrings::hasUnit(code)) {
3047 44427 : args.rval() = StringValue(cx->runtime->staticStrings.getUnit(code));
3048 44427 : return JS_TRUE;
3049 : }
3050 1175040 : args[0].setInt32(code);
3051 : }
3052 1175040 : jschar *chars = (jschar *) cx->malloc_((args.length() + 1) * sizeof(jschar));
3053 1175040 : if (!chars)
3054 0 : return JS_FALSE;
3055 2350080 : for (unsigned i = 0; i < args.length(); i++) {
3056 : uint16_t code;
3057 1175040 : if (!ValueToUint16(cx, args[i], &code)) {
3058 0 : cx->free_(chars);
3059 0 : return JS_FALSE;
3060 : }
3061 1175040 : chars[i] = (jschar)code;
3062 : }
3063 1175040 : chars[args.length()] = 0;
3064 1175040 : JSString *str = js_NewString(cx, chars, args.length());
3065 1175040 : if (!str) {
3066 0 : cx->free_(chars);
3067 0 : return JS_FALSE;
3068 : }
3069 :
3070 1175040 : args.rval() = StringValue(str);
3071 1175040 : return JS_TRUE;
3072 : }
3073 :
3074 : static JSFunctionSpec string_static_methods[] = {
3075 : JS_FN("fromCharCode", js::str_fromCharCode, 1, 0),
3076 : JS_FS_END
3077 : };
3078 :
3079 : Shape *
3080 3721 : StringObject::assignInitialShape(JSContext *cx)
3081 : {
3082 3721 : JS_ASSERT(nativeEmpty());
3083 :
3084 3721 : return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom),
3085 3721 : LENGTH_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
3086 : }
3087 :
3088 : JSObject *
3089 2266 : js_InitStringClass(JSContext *cx, JSObject *obj)
3090 : {
3091 2266 : JS_ASSERT(obj->isNative());
3092 :
3093 2266 : GlobalObject *global = &obj->asGlobal();
3094 :
3095 2266 : JSObject *proto = global->createBlankPrototype(cx, &StringClass);
3096 2266 : if (!proto || !proto->asString().init(cx, cx->runtime->emptyString))
3097 0 : return NULL;
3098 :
3099 : /* Now create the String function. */
3100 2266 : JSFunction *ctor = global->createConstructor(cx, js_String, CLASS_ATOM(cx, String), 1);
3101 2266 : if (!ctor)
3102 0 : return NULL;
3103 :
3104 2266 : if (!LinkConstructorAndPrototype(cx, ctor, proto))
3105 0 : return NULL;
3106 :
3107 4532 : if (!DefinePropertiesAndBrand(cx, proto, NULL, string_methods) ||
3108 2266 : !DefinePropertiesAndBrand(cx, ctor, NULL, string_static_methods))
3109 : {
3110 0 : return NULL;
3111 : }
3112 :
3113 : /* Capture normal data properties pregenerated for String objects. */
3114 2266 : TypeObject *type = proto->getNewType(cx);
3115 2266 : if (!type)
3116 0 : return NULL;
3117 2266 : AddTypeProperty(cx, type, "length", Type::Int32Type());
3118 :
3119 2266 : if (!DefineConstructorAndPrototype(cx, global, JSProto_String, ctor, proto))
3120 0 : return NULL;
3121 :
3122 : /*
3123 : * Define escape/unescape, the URI encode/decode functions, and maybe
3124 : * uneval on the global object.
3125 : */
3126 2266 : if (!JS_DefineFunctions(cx, global, string_functions))
3127 0 : return NULL;
3128 :
3129 2266 : return proto;
3130 : }
3131 :
3132 : JSFixedString *
3133 6777523 : js_NewString(JSContext *cx, jschar *chars, size_t length)
3134 : {
3135 6777523 : JSFixedString *s = JSFixedString::new_(cx, chars, length);
3136 6777523 : if (s)
3137 6777523 : Probes::createString(cx, s, length);
3138 6777523 : return s;
3139 : }
3140 :
3141 : static JSInlineString *
3142 84974 : NewShortString(JSContext *cx, const char *chars, size_t length)
3143 : {
3144 84974 : JS_ASSERT(JSShortString::lengthFits(length));
3145 84974 : JSInlineString *str = JSInlineString::lengthFits(length)
3146 : ? JSInlineString::new_(cx)
3147 84974 : : JSShortString::new_(cx);
3148 84974 : if (!str)
3149 0 : return NULL;
3150 :
3151 84974 : jschar *storage = str->init(length);
3152 84974 : if (js_CStringsAreUTF8) {
3153 : #ifdef DEBUG
3154 0 : size_t oldLength = length;
3155 : #endif
3156 0 : if (!InflateUTF8StringToBuffer(cx, chars, length, storage, &length))
3157 0 : return NULL;
3158 0 : JS_ASSERT(length <= oldLength);
3159 0 : storage[length] = 0;
3160 0 : str->resetLength(length);
3161 : } else {
3162 84974 : size_t n = length;
3163 84974 : jschar *p = storage;
3164 837442 : while (n--)
3165 667494 : *p++ = (unsigned char)*chars++;
3166 84974 : *p = 0;
3167 : }
3168 84974 : Probes::createString(cx, str, length);
3169 84974 : return str;
3170 : }
3171 :
3172 : JSLinearString *
3173 1933981 : js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
3174 : {
3175 1933981 : if (length == 0)
3176 40843 : return cx->runtime->emptyString;
3177 :
3178 1893138 : JSLinearString *base = baseArg->ensureLinear(cx);
3179 1893138 : if (!base)
3180 0 : return NULL;
3181 :
3182 1893138 : if (start == 0 && length == base->length())
3183 95011 : return base;
3184 :
3185 1798127 : const jschar *chars = base->chars() + start;
3186 :
3187 1798127 : if (JSLinearString *staticStr = cx->runtime->staticStrings.lookup(chars, length))
3188 934983 : return staticStr;
3189 :
3190 863144 : JSLinearString *s = JSDependentString::new_(cx, base, chars, length);
3191 863144 : Probes::createString(cx, s, length);
3192 863144 : return s;
3193 : }
3194 :
3195 : JSFixedString *
3196 95203845 : js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n)
3197 : {
3198 95203845 : if (JSShortString::lengthFits(n))
3199 91949158 : return NewShortString(cx, s, n);
3200 :
3201 3254687 : jschar *news = (jschar *) cx->malloc_((n + 1) * sizeof(jschar));
3202 3254687 : if (!news)
3203 0 : return NULL;
3204 3254687 : js_strncpy(news, s, n);
3205 3254687 : news[n] = 0;
3206 3254687 : JSFixedString *str = js_NewString(cx, news, n);
3207 3254687 : if (!str)
3208 0 : cx->free_(news);
3209 3254687 : return str;
3210 : }
3211 :
3212 : JSFixedString *
3213 212038 : js_NewStringCopyN(JSContext *cx, const char *s, size_t n)
3214 : {
3215 212038 : if (JSShortString::lengthFits(n))
3216 84974 : return NewShortString(cx, s, n);
3217 :
3218 127064 : jschar *chars = InflateString(cx, s, &n);
3219 127064 : if (!chars)
3220 0 : return NULL;
3221 127064 : JSFixedString *str = js_NewString(cx, chars, n);
3222 127064 : if (!str)
3223 0 : cx->free_(chars);
3224 127064 : return str;
3225 : }
3226 :
3227 : JSFixedString *
3228 0 : js_NewStringCopyZ(JSContext *cx, const jschar *s)
3229 : {
3230 0 : size_t n = js_strlen(s);
3231 0 : if (JSShortString::lengthFits(n))
3232 0 : return NewShortString(cx, s, n);
3233 :
3234 0 : size_t m = (n + 1) * sizeof(jschar);
3235 0 : jschar *news = (jschar *) cx->malloc_(m);
3236 0 : if (!news)
3237 0 : return NULL;
3238 0 : js_memcpy(news, s, m);
3239 0 : JSFixedString *str = js_NewString(cx, news, n);
3240 0 : if (!str)
3241 0 : cx->free_(news);
3242 0 : return str;
3243 : }
3244 :
3245 : JSFixedString *
3246 212029 : js_NewStringCopyZ(JSContext *cx, const char *s)
3247 : {
3248 212029 : return js_NewStringCopyN(cx, s, strlen(s));
3249 : }
3250 :
3251 : const char *
3252 20566 : js_ValueToPrintable(JSContext *cx, const Value &v, JSAutoByteString *bytes, bool asSource)
3253 : {
3254 : JSString *str;
3255 :
3256 20566 : str = (asSource ? js_ValueToSource : ToString)(cx, v);
3257 20566 : if (!str)
3258 0 : return NULL;
3259 20566 : str = js_QuoteString(cx, str, 0);
3260 20566 : if (!str)
3261 0 : return NULL;
3262 20566 : return bytes->encode(cx, str);
3263 : }
3264 :
3265 : JSString *
3266 3838935 : js::ToStringSlow(JSContext *cx, const Value &arg)
3267 : {
3268 : /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
3269 3838935 : JS_ASSERT(!arg.isString());
3270 :
3271 3838935 : Value v = arg;
3272 3838935 : if (!ToPrimitive(cx, JSTYPE_STRING, &v))
3273 5433 : return NULL;
3274 :
3275 : JSString *str;
3276 3833502 : if (v.isString()) {
3277 36229 : str = v.toString();
3278 3797273 : } else if (v.isInt32()) {
3279 877957 : str = js_IntToString(cx, v.toInt32());
3280 2919316 : } else if (v.isDouble()) {
3281 2848603 : str = js_NumberToString(cx, v.toDouble());
3282 70713 : } else if (v.isBoolean()) {
3283 3069 : str = js_BooleanToString(cx, v.toBoolean());
3284 67644 : } else if (v.isNull()) {
3285 873 : str = cx->runtime->atomState.nullAtom;
3286 : } else {
3287 66771 : str = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
3288 : }
3289 3833502 : return str;
3290 : }
3291 :
3292 : JS_FRIEND_API(JSString *)
3293 11323 : js_ValueToSource(JSContext *cx, const Value &v)
3294 : {
3295 11323 : JS_CHECK_RECURSION(cx, return NULL);
3296 :
3297 11323 : if (v.isUndefined())
3298 351 : return cx->runtime->atomState.void0Atom;
3299 10972 : if (v.isString())
3300 2286 : return js_QuoteString(cx, v.toString(), '"');
3301 8686 : if (v.isPrimitive()) {
3302 : /* Special case to preserve negative zero, _contra_ toString. */
3303 3798 : if (v.isDouble() && JSDOUBLE_IS_NEGZERO(v.toDouble())) {
3304 : /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
3305 : static const jschar js_negzero_ucNstr[] = {'-', '0'};
3306 :
3307 9 : return js_NewStringCopyN(cx, js_negzero_ucNstr, 2);
3308 : }
3309 3789 : return ToString(cx, v);
3310 : }
3311 :
3312 4888 : Value rval = NullValue();
3313 : Value fval;
3314 4888 : jsid id = ATOM_TO_JSID(cx->runtime->atomState.toSourceAtom);
3315 4888 : if (!js_GetMethod(cx, &v.toObject(), id, 0, &fval))
3316 0 : return NULL;
3317 4888 : if (js_IsCallable(fval)) {
3318 4888 : if (!Invoke(cx, v, fval, 0, NULL, &rval))
3319 18 : return NULL;
3320 : }
3321 :
3322 4870 : return ToString(cx, rval);
3323 : }
3324 :
3325 : namespace js {
3326 :
3327 : bool
3328 1632114 : EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result)
3329 : {
3330 1632114 : if (str1 == str2) {
3331 130408 : *result = true;
3332 130408 : return true;
3333 : }
3334 :
3335 1501706 : size_t length1 = str1->length();
3336 1501706 : if (length1 != str2->length()) {
3337 731666 : *result = false;
3338 731666 : return true;
3339 : }
3340 :
3341 770040 : JSLinearString *linear1 = str1->ensureLinear(cx);
3342 770040 : if (!linear1)
3343 0 : return false;
3344 770040 : JSLinearString *linear2 = str2->ensureLinear(cx);
3345 770040 : if (!linear2)
3346 0 : return false;
3347 :
3348 770040 : *result = PodEqual(linear1->chars(), linear2->chars(), length1);
3349 770040 : return true;
3350 : }
3351 :
3352 : bool
3353 116020 : EqualStrings(JSLinearString *str1, JSLinearString *str2)
3354 : {
3355 116020 : if (str1 == str2)
3356 432 : return true;
3357 :
3358 115588 : size_t length1 = str1->length();
3359 115588 : if (length1 != str2->length())
3360 39708 : return false;
3361 :
3362 75880 : return PodEqual(str1->chars(), str2->chars(), length1);
3363 : }
3364 :
3365 : } /* namespace js */
3366 :
3367 : namespace js {
3368 :
3369 : static bool
3370 331072 : CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
3371 : {
3372 331072 : JS_ASSERT(str1);
3373 331072 : JS_ASSERT(str2);
3374 :
3375 331072 : if (str1 == str2) {
3376 2554 : *result = 0;
3377 2554 : return true;
3378 : }
3379 :
3380 328518 : const jschar *s1 = str1->getChars(cx);
3381 328518 : if (!s1)
3382 0 : return false;
3383 :
3384 328518 : const jschar *s2 = str2->getChars(cx);
3385 328518 : if (!s2)
3386 0 : return false;
3387 :
3388 328518 : return CompareChars(s1, str1->length(), s2, str2->length(), result);
3389 : }
3390 :
3391 : bool
3392 331072 : CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
3393 : {
3394 331072 : return CompareStringsImpl(cx, str1, str2, result);
3395 : }
3396 :
3397 : } /* namespace js */
3398 :
3399 : namespace js {
3400 :
3401 : bool
3402 9440 : StringEqualsAscii(JSLinearString *str, const char *asciiBytes)
3403 : {
3404 9440 : size_t length = strlen(asciiBytes);
3405 : #ifdef DEBUG
3406 154818 : for (size_t i = 0; i != length; ++i)
3407 145378 : JS_ASSERT(unsigned(asciiBytes[i]) <= 127);
3408 : #endif
3409 9440 : if (length != str->length())
3410 4681 : return false;
3411 4759 : const jschar *chars = str->chars();
3412 76467 : for (size_t i = 0; i != length; ++i) {
3413 71719 : if (unsigned(asciiBytes[i]) != unsigned(chars[i]))
3414 11 : return false;
3415 : }
3416 4748 : return true;
3417 : }
3418 :
3419 : } /* namespacejs */
3420 :
3421 : size_t
3422 57843 : js_strlen(const jschar *s)
3423 : {
3424 : const jschar *t;
3425 :
3426 1132549 : for (t = s; *t != 0; t++)
3427 1074706 : continue;
3428 57843 : return (size_t)(t - s);
3429 : }
3430 :
3431 : jschar *
3432 0 : js_strchr(const jschar *s, jschar c)
3433 : {
3434 0 : while (*s != 0) {
3435 0 : if (*s == c)
3436 0 : return (jschar *)s;
3437 0 : s++;
3438 : }
3439 0 : return NULL;
3440 : }
3441 :
3442 : jschar *
3443 366779 : js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
3444 : {
3445 3721900 : while (s < limit) {
3446 2989197 : if (*s == c)
3447 855 : return (jschar *)s;
3448 2988342 : s++;
3449 : }
3450 365924 : return NULL;
3451 : }
3452 :
3453 : namespace js {
3454 :
3455 : jschar *
3456 2993986 : InflateString(JSContext *cx, const char *bytes, size_t *lengthp, FlationCoding fc)
3457 : {
3458 : size_t nchars;
3459 : jschar *chars;
3460 2993986 : size_t nbytes = *lengthp;
3461 :
3462 2993986 : if (js_CStringsAreUTF8 || fc == CESU8Encoding) {
3463 13186 : if (!InflateUTF8StringToBuffer(cx, bytes, nbytes, NULL, &nchars, fc))
3464 0 : goto bad;
3465 13186 : chars = (jschar *) cx->malloc_((nchars + 1) * sizeof (jschar));
3466 13186 : if (!chars)
3467 0 : goto bad;
3468 13186 : JS_ALWAYS_TRUE(InflateUTF8StringToBuffer(cx, bytes, nbytes, chars, &nchars, fc));
3469 : } else {
3470 2980800 : nchars = nbytes;
3471 2980800 : chars = (jschar *) cx->malloc_((nchars + 1) * sizeof(jschar));
3472 2980800 : if (!chars)
3473 0 : goto bad;
3474 181660130 : for (size_t i = 0; i < nchars; i++)
3475 178679330 : chars[i] = (unsigned char) bytes[i];
3476 : }
3477 2993986 : *lengthp = nchars;
3478 2993986 : chars[nchars] = 0;
3479 2993986 : return chars;
3480 :
3481 : bad:
3482 : /*
3483 : * For compatibility with callers of JS_DecodeBytes we must zero lengthp
3484 : * on errors.
3485 : */
3486 0 : *lengthp = 0;
3487 0 : return NULL;
3488 : }
3489 :
3490 : /*
3491 : * May be called with null cx.
3492 : */
3493 : char *
3494 67839 : DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
3495 : {
3496 : size_t nbytes, i;
3497 : char *bytes;
3498 :
3499 67839 : if (js_CStringsAreUTF8) {
3500 20 : nbytes = GetDeflatedStringLength(cx, chars, nchars);
3501 20 : if (nbytes == (size_t) -1)
3502 0 : return NULL;
3503 20 : bytes = (char *) (cx ? cx->malloc_(nbytes + 1) : OffTheBooks::malloc_(nbytes + 1));
3504 20 : if (!bytes)
3505 0 : return NULL;
3506 20 : JS_ALWAYS_TRUE(DeflateStringToBuffer(cx, chars, nchars, bytes, &nbytes));
3507 : } else {
3508 67819 : nbytes = nchars;
3509 67819 : bytes = (char *) (cx ? cx->malloc_(nbytes + 1) : OffTheBooks::malloc_(nbytes + 1));
3510 67819 : if (!bytes)
3511 0 : return NULL;
3512 1105879 : for (i = 0; i < nbytes; i++)
3513 1038060 : bytes[i] = (char) chars[i];
3514 : }
3515 67839 : bytes[nbytes] = 0;
3516 67839 : return bytes;
3517 : }
3518 :
3519 : size_t
3520 417 : GetDeflatedStringLength(JSContext *cx, const jschar *chars, size_t nchars)
3521 : {
3522 417 : if (!js_CStringsAreUTF8)
3523 396 : return nchars;
3524 :
3525 21 : return GetDeflatedUTF8StringLength(cx, chars, nchars);
3526 : }
3527 :
3528 : /*
3529 : * May be called with null cx through public API, see below.
3530 : */
3531 : size_t
3532 21 : GetDeflatedUTF8StringLength(JSContext *cx, const jschar *chars,
3533 : size_t nchars, FlationCoding fc)
3534 : {
3535 : size_t nbytes;
3536 : const jschar *end;
3537 : unsigned c, c2;
3538 : char buffer[10];
3539 21 : bool useCESU8 = fc == CESU8Encoding;
3540 :
3541 21 : nbytes = nchars;
3542 524 : for (end = chars + nchars; chars != end; chars++) {
3543 503 : c = *chars;
3544 503 : if (c < 0x80)
3545 502 : continue;
3546 1 : if (0xD800 <= c && c <= 0xDFFF && !useCESU8) {
3547 : /* Surrogate pair. */
3548 1 : chars++;
3549 :
3550 : /* nbytes sets 1 length since this is surrogate pair. */
3551 1 : nbytes--;
3552 1 : if (c >= 0xDC00 || chars == end)
3553 : goto bad_surrogate;
3554 1 : c2 = *chars;
3555 1 : if (c2 < 0xDC00 || c2 > 0xDFFF)
3556 : goto bad_surrogate;
3557 1 : c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3558 : }
3559 1 : c >>= 11;
3560 1 : nbytes++;
3561 4 : while (c) {
3562 2 : c >>= 5;
3563 2 : nbytes++;
3564 : }
3565 : }
3566 21 : return nbytes;
3567 :
3568 : bad_surrogate:
3569 0 : if (cx) {
3570 0 : JS_snprintf(buffer, 10, "0x%x", c);
3571 : JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
3572 0 : NULL, JSMSG_BAD_SURROGATE_CHAR, buffer);
3573 : }
3574 0 : return (size_t) -1;
3575 : }
3576 :
3577 : bool
3578 417 : DeflateStringToBuffer(JSContext *cx, const jschar *src, size_t srclen,
3579 : char *dst, size_t *dstlenp)
3580 : {
3581 : size_t dstlen, i;
3582 :
3583 417 : dstlen = *dstlenp;
3584 417 : if (!js_CStringsAreUTF8) {
3585 396 : if (srclen > dstlen) {
3586 0 : for (i = 0; i < dstlen; i++)
3587 0 : dst[i] = (char) src[i];
3588 0 : if (cx) {
3589 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3590 0 : JSMSG_BUFFER_TOO_SMALL);
3591 : }
3592 0 : return JS_FALSE;
3593 : }
3594 25947 : for (i = 0; i < srclen; i++)
3595 25551 : dst[i] = (char) src[i];
3596 396 : *dstlenp = srclen;
3597 396 : return JS_TRUE;
3598 : }
3599 :
3600 21 : return DeflateStringToUTF8Buffer(cx, src, srclen, dst, dstlenp);
3601 : }
3602 :
3603 : bool
3604 21 : DeflateStringToUTF8Buffer(JSContext *cx, const jschar *src, size_t srclen,
3605 : char *dst, size_t *dstlenp, FlationCoding fc)
3606 : {
3607 : size_t i, utf8Len;
3608 : jschar c, c2;
3609 : uint32_t v;
3610 : uint8_t utf8buf[6];
3611 :
3612 21 : bool useCESU8 = fc == CESU8Encoding;
3613 21 : size_t dstlen = *dstlenp;
3614 21 : size_t origDstlen = dstlen;
3615 :
3616 545 : while (srclen) {
3617 503 : c = *src++;
3618 503 : srclen--;
3619 503 : if ((c >= 0xDC00) && (c <= 0xDFFF) && !useCESU8)
3620 0 : goto badSurrogate;
3621 503 : if (c < 0xD800 || c > 0xDBFF || useCESU8) {
3622 502 : v = c;
3623 : } else {
3624 1 : if (srclen < 1)
3625 0 : goto badSurrogate;
3626 1 : c2 = *src;
3627 1 : if ((c2 < 0xDC00) || (c2 > 0xDFFF))
3628 : goto badSurrogate;
3629 1 : src++;
3630 1 : srclen--;
3631 1 : v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3632 : }
3633 503 : if (v < 0x0080) {
3634 : /* no encoding necessary - performance hack */
3635 502 : if (dstlen == 0)
3636 0 : goto bufferTooSmall;
3637 502 : *dst++ = (char) v;
3638 502 : utf8Len = 1;
3639 : } else {
3640 1 : utf8Len = js_OneUcs4ToUtf8Char(utf8buf, v);
3641 1 : if (utf8Len > dstlen)
3642 0 : goto bufferTooSmall;
3643 5 : for (i = 0; i < utf8Len; i++)
3644 4 : *dst++ = (char) utf8buf[i];
3645 : }
3646 503 : dstlen -= utf8Len;
3647 : }
3648 21 : *dstlenp = (origDstlen - dstlen);
3649 21 : return JS_TRUE;
3650 :
3651 : badSurrogate:
3652 0 : *dstlenp = (origDstlen - dstlen);
3653 : /* Delegate error reporting to the measurement function. */
3654 0 : if (cx)
3655 0 : GetDeflatedStringLength(cx, src - 1, srclen + 1);
3656 0 : return JS_FALSE;
3657 :
3658 : bufferTooSmall:
3659 0 : *dstlenp = (origDstlen - dstlen);
3660 0 : if (cx) {
3661 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3662 0 : JSMSG_BUFFER_TOO_SMALL);
3663 : }
3664 0 : return JS_FALSE;
3665 : }
3666 :
3667 : bool
3668 28488972 : InflateStringToBuffer(JSContext *cx, const char *src, size_t srclen,
3669 : jschar *dst, size_t *dstlenp)
3670 : {
3671 : size_t dstlen, i;
3672 :
3673 28488972 : if (js_CStringsAreUTF8)
3674 55017 : return InflateUTF8StringToBuffer(cx, src, srclen, dst, dstlenp);
3675 :
3676 28433955 : if (dst) {
3677 28433955 : dstlen = *dstlenp;
3678 28433955 : if (srclen > dstlen) {
3679 0 : for (i = 0; i < dstlen; i++)
3680 0 : dst[i] = (unsigned char) src[i];
3681 0 : if (cx) {
3682 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3683 0 : JSMSG_BUFFER_TOO_SMALL);
3684 : }
3685 0 : return JS_FALSE;
3686 : }
3687 242830820 : for (i = 0; i < srclen; i++)
3688 214396865 : dst[i] = (unsigned char) src[i];
3689 : }
3690 28433955 : *dstlenp = srclen;
3691 28433955 : return JS_TRUE;
3692 : }
3693 :
3694 : bool
3695 119671 : InflateUTF8StringToBuffer(JSContext *cx, const char *src, size_t srclen,
3696 : jschar *dst, size_t *dstlenp, FlationCoding fc)
3697 : {
3698 : size_t dstlen, origDstlen, offset, j, n;
3699 : uint32_t v;
3700 :
3701 119671 : dstlen = dst ? *dstlenp : (size_t) -1;
3702 119671 : origDstlen = dstlen;
3703 119671 : offset = 0;
3704 119671 : bool useCESU8 = fc == CESU8Encoding;
3705 :
3706 27508801 : while (srclen) {
3707 27269459 : v = (uint8_t) *src;
3708 27269459 : n = 1;
3709 27269459 : if (v & 0x80) {
3710 729 : while (v & (0x80 >> n))
3711 315 : n++;
3712 207 : if (n > srclen)
3713 0 : goto bufferTooSmall;
3714 207 : if (n == 1 || n > 4)
3715 : goto badCharacter;
3716 522 : for (j = 1; j < n; j++) {
3717 315 : if ((src[j] & 0xC0) != 0x80)
3718 0 : goto badCharacter;
3719 : }
3720 207 : v = Utf8ToOneUcs4Char((uint8_t *)src, n);
3721 207 : if (v >= 0x10000 && !useCESU8) {
3722 0 : v -= 0x10000;
3723 0 : if (v > 0xFFFFF || dstlen < 2) {
3724 0 : *dstlenp = (origDstlen - dstlen);
3725 0 : if (cx) {
3726 : char buffer[10];
3727 0 : JS_snprintf(buffer, 10, "0x%x", v + 0x10000);
3728 : JS_ReportErrorFlagsAndNumber(cx,
3729 : JSREPORT_ERROR,
3730 : js_GetErrorMessage, NULL,
3731 : JSMSG_UTF8_CHAR_TOO_LARGE,
3732 0 : buffer);
3733 : }
3734 0 : return JS_FALSE;
3735 : }
3736 0 : if (dst) {
3737 0 : *dst++ = (jschar)((v >> 10) + 0xD800);
3738 0 : v = (jschar)((v & 0x3FF) + 0xDC00);
3739 : }
3740 0 : dstlen--;
3741 : }
3742 : }
3743 27269459 : if (!dstlen)
3744 0 : goto bufferTooSmall;
3745 27269459 : if (dst)
3746 27006319 : *dst++ = (jschar) v;
3747 27269459 : dstlen--;
3748 27269459 : offset += n;
3749 27269459 : src += n;
3750 27269459 : srclen -= n;
3751 : }
3752 119671 : *dstlenp = (origDstlen - dstlen);
3753 119671 : return JS_TRUE;
3754 :
3755 : badCharacter:
3756 0 : *dstlenp = (origDstlen - dstlen);
3757 0 : if (cx) {
3758 : char buffer[10];
3759 0 : JS_snprintf(buffer, 10, "%d", offset);
3760 : JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR,
3761 : js_GetErrorMessage, NULL,
3762 : JSMSG_MALFORMED_UTF8_CHAR,
3763 0 : buffer);
3764 : }
3765 0 : return JS_FALSE;
3766 :
3767 : bufferTooSmall:
3768 0 : *dstlenp = (origDstlen - dstlen);
3769 0 : if (cx) {
3770 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3771 0 : JSMSG_BUFFER_TOO_SMALL);
3772 : }
3773 0 : return JS_FALSE;
3774 : }
3775 :
3776 : } /* namepsace js */
3777 :
3778 : const jschar js_uriReservedPlusPound_ucstr[] =
3779 : {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#', 0};
3780 : const jschar js_uriUnescaped_ucstr[] =
3781 : {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
3782 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3783 : 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
3784 : 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
3785 : 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
3786 : '-', '_', '.', '!', '~', '*', '\'', '(', ')', 0};
3787 :
3788 : #define ____ false
3789 :
3790 : /*
3791 : * Identifier start chars:
3792 : * - 36: $
3793 : * - 65..90: A..Z
3794 : * - 95: _
3795 : * - 97..122: a..z
3796 : */
3797 : const bool js_isidstart[] = {
3798 : /* 0 1 2 3 4 5 6 7 8 9 */
3799 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3800 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3801 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3802 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
3803 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3804 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3805 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
3806 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
3807 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
3808 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
3809 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
3810 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
3811 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
3812 : };
3813 :
3814 : /*
3815 : * Identifier chars:
3816 : * - 36: $
3817 : * - 48..57: 0..9
3818 : * - 65..90: A..Z
3819 : * - 95: _
3820 : * - 97..122: a..z
3821 : */
3822 : const bool js_isident[] = {
3823 : /* 0 1 2 3 4 5 6 7 8 9 */
3824 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3825 : /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3826 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3827 : /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
3828 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
3829 : /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
3830 : /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
3831 : /* 7 */ true, true, true, true, true, true, true, true, true, true,
3832 : /* 8 */ true, true, true, true, true, true, true, true, true, true,
3833 : /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
3834 : /* 10 */ true, true, true, true, true, true, true, true, true, true,
3835 : /* 11 */ true, true, true, true, true, true, true, true, true, true,
3836 : /* 12 */ true, true, true, ____, ____, ____, ____, ____
3837 : };
3838 :
3839 : /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
3840 : const bool js_isspace[] = {
3841 : /* 0 1 2 3 4 5 6 7 8 9 */
3842 : /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
3843 : /* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
3844 : /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3845 : /* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
3846 : /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3847 : /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3848 : /* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3849 : /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3850 : /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3851 : /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3852 : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3853 : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
3854 : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
3855 : };
3856 :
3857 : #undef ____
3858 :
3859 : #define URI_CHUNK 64U
3860 :
3861 : static inline bool
3862 162 : TransferBufferToString(JSContext *cx, StringBuffer &sb, Value *rval)
3863 : {
3864 162 : JSString *str = sb.finishString();
3865 162 : if (!str)
3866 0 : return false;
3867 162 : rval->setString(str);
3868 162 : return true;
3869 : }
3870 :
3871 : /*
3872 : * ECMA 3, 15.1.3 URI Handling Function Properties
3873 : *
3874 : * The following are implementations of the algorithms
3875 : * given in the ECMA specification for the hidden functions
3876 : * 'Encode' and 'Decode'.
3877 : */
3878 : static JSBool
3879 0 : Encode(JSContext *cx, JSString *str, const jschar *unescapedSet,
3880 : const jschar *unescapedSet2, Value *rval)
3881 : {
3882 : static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
3883 :
3884 0 : size_t length = str->length();
3885 0 : const jschar *chars = str->getChars(cx);
3886 0 : if (!chars)
3887 0 : return JS_FALSE;
3888 :
3889 0 : if (length == 0) {
3890 0 : rval->setString(cx->runtime->emptyString);
3891 0 : return JS_TRUE;
3892 : }
3893 :
3894 0 : StringBuffer sb(cx);
3895 : jschar hexBuf[4];
3896 0 : hexBuf[0] = '%';
3897 0 : hexBuf[3] = 0;
3898 0 : for (size_t k = 0; k < length; k++) {
3899 0 : jschar c = chars[k];
3900 0 : if (js_strchr(unescapedSet, c) ||
3901 0 : (unescapedSet2 && js_strchr(unescapedSet2, c))) {
3902 0 : if (!sb.append(c))
3903 0 : return JS_FALSE;
3904 : } else {
3905 0 : if ((c >= 0xDC00) && (c <= 0xDFFF)) {
3906 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3907 0 : JSMSG_BAD_URI, NULL);
3908 0 : return JS_FALSE;
3909 : }
3910 : uint32_t v;
3911 0 : if (c < 0xD800 || c > 0xDBFF) {
3912 0 : v = c;
3913 : } else {
3914 0 : k++;
3915 0 : if (k == length) {
3916 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3917 0 : JSMSG_BAD_URI, NULL);
3918 0 : return JS_FALSE;
3919 : }
3920 0 : jschar c2 = chars[k];
3921 0 : if ((c2 < 0xDC00) || (c2 > 0xDFFF)) {
3922 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3923 0 : JSMSG_BAD_URI, NULL);
3924 0 : return JS_FALSE;
3925 : }
3926 0 : v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
3927 : }
3928 : uint8_t utf8buf[4];
3929 0 : size_t L = js_OneUcs4ToUtf8Char(utf8buf, v);
3930 0 : for (size_t j = 0; j < L; j++) {
3931 0 : hexBuf[1] = HexDigits[utf8buf[j] >> 4];
3932 0 : hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
3933 0 : if (!sb.append(hexBuf, 3))
3934 0 : return JS_FALSE;
3935 : }
3936 : }
3937 : }
3938 :
3939 0 : return TransferBufferToString(cx, sb, rval);
3940 : }
3941 :
3942 : static JSBool
3943 171 : Decode(JSContext *cx, JSString *str, const jschar *reservedSet, Value *rval)
3944 : {
3945 171 : size_t length = str->length();
3946 171 : const jschar *chars = str->getChars(cx);
3947 171 : if (!chars)
3948 0 : return JS_FALSE;
3949 :
3950 171 : if (length == 0) {
3951 0 : rval->setString(cx->runtime->emptyString);
3952 0 : return JS_TRUE;
3953 : }
3954 :
3955 342 : StringBuffer sb(cx);
3956 333 : for (size_t k = 0; k < length; k++) {
3957 171 : jschar c = chars[k];
3958 171 : if (c == '%') {
3959 9 : size_t start = k;
3960 9 : if ((k + 2) >= length)
3961 9 : goto report_bad_uri;
3962 0 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
3963 : goto report_bad_uri;
3964 0 : uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
3965 0 : k += 2;
3966 0 : if (!(B & 0x80)) {
3967 0 : c = (jschar)B;
3968 : } else {
3969 0 : int n = 1;
3970 0 : while (B & (0x80 >> n))
3971 0 : n++;
3972 0 : if (n == 1 || n > 4)
3973 : goto report_bad_uri;
3974 : uint8_t octets[4];
3975 0 : octets[0] = (uint8_t)B;
3976 0 : if (k + 3 * (n - 1) >= length)
3977 0 : goto report_bad_uri;
3978 0 : for (int j = 1; j < n; j++) {
3979 0 : k++;
3980 0 : if (chars[k] != '%')
3981 0 : goto report_bad_uri;
3982 0 : if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
3983 : goto report_bad_uri;
3984 0 : B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
3985 0 : if ((B & 0xC0) != 0x80)
3986 0 : goto report_bad_uri;
3987 0 : k += 2;
3988 0 : octets[j] = (char)B;
3989 : }
3990 0 : uint32_t v = Utf8ToOneUcs4Char(octets, n);
3991 0 : if (v >= 0x10000) {
3992 0 : v -= 0x10000;
3993 0 : if (v > 0xFFFFF)
3994 0 : goto report_bad_uri;
3995 0 : c = (jschar)((v & 0x3FF) + 0xDC00);
3996 0 : jschar H = (jschar)((v >> 10) + 0xD800);
3997 0 : if (!sb.append(H))
3998 0 : return JS_FALSE;
3999 : } else {
4000 0 : c = (jschar)v;
4001 : }
4002 : }
4003 0 : if (js_strchr(reservedSet, c)) {
4004 0 : if (!sb.append(chars + start, k - start + 1))
4005 0 : return JS_FALSE;
4006 : } else {
4007 0 : if (!sb.append(c))
4008 0 : return JS_FALSE;
4009 : }
4010 : } else {
4011 162 : if (!sb.append(c))
4012 0 : return JS_FALSE;
4013 : }
4014 : }
4015 :
4016 162 : return TransferBufferToString(cx, sb, rval);
4017 :
4018 : report_bad_uri:
4019 9 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_URI);
4020 : /* FALL THROUGH */
4021 :
4022 9 : return JS_FALSE;
4023 : }
4024 :
4025 : static JSBool
4026 171 : str_decodeURI(JSContext *cx, unsigned argc, Value *vp)
4027 : {
4028 171 : CallArgs args = CallArgsFromVp(argc, vp);
4029 171 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4030 171 : if (!str)
4031 0 : return false;
4032 :
4033 : Value result;
4034 171 : if (!Decode(cx, str, js_uriReservedPlusPound_ucstr, &result))
4035 9 : return false;
4036 :
4037 162 : args.rval() = result;
4038 162 : return true;
4039 : }
4040 :
4041 : static JSBool
4042 0 : str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
4043 : {
4044 0 : CallArgs args = CallArgsFromVp(argc, vp);
4045 0 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4046 0 : if (!str)
4047 0 : return false;
4048 :
4049 : Value result;
4050 0 : if (!Decode(cx, str, js_empty_ucstr, &result))
4051 0 : return false;
4052 :
4053 0 : args.rval() = result;
4054 0 : return true;
4055 : }
4056 :
4057 : static JSBool
4058 0 : str_encodeURI(JSContext *cx, unsigned argc, Value *vp)
4059 : {
4060 0 : CallArgs args = CallArgsFromVp(argc, vp);
4061 0 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4062 0 : if (!str)
4063 0 : return false;
4064 :
4065 : Value result;
4066 0 : if (!Encode(cx, str, js_uriReservedPlusPound_ucstr, js_uriUnescaped_ucstr, &result))
4067 0 : return false;
4068 :
4069 0 : args.rval() = result;
4070 0 : return true;
4071 : }
4072 :
4073 : static JSBool
4074 0 : str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
4075 : {
4076 0 : CallArgs args = CallArgsFromVp(argc, vp);
4077 0 : JSLinearString *str = ArgToRootedString(cx, args, 0);
4078 0 : if (!str)
4079 0 : return false;
4080 :
4081 : Value result;
4082 0 : if (!Encode(cx, str, js_uriUnescaped_ucstr, NULL, &result))
4083 0 : return false;
4084 :
4085 0 : args.rval() = result;
4086 0 : return true;
4087 : }
4088 :
4089 : /*
4090 : * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
4091 : * least 4 bytes long. Return the number of UTF-8 bytes of data written.
4092 : */
4093 : int
4094 1 : js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char)
4095 : {
4096 1 : int utf8Length = 1;
4097 :
4098 1 : JS_ASSERT(ucs4Char <= 0x10FFFF);
4099 1 : if (ucs4Char < 0x80) {
4100 0 : *utf8Buffer = (uint8_t)ucs4Char;
4101 : } else {
4102 : int i;
4103 1 : uint32_t a = ucs4Char >> 11;
4104 1 : utf8Length = 2;
4105 4 : while (a) {
4106 2 : a >>= 5;
4107 2 : utf8Length++;
4108 : }
4109 1 : i = utf8Length;
4110 5 : while (--i) {
4111 3 : utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
4112 3 : ucs4Char >>= 6;
4113 : }
4114 1 : *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
4115 : }
4116 1 : return utf8Length;
4117 : }
4118 :
4119 : /*
4120 : * Convert a utf8 character sequence into a UCS-4 character and return that
4121 : * character. It is assumed that the caller already checked that the sequence
4122 : * is valid.
4123 : */
4124 : static uint32_t
4125 207 : Utf8ToOneUcs4Char(const uint8_t *utf8Buffer, int utf8Length)
4126 : {
4127 207 : JS_ASSERT(1 <= utf8Length && utf8Length <= 4);
4128 :
4129 207 : if (utf8Length == 1) {
4130 0 : JS_ASSERT(!(*utf8Buffer & 0x80));
4131 0 : return *utf8Buffer;
4132 : }
4133 :
4134 : /* from Unicode 3.1, non-shortest form is illegal */
4135 : static const uint32_t minucs4Table[] = { 0x80, 0x800, 0x10000 };
4136 :
4137 0 : JS_ASSERT((*utf8Buffer & (0x100 - (1 << (7 - utf8Length)))) ==
4138 207 : (0x100 - (1 << (8 - utf8Length))));
4139 207 : uint32_t ucs4Char = *utf8Buffer++ & ((1 << (7 - utf8Length)) - 1);
4140 207 : uint32_t minucs4Char = minucs4Table[utf8Length - 2];
4141 729 : while (--utf8Length) {
4142 315 : JS_ASSERT((*utf8Buffer & 0xC0) == 0x80);
4143 315 : ucs4Char = (ucs4Char << 6) | (*utf8Buffer++ & 0x3F);
4144 : }
4145 :
4146 207 : if (JS_UNLIKELY(ucs4Char < minucs4Char || (ucs4Char >= 0xD800 && ucs4Char <= 0xDFFF)))
4147 0 : return INVALID_UTF8;
4148 :
4149 207 : return ucs4Char;
4150 : }
4151 :
4152 : namespace js {
4153 :
4154 : size_t
4155 444636 : PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str, uint32_t quote)
4156 : {
4157 : enum {
4158 : STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
4159 : } state;
4160 :
4161 444636 : JS_ASSERT(quote == 0 || quote == '\'' || quote == '"');
4162 444636 : JS_ASSERT_IF(!buffer, bufferSize == 0);
4163 444636 : JS_ASSERT_IF(fp, !buffer);
4164 :
4165 444636 : if (bufferSize == 0)
4166 0 : buffer = NULL;
4167 : else
4168 444636 : bufferSize--;
4169 :
4170 444636 : const jschar *chars = str->chars();
4171 444636 : const jschar *charsEnd = chars + str->length();
4172 444636 : size_t n = 0;
4173 444636 : state = FIRST_QUOTE;
4174 444636 : unsigned shift = 0;
4175 444636 : unsigned hex = 0;
4176 444636 : unsigned u = 0;
4177 444636 : char c = 0; /* to quell GCC warnings */
4178 :
4179 4460948 : for (;;) {
4180 4905584 : switch (state) {
4181 : case STOP:
4182 444636 : goto stop;
4183 : case FIRST_QUOTE:
4184 444636 : state = CHARS;
4185 444636 : goto do_quote;
4186 : case LAST_QUOTE:
4187 444636 : state = STOP;
4188 : do_quote:
4189 889272 : if (quote == 0)
4190 889272 : continue;
4191 0 : c = (char)quote;
4192 0 : break;
4193 : case CHARS:
4194 3570380 : if (chars == charsEnd) {
4195 444636 : state = LAST_QUOTE;
4196 444636 : continue;
4197 : }
4198 3125744 : u = *chars++;
4199 3125744 : if (u < ' ') {
4200 360 : if (u != 0) {
4201 350 : const char *escape = strchr(js_EscapeMap, (int)u);
4202 350 : if (escape) {
4203 100 : u = escape[1];
4204 100 : goto do_escape;
4205 : }
4206 : }
4207 260 : goto do_hex_escape;
4208 : }
4209 3125384 : if (u < 127) {
4210 3125374 : if (u == quote || u == '\\')
4211 : goto do_escape;
4212 3125366 : c = (char)u;
4213 10 : } else if (u < 0x100) {
4214 10 : goto do_hex_escape;
4215 : } else {
4216 0 : shift = 16;
4217 0 : hex = u;
4218 0 : u = 'u';
4219 0 : goto do_escape;
4220 : }
4221 3125366 : break;
4222 : do_hex_escape:
4223 270 : shift = 8;
4224 270 : hex = u;
4225 270 : u = 'x';
4226 : do_escape:
4227 378 : c = '\\';
4228 378 : state = ESCAPE_START;
4229 378 : break;
4230 : case ESCAPE_START:
4231 378 : JS_ASSERT(' ' <= u && u < 127);
4232 378 : c = (char)u;
4233 378 : state = ESCAPE_MORE;
4234 378 : break;
4235 : case ESCAPE_MORE:
4236 918 : if (shift == 0) {
4237 378 : state = CHARS;
4238 378 : continue;
4239 : }
4240 540 : shift -= 4;
4241 540 : u = 0xF & (hex >> shift);
4242 540 : c = (char)(u + (u < 10 ? '0' : 'A' - 10));
4243 540 : break;
4244 : }
4245 3126662 : if (buffer) {
4246 3126662 : JS_ASSERT(n <= bufferSize);
4247 3126662 : if (n != bufferSize) {
4248 3126662 : buffer[n] = c;
4249 : } else {
4250 0 : buffer[n] = '\0';
4251 0 : buffer = NULL;
4252 : }
4253 0 : } else if (fp) {
4254 0 : if (fputc(c, fp) < 0)
4255 0 : return size_t(-1);
4256 : }
4257 3126662 : n++;
4258 : }
4259 : stop:
4260 444636 : if (buffer)
4261 444636 : buffer[n] = '\0';
4262 444636 : return n;
4263 : }
4264 :
4265 : } /* namespace js */
|