/[jscoverage]/trunk/js/jsstr.cpp
ViewVC logotype

Contents of /trunk/js/jsstr.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 399 - (show annotations)
Tue Dec 9 03:37:47 2008 UTC (10 years, 11 months ago) by siliconforks
File size: 185244 byte(s)
Use SpiderMonkey from Firefox 3.1b2.

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 #include "jsstddef.h"
52 #include <stdlib.h>
53 #include <string.h>
54 #include "jstypes.h"
55 #include "jsutil.h" /* Added by JSIFY */
56 #include "jshash.h" /* Added by JSIFY */
57 #include "jsprf.h"
58 #include "jsapi.h"
59 #include "jsarray.h"
60 #include "jsatom.h"
61 #include "jsbool.h"
62 #include "jsbuiltins.h"
63 #include "jscntxt.h"
64 #include "jsversion.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 "jsregexp.h"
72 #include "jsscope.h"
73 #include "jsstr.h"
74 #include "jsbit.h"
75
76 #define JSSTRDEP_RECURSION_LIMIT 100
77
78 size_t
79 js_MinimizeDependentStrings(JSString *str, int level, JSString **basep)
80 {
81 JSString *base;
82 size_t start, length;
83
84 JS_ASSERT(JSSTRING_IS_DEPENDENT(str));
85 base = JSSTRDEP_BASE(str);
86 start = JSSTRDEP_START(str);
87 if (JSSTRING_IS_DEPENDENT(base)) {
88 if (level < JSSTRDEP_RECURSION_LIMIT) {
89 start += js_MinimizeDependentStrings(base, level + 1, &base);
90 } else {
91 do {
92 start += JSSTRDEP_START(base);
93 base = JSSTRDEP_BASE(base);
94 } while (JSSTRING_IS_DEPENDENT(base));
95 }
96 if (start == 0) {
97 JS_ASSERT(JSSTRDEP_IS_PREFIX(str));
98 JSPREFIX_SET_BASE(str, base);
99 } else if (start <= JSSTRDEP_START_MASK) {
100 length = JSSTRDEP_LENGTH(str);
101 JSSTRDEP_INIT(str, base, start, length);
102 }
103 }
104 *basep = base;
105 return start;
106 }
107
108 jschar *
109 js_GetDependentStringChars(JSString *str)
110 {
111 size_t start;
112 JSString *base;
113
114 start = js_MinimizeDependentStrings(str, 0, &base);
115 JS_ASSERT(start < JSFLATSTR_LENGTH(base));
116 return JSFLATSTR_CHARS(base) + start;
117 }
118
119 const jschar *
120 js_GetStringChars(JSContext *cx, JSString *str)
121 {
122 if (!js_MakeStringImmutable(cx, str))
123 return NULL;
124 return JSFLATSTR_CHARS(str);
125 }
126
127 JSString * JS_FASTCALL
128 js_ConcatStrings(JSContext *cx, JSString *left, JSString *right)
129 {
130 size_t rn, ln, lrdist, n;
131 jschar *rs, *ls, *s;
132 JSString *ldep; /* non-null if left should become dependent */
133 JSString *str;
134
135 JSSTRING_CHARS_AND_LENGTH(right, rs, rn);
136 if (rn == 0)
137 return left;
138
139 JSSTRING_CHARS_AND_LENGTH(left, ls, ln);
140 if (ln == 0)
141 return right;
142
143 if (!JSSTRING_IS_MUTABLE(left)) {
144 /* We must copy if left does not own a buffer to realloc. */
145 s = (jschar *) JS_malloc(cx, (ln + rn + 1) * sizeof(jschar));
146 if (!s)
147 return NULL;
148 js_strncpy(s, ls, ln);
149 ldep = NULL;
150 } else {
151 /* We can realloc left's space and make it depend on our result. */
152 JS_ASSERT(JSSTRING_IS_FLAT(left));
153 s = (jschar *) JS_realloc(cx, ls, (ln + rn + 1) * sizeof(jschar));
154 if (!s)
155 return NULL;
156
157 /* Take care: right could depend on left! */
158 lrdist = (size_t)(rs - ls);
159 if (lrdist < ln)
160 rs = s + lrdist;
161 left->u.chars = ls = s;
162 ldep = left;
163 }
164
165 js_strncpy(s + ln, rs, rn);
166 n = ln + rn;
167 s[n] = 0;
168 str = js_NewString(cx, s, n);
169 if (!str) {
170 /* Out of memory: clean up any space we (re-)allocated. */
171 if (!ldep) {
172 JS_free(cx, s);
173 } else {
174 s = (jschar *) JS_realloc(cx, ls, (ln + 1) * sizeof(jschar));
175 if (s)
176 left->u.chars = s;
177 }
178 } else {
179 JSFLATSTR_SET_MUTABLE(str);
180
181 /* Morph left into a dependent prefix if we realloc'd its buffer. */
182 if (ldep) {
183 JSPREFIX_INIT(ldep, str, ln);
184 #ifdef DEBUG
185 {
186 JSRuntime *rt = cx->runtime;
187 JS_RUNTIME_METER(rt, liveDependentStrings);
188 JS_RUNTIME_METER(rt, totalDependentStrings);
189 JS_LOCK_RUNTIME_VOID(rt,
190 (rt->strdepLengthSum += (double)ln,
191 rt->strdepLengthSquaredSum += (double)ln * (double)ln));
192 }
193 #endif
194 }
195 }
196
197 return str;
198 }
199
200 const jschar *
201 js_UndependString(JSContext *cx, JSString *str)
202 {
203 size_t n, size;
204 jschar *s;
205
206 if (JSSTRING_IS_DEPENDENT(str)) {
207 n = JSSTRDEP_LENGTH(str);
208 size = (n + 1) * sizeof(jschar);
209 s = (jschar *) JS_malloc(cx, size);
210 if (!s)
211 return NULL;
212
213 js_strncpy(s, JSSTRDEP_CHARS(str), n);
214 s[n] = 0;
215 JSFLATSTR_INIT(str, s, n);
216
217 #ifdef DEBUG
218 {
219 JSRuntime *rt = cx->runtime;
220 JS_RUNTIME_UNMETER(rt, liveDependentStrings);
221 JS_RUNTIME_UNMETER(rt, totalDependentStrings);
222 JS_LOCK_RUNTIME_VOID(rt,
223 (rt->strdepLengthSum -= (double)n,
224 rt->strdepLengthSquaredSum -= (double)n * (double)n));
225 }
226 #endif
227 }
228
229 return JSFLATSTR_CHARS(str);
230 }
231
232 JSBool
233 js_MakeStringImmutable(JSContext *cx, JSString *str)
234 {
235 if (JSSTRING_IS_DEPENDENT(str) && !js_UndependString(cx, str)) {
236 JS_RUNTIME_METER(cx->runtime, badUndependStrings);
237 return JS_FALSE;
238 }
239 JSFLATSTR_CLEAR_MUTABLE(str);
240 return JS_TRUE;
241 }
242
243 static JSString *
244 ArgToRootedString(JSContext *cx, uintN argc, jsval *vp, uintN arg)
245 {
246 JSObject *obj;
247 JSString *str;
248
249 if (arg >= argc)
250 return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
251 vp += 2 + arg;
252
253 if (JSVAL_IS_OBJECT(*vp)) {
254 obj = JSVAL_TO_OBJECT(*vp);
255 if (!obj)
256 return ATOM_TO_STRING(cx->runtime->atomState.nullAtom);
257 if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, vp))
258 return NULL;
259 }
260 if (JSVAL_IS_STRING(*vp))
261 return JSVAL_TO_STRING(*vp);
262 if (JSVAL_IS_INT(*vp)) {
263 str = js_NumberToString(cx, JSVAL_TO_INT(*vp));
264 } else if (JSVAL_IS_DOUBLE(*vp)) {
265 str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(*vp));
266 } else if (JSVAL_IS_BOOLEAN(*vp)) {
267 return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[
268 JSVAL_TO_BOOLEAN(*vp)? 1 : 0]);
269 } else {
270 JS_ASSERT(JSVAL_IS_VOID(*vp));
271 return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
272 }
273 if (str)
274 *vp = STRING_TO_JSVAL(str);
275 return str;
276 }
277
278 /*
279 * Forward declarations for URI encode/decode and helper routines
280 */
281 static JSBool
282 str_decodeURI(JSContext *cx, uintN argc, jsval *vp);
283
284 static JSBool
285 str_decodeURI_Component(JSContext *cx, uintN argc, jsval *vp);
286
287 static JSBool
288 str_encodeURI(JSContext *cx, uintN argc, jsval *vp);
289
290 static JSBool
291 str_encodeURI_Component(JSContext *cx, uintN argc, jsval *vp);
292
293 static uint32
294 Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length);
295
296 /*
297 * Contributions from the String class to the set of methods defined for the
298 * global object. escape and unescape used to be defined in the Mocha library,
299 * but as ECMA decided to spec them, they've been moved to the core engine
300 * and made ECMA-compliant. (Incomplete escapes are interpreted as literal
301 * characters by unescape.)
302 */
303
304 /*
305 * Stuff to emulate the old libmocha escape, which took a second argument
306 * giving the type of escape to perform. Retained for compatibility, and
307 * copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes.
308 */
309
310 #define URL_XALPHAS ((uint8) 1)
311 #define URL_XPALPHAS ((uint8) 2)
312 #define URL_PATH ((uint8) 4)
313
314 static const uint8 urlCharType[256] =
315 /* Bit 0 xalpha -- the alphas
316 * Bit 1 xpalpha -- as xalpha but
317 * converts spaces to plus and plus to %20
318 * Bit 2 ... path -- as xalphas but doesn't escape '/'
319 */
320 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
321 { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */
322 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */
323 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */
324 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
325 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */
326 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */
327 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */
328 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */
329 0, };
330
331 /* This matches the ECMA escape set when mask is 7 (default.) */
332
333 #define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask))
334
335 /* See ECMA-262 Edition 3 B.2.1 */
336 JSBool
337 js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
338 {
339 JSString *str;
340 size_t i, ni, length, newlength;
341 const jschar *chars;
342 jschar *newchars;
343 jschar ch;
344 jsint mask;
345 jsdouble d;
346 const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
347 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
348
349 mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
350 if (argc > 1) {
351 d = js_ValueToNumber(cx, &argv[1]);
352 if (JSVAL_IS_NULL(argv[1]))
353 return JS_FALSE;
354 if (!JSDOUBLE_IS_FINITE(d) ||
355 (mask = (jsint)d) != d ||
356 mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))
357 {
358 char numBuf[12];
359 JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) mask);
360 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
361 JSMSG_BAD_STRING_MASK, numBuf);
362 return JS_FALSE;
363 }
364 }
365
366 str = ArgToRootedString(cx, argc, argv - 2, 0);
367 if (!str)
368 return JS_FALSE;
369
370 JSSTRING_CHARS_AND_LENGTH(str, chars, length);
371 newlength = length;
372
373 /* Take a first pass and see how big the result string will need to be. */
374 for (i = 0; i < length; i++) {
375 if ((ch = chars[i]) < 128 && IS_OK(ch, mask))
376 continue;
377 if (ch < 256) {
378 if (mask == URL_XPALPHAS && ch == ' ')
379 continue; /* The character will be encoded as '+' */
380 newlength += 2; /* The character will be encoded as %XX */
381 } else {
382 newlength += 5; /* The character will be encoded as %uXXXX */
383 }
384
385 /*
386 * This overflow test works because newlength is incremented by at
387 * most 5 on each iteration.
388 */
389 if (newlength < length) {
390 js_ReportAllocationOverflow(cx);
391 return JS_FALSE;
392 }
393 }
394
395 if (newlength >= ~(size_t)0 / sizeof(jschar)) {
396 js_ReportAllocationOverflow(cx);
397 return JS_FALSE;
398 }
399
400 newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar));
401 if (!newchars)
402 return JS_FALSE;
403 for (i = 0, ni = 0; i < length; i++) {
404 if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) {
405 newchars[ni++] = ch;
406 } else if (ch < 256) {
407 if (mask == URL_XPALPHAS && ch == ' ') {
408 newchars[ni++] = '+'; /* convert spaces to pluses */
409 } else {
410 newchars[ni++] = '%';
411 newchars[ni++] = digits[ch >> 4];
412 newchars[ni++] = digits[ch & 0xF];
413 }
414 } else {
415 newchars[ni++] = '%';
416 newchars[ni++] = 'u';
417 newchars[ni++] = digits[ch >> 12];
418 newchars[ni++] = digits[(ch & 0xF00) >> 8];
419 newchars[ni++] = digits[(ch & 0xF0) >> 4];
420 newchars[ni++] = digits[ch & 0xF];
421 }
422 }
423 JS_ASSERT(ni == newlength);
424 newchars[newlength] = 0;
425
426 str = js_NewString(cx, newchars, newlength);
427 if (!str) {
428 JS_free(cx, newchars);
429 return JS_FALSE;
430 }
431 *rval = STRING_TO_JSVAL(str);
432 return JS_TRUE;
433 }
434 #undef IS_OK
435
436 static JSBool
437 str_escape(JSContext *cx, uintN argc, jsval *vp)
438 {
439 JSObject *obj;
440
441 obj = JS_THIS_OBJECT(cx, vp);
442 return obj && js_str_escape(cx, obj, argc, vp + 2, vp);
443 }
444
445 /* See ECMA-262 Edition 3 B.2.2 */
446 static JSBool
447 str_unescape(JSContext *cx, uintN argc, jsval *vp)
448 {
449 JSString *str;
450 size_t i, ni, length;
451 const jschar *chars;
452 jschar *newchars;
453 jschar ch;
454
455 str = ArgToRootedString(cx, argc, vp, 0);
456 if (!str)
457 return JS_FALSE;
458
459 JSSTRING_CHARS_AND_LENGTH(str, chars, length);
460
461 /* Don't bother allocating less space for the new string. */
462 newchars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
463 if (!newchars)
464 return JS_FALSE;
465 ni = i = 0;
466 while (i < length) {
467 ch = chars[i++];
468 if (ch == '%') {
469 if (i + 1 < length &&
470 JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1]))
471 {
472 ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]);
473 i += 2;
474 } else if (i + 4 < length && chars[i] == 'u' &&
475 JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) &&
476 JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4]))
477 {
478 ch = (((((JS7_UNHEX(chars[i + 1]) << 4)
479 + JS7_UNHEX(chars[i + 2])) << 4)
480 + JS7_UNHEX(chars[i + 3])) << 4)
481 + JS7_UNHEX(chars[i + 4]);
482 i += 5;
483 }
484 }
485 newchars[ni++] = ch;
486 }
487 newchars[ni] = 0;
488
489 str = js_NewString(cx, newchars, ni);
490 if (!str) {
491 JS_free(cx, newchars);
492 return JS_FALSE;
493 }
494 *vp = STRING_TO_JSVAL(str);
495 return JS_TRUE;
496 }
497
498 #if JS_HAS_UNEVAL
499 static JSBool
500 str_uneval(JSContext *cx, uintN argc, jsval *vp)
501 {
502 JSString *str;
503
504 str = js_ValueToSource(cx, argc != 0 ? vp[2] : JSVAL_VOID);
505 if (!str)
506 return JS_FALSE;
507 *vp = STRING_TO_JSVAL(str);
508 return JS_TRUE;
509 }
510 #endif
511
512 const char js_escape_str[] = "escape";
513 const char js_unescape_str[] = "unescape";
514 #if JS_HAS_UNEVAL
515 const char js_uneval_str[] = "uneval";
516 #endif
517 const char js_decodeURI_str[] = "decodeURI";
518 const char js_encodeURI_str[] = "encodeURI";
519 const char js_decodeURIComponent_str[] = "decodeURIComponent";
520 const char js_encodeURIComponent_str[] = "encodeURIComponent";
521
522 static JSFunctionSpec string_functions[] = {
523 JS_FN(js_escape_str, str_escape, 1,0),
524 JS_FN(js_unescape_str, str_unescape, 1,0),
525 #if JS_HAS_UNEVAL
526 JS_FN(js_uneval_str, str_uneval, 1,0),
527 #endif
528 JS_FN(js_decodeURI_str, str_decodeURI, 1,0),
529 JS_FN(js_encodeURI_str, str_encodeURI, 1,0),
530 JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0),
531 JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0),
532
533 JS_FS_END
534 };
535
536 jschar js_empty_ucstr[] = {0};
537 JSSubString js_EmptySubString = {0, js_empty_ucstr};
538
539 enum string_tinyid {
540 STRING_LENGTH = -1
541 };
542
543 static JSPropertySpec string_props[] = {
544 {js_length_str, STRING_LENGTH,
545 JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, 0,0},
546 {0,0,0,0,0}
547 };
548
549 static JSBool
550 str_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
551 {
552 jsval v;
553 JSString *str;
554 jsint slot;
555
556 if (!JSVAL_IS_INT(id))
557 return JS_TRUE;
558
559 slot = JSVAL_TO_INT(id);
560 if (slot == STRING_LENGTH) {
561 if (OBJ_GET_CLASS(cx, obj) == &js_StringClass) {
562 /* Follow ECMA-262 by fetching intrinsic length of our string. */
563 v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
564 JS_ASSERT(JSVAL_IS_STRING(v));
565 str = JSVAL_TO_STRING(v);
566 } else {
567 /* Preserve compatibility: convert obj to a string primitive. */
568 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
569 if (!str)
570 return JS_FALSE;
571 }
572
573 *vp = INT_TO_JSVAL((jsint) JSSTRING_LENGTH(str));
574 }
575 return JS_TRUE;
576 }
577
578 #define STRING_ELEMENT_ATTRS (JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT)
579
580 static JSBool
581 str_enumerate(JSContext *cx, JSObject *obj)
582 {
583 jsval v;
584 JSString *str, *str1;
585 size_t i, length;
586
587 v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
588 JS_ASSERT(JSVAL_IS_STRING(v));
589 str = JSVAL_TO_STRING(v);
590
591 length = JSSTRING_LENGTH(str);
592 for (i = 0; i < length; i++) {
593 str1 = js_NewDependentString(cx, str, i, 1);
594 if (!str1)
595 return JS_FALSE;
596 if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(i),
597 STRING_TO_JSVAL(str1), NULL, NULL,
598 STRING_ELEMENT_ATTRS, NULL)) {
599 return JS_FALSE;
600 }
601 }
602 return JS_TRUE;
603 }
604
605 static JSBool
606 str_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
607 JSObject **objp)
608 {
609 jsval v;
610 JSString *str, *str1;
611 jsint slot;
612
613 if (!JSVAL_IS_INT(id) || (flags & JSRESOLVE_ASSIGNING))
614 return JS_TRUE;
615
616 v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
617 JS_ASSERT(JSVAL_IS_STRING(v));
618 str = JSVAL_TO_STRING(v);
619
620 slot = JSVAL_TO_INT(id);
621 if ((size_t)slot < JSSTRING_LENGTH(str)) {
622 str1 = js_GetUnitString(cx, str, (size_t)slot);
623 if (!str1)
624 return JS_FALSE;
625 if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(slot),
626 STRING_TO_JSVAL(str1), NULL, NULL,
627 STRING_ELEMENT_ATTRS, NULL)) {
628 return JS_FALSE;
629 }
630 *objp = obj;
631 }
632 return JS_TRUE;
633 }
634
635 JSClass js_StringClass = {
636 js_String_str,
637 JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE |
638 JSCLASS_HAS_CACHED_PROTO(JSProto_String),
639 JS_PropertyStub, JS_PropertyStub, str_getProperty, JS_PropertyStub,
640 str_enumerate, (JSResolveOp)str_resolve, JS_ConvertStub, JS_FinalizeStub,
641 JSCLASS_NO_OPTIONAL_MEMBERS
642 };
643
644 #define NORMALIZE_THIS(cx,vp,str) \
645 JS_BEGIN_MACRO \
646 if (JSVAL_IS_STRING(vp[1])) { \
647 str = JSVAL_TO_STRING(vp[1]); \
648 } else { \
649 str = NormalizeThis(cx, vp); \
650 if (!str) \
651 return JS_FALSE; \
652 } \
653 JS_END_MACRO
654
655 static JSString *
656 NormalizeThis(JSContext *cx, jsval *vp)
657 {
658 JSString *str;
659
660 if (JSVAL_IS_NULL(vp[1]) && JSVAL_IS_NULL(JS_THIS(cx, vp)))
661 return NULL;
662 str = js_ValueToString(cx, vp[1]);
663 if (!str)
664 return NULL;
665 vp[1] = STRING_TO_JSVAL(str);
666 return str;
667 }
668
669 #if JS_HAS_TOSOURCE
670
671 /*
672 * String.prototype.quote is generic (as are most string methods), unlike
673 * toSource, toString, and valueOf.
674 */
675 static JSBool
676 str_quote(JSContext *cx, uintN argc, jsval *vp)
677 {
678 JSString *str;
679
680 NORMALIZE_THIS(cx, vp, str);
681 str = js_QuoteString(cx, str, '"');
682 if (!str)
683 return JS_FALSE;
684 *vp = STRING_TO_JSVAL(str);
685 return JS_TRUE;
686 }
687
688 static JSBool
689 str_toSource(JSContext *cx, uintN argc, jsval *vp)
690 {
691 jsval v;
692 JSString *str;
693 size_t i, j, k, n;
694 char buf[16];
695 jschar *s, *t;
696
697 if (!js_GetPrimitiveThis(cx, vp, &js_StringClass, &v))
698 return JS_FALSE;
699 JS_ASSERT(JSVAL_IS_STRING(v));
700 str = js_QuoteString(cx, JSVAL_TO_STRING(v), '"');
701 if (!str)
702 return JS_FALSE;
703 j = JS_snprintf(buf, sizeof buf, "(new %s(", js_StringClass.name);
704 JSSTRING_CHARS_AND_LENGTH(str, s, k);
705 n = j + k + 2;
706 t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
707 if (!t)
708 return JS_FALSE;
709 for (i = 0; i < j; i++)
710 t[i] = buf[i];
711 for (j = 0; j < k; i++, j++)
712 t[i] = s[j];
713 t[i++] = ')';
714 t[i++] = ')';
715 t[i] = 0;
716 str = js_NewString(cx, t, n);
717 if (!str) {
718 JS_free(cx, t);
719 return JS_FALSE;
720 }
721 *vp = STRING_TO_JSVAL(str);
722 return JS_TRUE;
723 }
724
725 #endif /* JS_HAS_TOSOURCE */
726
727 static JSBool
728 str_toString(JSContext *cx, uintN argc, jsval *vp)
729 {
730 return js_GetPrimitiveThis(cx, vp, &js_StringClass, vp);
731 }
732
733 /*
734 * Java-like string native methods.
735 */
736
737 static JSString *
738 SubstringTail(JSContext *cx, JSString *str, jsdouble length, jsdouble begin, jsdouble end)
739 {
740 if (begin < 0)
741 begin = 0;
742 else if (begin > length)
743 begin = length;
744
745 if (end < 0)
746 end = 0;
747 else if (end > length)
748 end = length;
749 if (end < begin) {
750 /* ECMA emulates old JDK1.0 java.lang.String.substring. */
751 jsdouble tmp = begin;
752 begin = end;
753 end = tmp;
754 }
755
756 return js_NewDependentString(cx, str, (size_t)begin, (size_t)(end - begin));
757 }
758
759 static JSBool
760 str_substring(JSContext *cx, uintN argc, jsval *vp)
761 {
762 JSString *str;
763 jsdouble d;
764 jsdouble length, begin, end;
765
766 NORMALIZE_THIS(cx, vp, str);
767 if (argc != 0) {
768 d = js_ValueToNumber(cx, &vp[2]);
769 if (JSVAL_IS_NULL(vp[2]))
770 return JS_FALSE;
771 length = JSSTRING_LENGTH(str);
772 begin = js_DoubleToInteger(d);
773 if (argc == 1) {
774 end = length;
775 } else {
776 d = js_ValueToNumber(cx, &vp[3]);
777 if (JSVAL_IS_NULL(vp[3]))
778 return JS_FALSE;
779 end = js_DoubleToInteger(d);
780 }
781
782 str = SubstringTail(cx, str, length, begin, end);
783 if (!str)
784 return JS_FALSE;
785 }
786 *vp = STRING_TO_JSVAL(str);
787 return JS_TRUE;
788 }
789
790 #ifdef JS_TRACER
791 static JSString* FASTCALL
792 String_p_toString(JSContext* cx, JSObject* obj)
793 {
794 if (!JS_InstanceOf(cx, obj, &js_StringClass, NULL))
795 return NULL;
796 jsval v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
797 JS_ASSERT(JSVAL_IS_STRING(v));
798 return JSVAL_TO_STRING(v);
799 }
800
801 static JSString* FASTCALL
802 String_p_substring(JSContext* cx, JSString* str, int32 begin, int32 end)
803 {
804 JS_ASSERT(JS_ON_TRACE(cx));
805
806 size_t length = JSSTRING_LENGTH(str);
807 return SubstringTail(cx, str, length, begin, end);
808 }
809
810 static JSString* FASTCALL
811 String_p_substring_1(JSContext* cx, JSString* str, int32 begin)
812 {
813 JS_ASSERT(JS_ON_TRACE(cx));
814
815 size_t length = JSSTRING_LENGTH(str);
816 return SubstringTail(cx, str, length, begin, length);
817 }
818 #endif
819
820 JSString* JS_FASTCALL
821 js_toLowerCase(JSContext *cx, JSString *str)
822 {
823 size_t i, n;
824 jschar *s, *news;
825
826 JSSTRING_CHARS_AND_LENGTH(str, s, n);
827 news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
828 if (!news)
829 return NULL;
830 for (i = 0; i < n; i++)
831 news[i] = JS_TOLOWER(s[i]);
832 news[n] = 0;
833 str = js_NewString(cx, news, n);
834 if (!str) {
835 JS_free(cx, news);
836 return NULL;
837 }
838 return str;
839 }
840
841 static JSBool
842 str_toLowerCase(JSContext *cx, uintN argc, jsval *vp)
843 {
844 JSString *str;
845
846 NORMALIZE_THIS(cx, vp, str);
847 str = js_toLowerCase(cx, str);
848 if (!str)
849 return JS_FALSE;
850 *vp = STRING_TO_JSVAL(str);
851 return JS_TRUE;
852 }
853
854 static JSBool
855 str_toLocaleLowerCase(JSContext *cx, uintN argc, jsval *vp)
856 {
857 JSString *str;
858
859 /*
860 * Forcefully ignore the first (or any) argument and return toLowerCase(),
861 * ECMA has reserved that argument, presumably for defining the locale.
862 */
863 if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) {
864 NORMALIZE_THIS(cx, vp, str);
865 return cx->localeCallbacks->localeToLowerCase(cx, str, vp);
866 }
867 return str_toLowerCase(cx, 0, vp);
868 }
869
870 JSString* JS_FASTCALL
871 js_toUpperCase(JSContext *cx, JSString *str)
872 {
873 size_t i, n;
874 jschar *s, *news;
875
876 JSSTRING_CHARS_AND_LENGTH(str, s, n);
877 news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
878 if (!news)
879 return NULL;
880 for (i = 0; i < n; i++)
881 news[i] = JS_TOUPPER(s[i]);
882 news[n] = 0;
883 str = js_NewString(cx, news, n);
884 if (!str) {
885 JS_free(cx, news);
886 return NULL;
887 }
888 return str;
889 }
890
891 static JSBool
892 str_toUpperCase(JSContext *cx, uintN argc, jsval *vp)
893 {
894 JSString *str;
895
896 NORMALIZE_THIS(cx, vp, str);
897 str = js_toUpperCase(cx, str);
898 if (!str)
899 return JS_FALSE;
900 *vp = STRING_TO_JSVAL(str);
901 return JS_TRUE;
902 }
903
904 static JSBool
905 str_toLocaleUpperCase(JSContext *cx, uintN argc, jsval *vp)
906 {
907 JSString *str;
908
909 /*
910 * Forcefully ignore the first (or any) argument and return toUpperCase(),
911 * ECMA has reserved that argument, presumably for defining the locale.
912 */
913 if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) {
914 NORMALIZE_THIS(cx, vp, str);
915 return cx->localeCallbacks->localeToUpperCase(cx, str, vp);
916 }
917 return str_toUpperCase(cx, 0, vp);
918 }
919
920 static JSBool
921 str_localeCompare(JSContext *cx, uintN argc, jsval *vp)
922 {
923 JSString *str, *thatStr;
924
925 NORMALIZE_THIS(cx, vp, str);
926 if (argc == 0) {
927 *vp = JSVAL_ZERO;
928 } else {
929 thatStr = js_ValueToString(cx, vp[2]);
930 if (!thatStr)
931 return JS_FALSE;
932 if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) {
933 vp[2] = STRING_TO_JSVAL(thatStr);
934 return cx->localeCallbacks->localeCompare(cx, str, thatStr, vp);
935 }
936 *vp = INT_TO_JSVAL(js_CompareStrings(str, thatStr));
937 }
938 return JS_TRUE;
939 }
940
941 static JSBool
942 str_charAt(JSContext *cx, uintN argc, jsval *vp)
943 {
944 jsval t;
945 JSString *str;
946 jsint i;
947 jsdouble d;
948
949 t = vp[1];
950 if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) {
951 str = JSVAL_TO_STRING(t);
952 i = JSVAL_TO_INT(vp[2]);
953 if ((size_t)i >= JSSTRING_LENGTH(str))
954 goto out_of_range;
955 } else {
956 str = NormalizeThis(cx, vp);
957 if (!str)
958 return JS_FALSE;
959
960 if (argc == 0) {
961 d = 0.0;
962 } else {
963 d = js_ValueToNumber(cx, &vp[2]);
964 if (JSVAL_IS_NULL(vp[2]))
965 return JS_FALSE;
966 d = js_DoubleToInteger(d);
967 }
968
969 if (d < 0 || JSSTRING_LENGTH(str) <= d)
970 goto out_of_range;
971 i = (jsint) d;
972 }
973
974 str = js_GetUnitString(cx, str, (size_t)i);
975 if (!str)
976 return JS_FALSE;
977 *vp = STRING_TO_JSVAL(str);
978 return JS_TRUE;
979
980 out_of_range:
981 *vp = JS_GetEmptyStringValue(cx);
982 return JS_TRUE;
983 }
984
985 static JSBool
986 str_charCodeAt(JSContext *cx, uintN argc, jsval *vp)
987 {
988 jsval t;
989 JSString *str;
990 jsint i;
991 jsdouble d;
992
993 t = vp[1];
994 if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) {
995 str = JSVAL_TO_STRING(t);
996 i = JSVAL_TO_INT(vp[2]);
997 if ((size_t)i >= JSSTRING_LENGTH(str))
998 goto out_of_range;
999 } else {
1000 str = NormalizeThis(cx, vp);
1001 if (!str)
1002 return JS_FALSE;
1003
1004 if (argc == 0) {
1005 d = 0.0;
1006 } else {
1007 d = js_ValueToNumber(cx, &vp[2]);
1008 if (JSVAL_IS_NULL(vp[2]))
1009 return JS_FALSE;
1010 d = js_DoubleToInteger(d);
1011 }
1012
1013 if (d < 0 || JSSTRING_LENGTH(str) <= d)
1014 goto out_of_range;
1015 i = (jsint) d;
1016 }
1017
1018 *vp = INT_TO_JSVAL(JSSTRING_CHARS(str)[i]);
1019 return JS_TRUE;
1020
1021 out_of_range:
1022 *vp = JS_GetNaNValue(cx);
1023 return JS_TRUE;
1024 }
1025
1026 #ifdef JS_TRACER
1027 int32 FASTCALL
1028 js_String_p_charCodeAt(JSString* str, int32 i)
1029 {
1030 if (i < 0 || (int32)JSSTRING_LENGTH(str) <= i)
1031 return -1;
1032 return JSSTRING_CHARS(str)[i];
1033 }
1034 #endif
1035
1036 jsint
1037 js_BoyerMooreHorspool(const jschar *text, jsint textlen,
1038 const jschar *pat, jsint patlen,
1039 jsint start)
1040 {
1041 jsint i, j, k, m;
1042 uint8 skip[BMH_CHARSET_SIZE];
1043 jschar c;
1044
1045 JS_ASSERT(0 < patlen && patlen <= BMH_PATLEN_MAX);
1046 for (i = 0; i < BMH_CHARSET_SIZE; i++)
1047 skip[i] = (uint8)patlen;
1048 m = patlen - 1;
1049 for (i = 0; i < m; i++) {
1050 c = pat[i];
1051 if (c >= BMH_CHARSET_SIZE)
1052 return BMH_BAD_PATTERN;
1053 skip[c] = (uint8)(m - i);
1054 }
1055 for (k = start + m;
1056 k < textlen;
1057 k += ((c = text[k]) >= BMH_CHARSET_SIZE) ? patlen : skip[c]) {
1058 for (i = k, j = m; ; i--, j--) {
1059 if (j < 0)
1060 return i + 1;
1061 if (text[i] != pat[j])
1062 break;
1063 }
1064 }
1065 return -1;
1066 }
1067
1068 static JSBool
1069 str_indexOf(JSContext *cx, uintN argc, jsval *vp)
1070 {
1071 jsval t;
1072 JSString *str, *str2;
1073 const jschar *text, *pat;
1074 jsint i, j, index, textlen, patlen;
1075 jsdouble d;
1076
1077 t = vp[1];
1078 if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_STRING(vp[2])) {
1079 str = JSVAL_TO_STRING(t);
1080 str2 = JSVAL_TO_STRING(vp[2]);
1081 } else {
1082 str = NormalizeThis(cx, vp);
1083 if (!str)
1084 return JS_FALSE;
1085
1086 str2 = ArgToRootedString(cx, argc, vp, 0);
1087 if (!str2)
1088 return JS_FALSE;
1089 }
1090
1091 text = JSSTRING_CHARS(str);
1092 textlen = (jsint) JSSTRING_LENGTH(str);
1093 pat = JSSTRING_CHARS(str2);
1094 patlen = (jsint) JSSTRING_LENGTH(str2);
1095
1096 if (argc > 1) {
1097 d = js_ValueToNumber(cx, &vp[3]);
1098 if (JSVAL_IS_NULL(vp[3]))
1099 return JS_FALSE;
1100 d = js_DoubleToInteger(d);
1101 if (d < 0)
1102 i = 0;
1103 else if (d > textlen)
1104 i = textlen;
1105 else
1106 i = (jsint)d;
1107 } else {
1108 i = 0;
1109 }
1110 if (patlen == 0) {
1111 *vp = INT_TO_JSVAL(i);
1112 return JS_TRUE;
1113 }
1114
1115 /* XXX tune the BMH threshold (512) */
1116 if (textlen - i >= 512 && (jsuint)(patlen - 2) <= BMH_PATLEN_MAX - 2) {
1117 index = js_BoyerMooreHorspool(text, textlen, pat, patlen, i);
1118 if (index != BMH_BAD_PATTERN)
1119 goto out;
1120 }
1121
1122 index = -1;
1123 j = 0;
1124 while (i + j < textlen) {
1125 if (text[i + j] == pat[j]) {
1126 if (++j == patlen) {
1127 index = i;
1128 break;
1129 }
1130 } else {
1131 i++;
1132 j = 0;
1133 }
1134 }
1135
1136 out:
1137 *vp = INT_TO_JSVAL(index);
1138 return JS_TRUE;
1139 }
1140
1141 static JSBool
1142 str_lastIndexOf(JSContext *cx, uintN argc, jsval *vp)
1143 {
1144 JSString *str, *str2;
1145 const jschar *text, *pat;
1146 jsint i, j, textlen, patlen;
1147 jsdouble d;
1148
1149 NORMALIZE_THIS(cx, vp, str);
1150 text = JSSTRING_CHARS(str);
1151 textlen = (jsint) JSSTRING_LENGTH(str);
1152
1153 str2 = ArgToRootedString(cx, argc, vp, 0);
1154 if (!str2)
1155 return JS_FALSE;
1156 pat = JSSTRING_CHARS(str2);
1157 patlen = (jsint) JSSTRING_LENGTH(str2);
1158
1159 if (argc > 1) {
1160 d = js_ValueToNumber(cx, &vp[3]);
1161 if (JSVAL_IS_NULL(vp[3]))
1162 return JS_FALSE;
1163 if (JSDOUBLE_IS_NaN(d)) {
1164 i = textlen;
1165 } else {
1166 d = js_DoubleToInteger(d);
1167 if (d < 0)
1168 i = 0;
1169 else if (d > textlen)
1170 i = textlen;
1171 else
1172 i = (jsint)d;
1173 }
1174 } else {
1175 i = textlen;
1176 }
1177
1178 if (patlen == 0) {
1179 *vp = INT_TO_JSVAL(i);
1180 return JS_TRUE;
1181 }
1182
1183 j = 0;
1184 while (i >= 0) {
1185 /* Don't assume that text is NUL-terminated: it could be dependent. */
1186 if (i + j < textlen && text[i + j] == pat[j]) {
1187 if (++j == patlen)
1188 break;
1189 } else {
1190 i--;
1191 j = 0;
1192 }
1193 }
1194 *vp = INT_TO_JSVAL(i);
1195 return JS_TRUE;
1196 }
1197
1198 static JSBool
1199 js_TrimString(JSContext *cx, jsval *vp, JSBool trimLeft, JSBool trimRight)
1200 {
1201 JSString *str;
1202 const jschar *chars;
1203 size_t length, begin, end;
1204
1205 NORMALIZE_THIS(cx, vp, str);
1206 JSSTRING_CHARS_AND_LENGTH(str, chars, length);
1207 begin = 0;
1208 end = length;
1209
1210 if (trimLeft) {
1211 while (begin < length && JS_ISSPACE(chars[begin]))
1212 ++begin;
1213 }
1214
1215 if (trimRight) {
1216 while (end > begin && JS_ISSPACE(chars[end-1]))
1217 --end;
1218 }
1219
1220 str = js_NewDependentString(cx, str, begin, end - begin);
1221 if (!str)
1222 return JS_FALSE;
1223
1224 *vp = STRING_TO_JSVAL(str);
1225 return JS_TRUE;
1226 }
1227
1228 static JSBool
1229 str_trim(JSContext *cx, uintN argc, jsval *vp)
1230 {
1231 return js_TrimString(cx, vp, JS_TRUE, JS_TRUE);
1232 }
1233
1234 static JSBool
1235 str_trimLeft(JSContext *cx, uintN argc, jsval *vp)
1236 {
1237 return js_TrimString(cx, vp, JS_TRUE, JS_FALSE);
1238 }
1239
1240 static JSBool
1241 str_trimRight(JSContext *cx, uintN argc, jsval *vp)
1242 {
1243 return js_TrimString(cx, vp, JS_FALSE, JS_TRUE);
1244 }
1245
1246 /*
1247 * Perl-inspired string functions.
1248 */
1249 typedef struct GlobData {
1250 jsbytecode *pc; /* in: program counter resulting in us matching */
1251 uintN flags; /* inout: mode and flag bits, see below */
1252 uintN optarg; /* in: index of optional flags argument */
1253 JSString *str; /* out: 'this' parameter object as string */
1254 JSRegExp *regexp; /* out: regexp parameter object private data */
1255 } GlobData;
1256
1257 /*
1258 * Mode and flag bit definitions for match_or_replace's GlobData.flags field.
1259 */
1260 #define MODE_MATCH 0x00 /* in: return match array on success */
1261 #define MODE_REPLACE 0x01 /* in: match and replace */
1262 #define MODE_SEARCH 0x02 /* in: search only, return match index or -1 */
1263 #define GET_MODE(f) ((f) & 0x03)
1264 #define FORCE_FLAT 0x04 /* in: force flat (non-regexp) string match */
1265 #define KEEP_REGEXP 0x08 /* inout: keep GlobData.regexp alive for caller
1266 of match_or_replace; if set on input
1267 but clear on output, regexp ownership
1268 does not pass to caller */
1269 #define GLOBAL_REGEXP 0x10 /* out: regexp had the 'g' flag */
1270
1271 static JSBool
1272 match_or_replace(JSContext *cx,
1273 JSBool (*glob)(JSContext *cx, jsint count, GlobData *data),
1274 void (*destroy)(JSContext *cx, GlobData *data),
1275 GlobData *data, uintN argc, jsval *vp)
1276 {
1277 JSString *str, *src, *opt;
1278 JSObject *reobj;
1279 JSRegExp *re;
1280 size_t index, length;
1281 JSBool ok, test;
1282 jsint count;
1283
1284 NORMALIZE_THIS(cx, vp, str);
1285 data->str = str;
1286
1287 if (argc != 0 && VALUE_IS_REGEXP(cx, vp[2])) {
1288 reobj = JSVAL_TO_OBJECT(vp[2]);
1289 re = (JSRegExp *) JS_GetPrivate(cx, reobj);
1290 } else {
1291 src = ArgToRootedString(cx, argc, vp, 0);
1292 if (!src)
1293 return JS_FALSE;
1294 if (data->optarg < argc) {
1295 opt = js_ValueToString(cx, vp[2 + data->optarg]);
1296 if (!opt)
1297 return JS_FALSE;
1298 } else {
1299 opt = NULL;
1300 }
1301 re = js_NewRegExpOpt(cx, src, opt, (data->flags & FORCE_FLAT) != 0);
1302 if (!re)
1303 return JS_FALSE;
1304 reobj = NULL;
1305 }
1306 /* From here on, all control flow must reach the matching DROP. */
1307 data->regexp = re;
1308 HOLD_REGEXP(cx, re);
1309
1310 if (re->flags & JSREG_GLOB)
1311 data->flags |= GLOBAL_REGEXP;
1312 index = 0;
1313 if (GET_MODE(data->flags) == MODE_SEARCH) {
1314 ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp);
1315 if (ok) {
1316 *vp = (*vp == JSVAL_TRUE)
1317 ? INT_TO_JSVAL(cx->regExpStatics.leftContext.length)
1318 : INT_TO_JSVAL(-1);
1319 }
1320 } else if (data->flags & GLOBAL_REGEXP) {
1321 if (reobj) {
1322 /* Set the lastIndex property's reserved slot to 0. */
1323 ok = js_SetLastIndex(cx, reobj, 0);
1324 } else {
1325 ok = JS_TRUE;
1326 }
1327 if (ok) {
1328 length = JSSTRING_LENGTH(str);
1329 for (count = 0; index <= length; count++) {
1330 ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp);
1331 if (!ok || *vp != JSVAL_TRUE)
1332 break;
1333 ok = glob(cx, count, data);
1334 if (!ok)
1335 break;
1336 if (cx->regExpStatics.lastMatch.length == 0) {
1337 if (index == length)
1338 break;
1339 index++;
1340 }
1341 }
1342 if (!ok && destroy)
1343 destroy(cx, data);
1344 }
1345 } else {
1346 if (GET_MODE(data->flags) == MODE_REPLACE) {
1347 test = JS_TRUE;
1348 } else {
1349 /*
1350 * MODE_MATCH implies str_match is being called from a script or a
1351 * scripted function. If the caller cares only about testing null
1352 * vs. non-null return value, optimize away the array object that
1353 * would normally be returned in *vp.
1354 *
1355 * Assume a full array result is required, then prove otherwise.
1356 */
1357 test = JS_FALSE;
1358 if (data->pc && (*data->pc == JSOP_CALL || *data->pc == JSOP_NEW)) {
1359 JS_ASSERT(js_CodeSpec[*data->pc].length == 3);
1360 switch (data->pc[3]) {
1361 case JSOP_POP:
1362 case JSOP_IFEQ:
1363 case JSOP_IFNE:
1364 case JSOP_IFEQX:
1365 case JSOP_IFNEX:
1366 test = JS_TRUE;
1367 break;
1368 default:;
1369 }
1370 }
1371 }
1372 ok = js_ExecuteRegExp(cx, re, str, &index, test, vp);
1373 }
1374
1375 DROP_REGEXP(cx, re);
1376 if (reobj) {
1377 /* Tell our caller that it doesn't need to destroy data->regexp. */
1378 data->flags &= ~KEEP_REGEXP;
1379 } else if (!ok || !(data->flags & KEEP_REGEXP)) {
1380 /* Caller didn't want to keep data->regexp, so null and destroy it. */
1381 data->regexp = NULL;
1382 js_DestroyRegExp(cx, re);
1383 }
1384
1385 return ok;
1386 }
1387
1388 typedef struct MatchData {
1389 GlobData base;
1390 jsval *arrayval; /* NB: local root pointer */
1391 } MatchData;
1392
1393 static JSBool
1394 match_glob(JSContext *cx, jsint count, GlobData *data)
1395 {
1396 MatchData *mdata;
1397 JSObject *arrayobj;
1398 JSSubString *matchsub;
1399 JSString *matchstr;
1400 jsval v;
1401
1402 mdata = (MatchData *)data;
1403 arrayobj = JSVAL_TO_OBJECT(*mdata->arrayval);
1404 if (!arrayobj) {
1405 arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL);
1406 if (!arrayobj)
1407 return JS_FALSE;
1408 *mdata->arrayval = OBJECT_TO_JSVAL(arrayobj);
1409 }
1410 matchsub = &cx->regExpStatics.lastMatch;
1411 matchstr = js_NewStringCopyN(cx, matchsub->chars, matchsub->length);
1412 if (!matchstr)
1413 return JS_FALSE;
1414 v = STRING_TO_JSVAL(matchstr);
1415 JS_ASSERT(count <= JSVAL_INT_MAX);
1416
1417 JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING);
1418 return OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(count), &v);
1419 }
1420
1421 JSBool
1422 js_StringMatchHelper(JSContext *cx, uintN argc, jsval *vp, jsbytecode *pc)
1423 {
1424 JSTempValueRooter tvr;
1425 MatchData mdata;
1426 JSBool ok;
1427
1428 JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
1429 mdata.base.pc = pc;
1430 mdata.base.flags = MODE_MATCH;
1431 mdata.base.optarg = 1;
1432 mdata.arrayval = &tvr.u.value;
1433 ok = match_or_replace(cx, match_glob, NULL, &mdata.base, argc, vp);
1434 if (ok && !JSVAL_IS_NULL(*mdata.arrayval))
1435 *vp = *mdata.arrayval;
1436 JS_POP_TEMP_ROOT(cx, &tvr);
1437 return ok;
1438 }
1439
1440 static JSBool
1441 str_match(JSContext *cx, uintN argc, jsval *vp)
1442 {
1443 JSStackFrame *fp;
1444
1445 for (fp = cx->fp; fp && !fp->regs; fp = fp->down)
1446 JS_ASSERT(!fp->script);
1447 return js_StringMatchHelper(cx, argc, vp, fp ? fp->regs->pc : NULL);
1448 }
1449
1450 #ifdef JS_TRACER
1451 static JSObject* FASTCALL
1452 String_p_match(JSContext* cx, JSString* str, jsbytecode *pc, JSObject* regexp)
1453 {
1454 jsval vp[3] = { JSVAL_NULL, STRING_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) };
1455 if (!js_StringMatchHelper(cx, 1, vp, pc))
1456 return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID);
1457 JS_ASSERT(JSVAL_IS_NULL(vp[0]) ||
1458 (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0]))));
1459 return JSVAL_TO_OBJECT(vp[0]);
1460 }
1461
1462 static JSObject* FASTCALL
1463 String_p_match_obj(JSContext* cx, JSObject* str, jsbytecode *pc, JSObject* regexp)
1464 {
1465 jsval vp[3] = { JSVAL_NULL, OBJECT_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) };
1466 if (!js_StringMatchHelper(cx, 1, vp, pc))
1467 return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID);
1468 JS_ASSERT(JSVAL_IS_NULL(vp[0]) ||
1469 (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0]))));
1470 return JSVAL_TO_OBJECT(vp[0]);
1471 }
1472 #endif
1473
1474 static JSBool
1475 str_search(JSContext *cx, uintN argc, jsval *vp)
1476 {
1477 GlobData data;
1478
1479 data.flags = MODE_SEARCH;
1480 data.optarg = 1;
1481 return match_or_replace(cx, NULL, NULL, &data, argc, vp);
1482 }
1483
1484 typedef struct ReplaceData {
1485 GlobData base; /* base struct state */
1486 JSObject *lambda; /* replacement function object or null */
1487 JSString *repstr; /* replacement string */
1488 jschar *dollar; /* null or pointer to first $ in repstr */
1489 jschar *dollarEnd; /* limit pointer for js_strchr_limit */
1490 jschar *chars; /* result chars, null initially */
1491 size_t length; /* result length, 0 initially */
1492 jsint index; /* index in result of next replacement */
1493 jsint leftIndex; /* left context index in base.str->chars */
1494 JSSubString dollarStr; /* for "$$" interpret_dollar result */
1495 } ReplaceData;
1496
1497 static JSSubString *
1498 interpret_dollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData *rdata,
1499 size_t *skip)
1500 {
1501 JSRegExpStatics *res;
1502 jschar dc, *cp;
1503 uintN num, tmp;
1504
1505 JS_ASSERT(*dp == '$');
1506
1507 /* If there is only a dollar, bail now */
1508 if (dp + 1 >= ep)
1509 return NULL;
1510
1511 /* Interpret all Perl match-induced dollar variables. */
1512 res = &cx->regExpStatics;
1513 dc = dp[1];
1514 if (JS7_ISDEC(dc)) {
1515 /* ECMA-262 Edition 3: 1-9 or 01-99 */
1516 num = JS7_UNDEC(dc);
1517 if (num > res->parenCount)
1518 return NULL;
1519
1520 cp = dp + 2;
1521 if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
1522 tmp = 10 * num + JS7_UNDEC(dc);
1523 if (tmp <= res->parenCount) {
1524 cp++;
1525 num = tmp;
1526 }
1527 }
1528 if (num == 0)
1529 return NULL;
1530
1531 /* Adjust num from 1 $n-origin to 0 array-index-origin. */
1532 num--;
1533 *skip = cp - dp;
1534 return REGEXP_PAREN_SUBSTRING(res, num);
1535 }
1536
1537 *skip = 2;
1538 switch (dc) {
1539 case '$':
1540 rdata->dollarStr.chars = dp;
1541 rdata->dollarStr.length = 1;
1542 return &rdata->dollarStr;
1543 case '&':
1544 return &res->lastMatch;
1545 case '+':
1546 return &res->lastParen;
1547 case '`':
1548 return &res->leftContext;
1549 case '\'':
1550 return &res->rightContext;
1551 }
1552 return NULL;
1553 }
1554
1555 static JSBool
1556 find_replen(JSContext *cx, ReplaceData *rdata, size_t *sizep)
1557 {
1558 JSString *repstr;
1559 size_t replen, skip;
1560 jschar *dp, *ep;
1561 JSSubString *sub;
1562 JSObject *lambda;
1563
1564 lambda = rdata->lambda;
1565 if (lambda) {
1566 uintN argc, i, j, m, n, p;
1567 jsval *invokevp, *sp;
1568 void *mark;
1569 JSBool ok;
1570
1571 /*
1572 * Save the regExpStatics from the current regexp, since they may be
1573 * clobbered by a RegExp usage in the lambda function. Note that all
1574 * members of JSRegExpStatics are JSSubStrings, so not GC roots, save
1575 * input, which is rooted otherwise via vp[1] in str_replace.
1576 */
1577 JSRegExpStatics save = cx->regExpStatics;
1578 JSBool freeMoreParens = JS_FALSE;
1579
1580 /*
1581 * In the lambda case, not only do we find the replacement string's
1582 * length, we compute repstr and return it via rdata for use within
1583 * do_replace. The lambda is called with arguments ($&, $1, $2, ...,
1584 * index, input), i.e., all the properties of a regexp match array.
1585 * For $&, etc., we must create string jsvals from cx->regExpStatics.
1586 * We grab up stack space to keep the newborn strings GC-rooted.
1587 */
1588 p = rdata->base.regexp->parenCount;
1589 argc = 1 + p + 2;
1590 invokevp = js_AllocStack(cx, 2 + argc, &mark);
1591 if (!invokevp)
1592 return JS_FALSE;
1593
1594 /* Push lambda and its 'this' parameter. */
1595 sp = invokevp;
1596 *sp++ = OBJECT_TO_JSVAL(lambda);
1597 *sp++ = OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, lambda));
1598
1599 #define PUSH_REGEXP_STATIC(sub) \
1600 JS_BEGIN_MACRO \
1601 JSString *str = js_NewStringCopyN(cx, \
1602 cx->regExpStatics.sub.chars, \
1603 cx->regExpStatics.sub.length); \
1604 if (!str) { \
1605 ok = JS_FALSE; \
1606 goto lambda_out; \
1607 } \
1608 *sp++ = STRING_TO_JSVAL(str); \
1609 JS_END_MACRO
1610
1611 /* Push $&, $1, $2, ... */
1612 PUSH_REGEXP_STATIC(lastMatch);
1613 i = 0;
1614 m = cx->regExpStatics.parenCount;
1615 n = JS_MIN(m, 9);
1616 for (j = 0; i < n; i++, j++)
1617 PUSH_REGEXP_STATIC(parens[j]);
1618 for (j = 0; i < m; i++, j++)
1619 PUSH_REGEXP_STATIC(moreParens[j]);
1620
1621 /*
1622 * We need to clear moreParens in the top-of-stack cx->regExpStatics
1623 * to it won't be possibly realloc'ed, leaving the bottom-of-stack
1624 * moreParens pointing to freed memory.
1625 */
1626 cx->regExpStatics.moreParens = NULL;
1627 freeMoreParens = JS_TRUE;
1628
1629 #undef PUSH_REGEXP_STATIC
1630
1631 /* Make sure to push undefined for any unmatched parens. */
1632 for (; i < p; i++)
1633 *sp++ = JSVAL_VOID;
1634
1635 /* Push match index and input string. */
1636 *sp++ = INT_TO_JSVAL((jsint)cx->regExpStatics.leftContext.length);
1637 *sp++ = STRING_TO_JSVAL(rdata->base.str);
1638
1639 ok = js_Invoke(cx, argc, invokevp, 0);
1640 if (ok) {
1641 /*
1642 * NB: we count on the newborn string root to hold any string
1643 * created by this js_ValueToString that would otherwise be GC-
1644 * able, until we use rdata->repstr in do_replace.
1645 */
1646 repstr = js_ValueToString(cx, *invokevp);
1647 if (!repstr) {
1648 ok = JS_FALSE;
1649 } else {
1650 rdata->repstr = repstr;
1651 *sizep = JSSTRING_LENGTH(repstr);
1652 }
1653 }
1654
1655 lambda_out:
1656 js_FreeStack(cx, mark);
1657 if (freeMoreParens)
1658 JS_free(cx, cx->regExpStatics.moreParens);
1659 cx->regExpStatics = save;
1660 return ok;
1661 }
1662
1663 repstr = rdata->repstr;
1664 replen = JSSTRING_LENGTH(repstr);
1665 for (dp = rdata->dollar, ep = rdata->dollarEnd; dp;
1666 dp = js_strchr_limit(dp, '$', ep)) {
1667 sub = interpret_dollar(cx, dp, ep, rdata, &skip);
1668 if (sub) {
1669 replen += sub->length - skip;
1670 dp += skip;
1671 }
1672 else
1673 dp++;
1674 }
1675 *sizep = replen;
1676 return JS_TRUE;
1677 }
1678
1679 static void
1680 do_replace(JSContext *cx, ReplaceData *rdata, jschar *chars)
1681 {
1682 JSString *repstr;
1683 jschar *bp, *cp, *dp, *ep;
1684 size_t len, skip;
1685 JSSubString *sub;
1686
1687 repstr = rdata->repstr;
1688 bp = cp = JSSTRING_CHARS(repstr);
1689 for (dp = rdata->dollar, ep = rdata->dollarEnd; dp;
1690 dp = js_strchr_limit(dp, '$', ep)) {
1691 len = dp - cp;
1692 js_strncpy(chars, cp, len);
1693 chars += len;
1694 cp = dp;
1695 sub = interpret_dollar(cx, dp, ep, rdata, &skip);
1696 if (sub) {
1697 len = sub->length;
1698 js_strncpy(chars, sub->chars, len);
1699 chars += len;
1700 cp += skip;
1701 dp += skip;
1702 } else {
1703 dp++;
1704 }
1705 }
1706 js_strncpy(chars, cp, JSSTRING_LENGTH(repstr) - (cp - bp));
1707 }
1708
1709 static void
1710 replace_destroy(JSContext *cx, GlobData *data)
1711 {
1712 ReplaceData *rdata;
1713
1714 rdata = (ReplaceData *)data;
1715 JS_free(cx, rdata->chars);
1716 rdata->chars = NULL;
1717 }
1718
1719 static JSBool
1720 replace_glob(JSContext *cx, jsint count, GlobData *data)
1721 {
1722 ReplaceData *rdata;
1723 JSString *str;
1724 size_t leftoff, leftlen, replen, growth;
1725 const jschar *left;
1726 jschar *chars;
1727
1728 rdata = (ReplaceData *)data;
1729 str = data->str;
1730 leftoff = rdata->leftIndex;
1731 left = JSSTRING_CHARS(str) + leftoff;
1732 leftlen = cx->regExpStatics.lastMatch.chars - left;
1733 rdata->leftIndex = cx->regExpStatics.lastMatch.chars - JSSTRING_CHARS(str);
1734 rdata->leftIndex += cx->regExpStatics.lastMatch.length;
1735 if (!find_replen(cx, rdata, &replen))
1736 return JS_FALSE;
1737 growth = leftlen + replen;
1738 chars = (jschar *)
1739 (rdata->chars
1740 ? JS_realloc(cx, rdata->chars, (rdata->length + growth + 1)
1741 * sizeof(jschar))
1742 : JS_malloc(cx, (growth + 1) * sizeof(jschar)));
1743 if (!chars)
1744 return JS_FALSE;
1745 rdata->chars = chars;
1746 rdata->length += growth;
1747 chars += rdata->index;
1748 rdata->index += growth;
1749 js_strncpy(chars, left, leftlen);
1750 chars += leftlen;
1751 do_replace(cx, rdata, chars);
1752 return JS_TRUE;
1753 }
1754
1755 static JSBool
1756 str_replace(JSContext *cx, uintN argc, jsval *vp)
1757 {
1758 JSObject *lambda;
1759 JSString *repstr;
1760
1761 if (argc >= 2 && JS_TypeOfValue(cx, vp[3]) == JSTYPE_FUNCTION) {
1762 lambda = JSVAL_TO_OBJECT(vp[3]);
1763 repstr = NULL;
1764 } else {
1765 lambda = NULL;
1766 repstr = ArgToRootedString(cx, argc, vp, 1);
1767 if (!repstr)
1768 return JS_FALSE;
1769 }
1770
1771 return js_StringReplaceHelper(cx, argc, lambda, repstr, vp);
1772 }
1773
1774 #ifdef JS_TRACER
1775 static JSString* FASTCALL
1776 String_p_replace_str(JSContext* cx, JSString* str, JSObject* regexp, JSString* repstr)
1777 {
1778 jsval vp[4] = {
1779 JSVAL_NULL, STRING_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp), STRING_TO_JSVAL(repstr)
1780 };
1781 if (!js_StringReplaceHelper(cx, 2, NULL, repstr, vp))
1782 return NULL;
1783 JS_ASSERT(JSVAL_IS_STRING(vp[0]));
1784 return JSVAL_TO_STRING(vp[0]);
1785 }
1786
1787 static JSString* FASTCALL
1788 String_p_replace_str2(JSContext* cx, JSString* str, JSString* patstr, JSString* repstr)
1789 {
1790 jsval vp[4] = {
1791 JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(patstr), STRING_TO_JSVAL(repstr)
1792 };
1793 if (!js_StringReplaceHelper(cx, 2, NULL, repstr, vp))
1794 return NULL;
1795 JS_ASSERT(JSVAL_IS_STRING(vp[0]));
1796 return JSVAL_TO_STRING(vp[0]);
1797 }
1798
1799 static JSString* FASTCALL
1800 String_p_replace_str3(JSContext* cx, JSString* str, JSString* patstr, JSString* repstr,
1801 JSString* flagstr)
1802 {
1803 jsval vp[5] = {
1804 JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(patstr), STRING_TO_JSVAL(repstr),
1805 STRING_TO_JSVAL(flagstr)
1806 };
1807 if (!js_StringReplaceHelper(cx, 3, NULL, repstr, vp))
1808 return NULL;
1809 JS_ASSERT(JSVAL_IS_STRING(vp[0]));
1810 return JSVAL_TO_STRING(vp[0]);
1811 }
1812 #endif
1813
1814 JSBool
1815 js_StringReplaceHelper(JSContext *cx, uintN argc, JSObject *lambda,
1816 JSString *repstr, jsval *vp)
1817 {
1818 ReplaceData rdata;
1819 JSBool ok;
1820 size_t leftlen, rightlen, length;
1821 jschar *chars;
1822 JSString *str;
1823
1824 /*
1825 * For ECMA Edition 3, the first argument is to be converted to a string
1826 * to match in a "flat" sense (without regular expression metachars having
1827 * special meanings) UNLESS the first arg is a RegExp object.
1828 */
1829 rdata.base.flags = MODE_REPLACE | KEEP_REGEXP | FORCE_FLAT;
1830 rdata.base.optarg = 2;
1831
1832 rdata.lambda = lambda;
1833 rdata.repstr = repstr;
1834 if (repstr) {
1835 rdata.dollarEnd = JSSTRING_CHARS(repstr) + JSSTRING_LENGTH(repstr);
1836 rdata.dollar = js_strchr_limit(JSSTRING_CHARS(repstr), '$',
1837 rdata.dollarEnd);
1838 } else {
1839 rdata.dollar = rdata.dollarEnd = NULL;
1840 }
1841 rdata.chars = NULL;
1842 rdata.length = 0;
1843 rdata.index = 0;
1844 rdata.leftIndex = 0;
1845
1846 ok = match_or_replace(cx, replace_glob, replace_destroy, &rdata.base,
1847 argc, vp);
1848 if (!ok)
1849 return JS_FALSE;
1850
1851 if (!rdata.chars) {
1852 if ((rdata.base.flags & GLOBAL_REGEXP) || *vp != JSVAL_TRUE) {
1853 /* Didn't match even once. */
1854 *vp = STRING_TO_JSVAL(rdata.base.str);
1855 goto out;
1856 }
1857 leftlen = cx->regExpStatics.leftContext.length;
1858 ok = find_replen(cx, &rdata, &length);
1859 if (!ok)
1860 goto out;
1861 length += leftlen;
1862 chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
1863 if (!chars) {
1864 ok = JS_FALSE;
1865 goto out;
1866 }
1867 js_strncpy(chars, cx->regExpStatics.leftContext.chars, leftlen);
1868 do_replace(cx, &rdata, chars + leftlen);
1869 rdata.chars = chars;
1870 rdata.length = length;
1871 }
1872
1873 rightlen = cx->regExpStatics.rightContext.length;
1874 length = rdata.length + rightlen;
1875 chars = (jschar *)
1876 JS_realloc(cx, rdata.chars, (length + 1) * sizeof(jschar));
1877 if (!chars) {
1878 JS_free(cx, rdata.chars);
1879 ok = JS_FALSE;
1880 goto out;
1881 }
1882 js_strncpy(chars + rdata.length, cx->regExpStatics.rightContext.chars,
1883 rightlen);
1884 chars[length] = 0;
1885
1886 str = js_NewString(cx, chars, length);
1887 if (!str) {
1888 JS_free(cx, chars);
1889 ok = JS_FALSE;
1890 goto out;
1891 }
1892 *vp = STRING_TO_JSVAL(str);
1893
1894 out:
1895 /* If KEEP_REGEXP is still set, it's our job to destroy regexp now. */
1896 if (rdata.base.flags & KEEP_REGEXP)
1897 js_DestroyRegExp(cx, rdata.base.regexp);
1898 return ok;
1899 }
1900
1901 /*
1902 * Subroutine used by str_split to find the next split point in str, starting
1903 * at offset *ip and looking either for the separator substring given by sep, or
1904 * for the next re match. In the re case, return the matched separator in *sep,
1905 * and the possibly updated offset in *ip.
1906 *
1907 * Return -2 on error, -1 on end of string, >= 0 for a valid index of the next
1908 * separator occurrence if found, or str->length if no separator is found.
1909 */
1910 static jsint
1911 find_split(JSContext *cx, JSString *str, JSRegExp *re, jsint *ip,
1912 JSSubString *sep)
1913 {
1914 jsint i, j, k;
1915 size_t length;
1916 jschar *chars;
1917
1918 /*
1919 * Stop if past end of string. If at end of string, we will compare the
1920 * null char stored there (by js_NewString*) to sep->chars[j] in the while
1921 * loop at the end of this function, so that
1922 *
1923 * "ab,".split(',') => ["ab", ""]
1924 *
1925 * and the resulting array converts back to the string "ab," for symmetry.
1926 * However, we ape Perl and do this only if there is a sufficiently large
1927 * limit argument (see str_split).
1928 */
1929 i = *ip;
1930 length = JSSTRING_LENGTH(str);
1931 if ((size_t)i > length)
1932 return -1;
1933
1934 chars = JSSTRING_CHARS(str);
1935
1936 /*
1937 * Match a regular expression against the separator at or above index i.
1938 * Call js_ExecuteRegExp with true for the test argument. On successful
1939 * match, get the separator from cx->regExpStatics.lastMatch.
1940 */
1941 if (re) {
1942 size_t index;
1943 jsval rval;
1944
1945 again:
1946 /* JS1.2 deviated from Perl by never matching at end of string. */
1947 index = (size_t)i;
1948 if (!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &rval))
1949 return -2;
1950 if (rval != JSVAL_TRUE) {
1951 /* Mismatch: ensure our caller advances i past end of string. */
1952 sep->length = 1;
1953 return length;
1954 }
1955 i = (jsint)index;
1956 *sep = cx->regExpStatics.lastMatch;
1957 if (sep->length == 0) {
1958 /*
1959 * Empty string match: never split on an empty match at the start
1960 * of a find_split cycle. Same rule as for an empty global match
1961 * in match_or_replace.
1962 */
1963 if (i == *ip) {
1964 /*
1965 * "Bump-along" to avoid sticking at an empty match, but don't
1966 * bump past end of string -- our caller must do that by adding
1967 * sep->length to our return value.
1968 */
1969 if ((size_t)i == length)
1970 return -1;
1971 i++;
1972 goto again;
1973 }
1974 if ((size_t)i == length) {
1975 /*
1976 * If there was a trivial zero-length match at the end of the
1977 * split, then we shouldn't output the matched string at the end
1978 * of the split array. See ECMA-262 Ed. 3, 15.5.4.14, Step 15.
1979 */
1980 sep->chars = NULL;
1981 }
1982 }
1983 JS_ASSERT((size_t)i >= sep->length);
1984 return i - sep->length;
1985 }
1986
1987 /*
1988 * Special case: if sep is the empty string, split str into one character
1989 * substrings. Let our caller worry about whether to split once at end of
1990 * string into an empty substring.
1991 */
1992 if (sep->length == 0)
1993 return ((size_t)i == length) ? -1 : i + 1;
1994
1995 /*
1996 * Now that we know sep is non-empty, search starting at i in str for an
1997 * occurrence of all of sep's chars. If we find them, return the index of
1998 * the first separator char. Otherwise, return length.
1999 */
2000 j = 0;
2001 while ((size_t)(k = i + j) < length) {
2002 if (chars[k] == sep->chars[j]) {
2003 if ((size_t)++j == sep->length)
2004 return i;
2005 } else {
2006 i++;
2007 j = 0;
2008 }
2009 }
2010 return k;
2011 }
2012
2013 static JSBool
2014 str_split(JSContext *cx, uintN argc, jsval *vp)
2015 {
2016 JSString *str, *sub;
2017 JSObject *arrayobj;
2018 jsval v;
2019 JSBool ok, limited;
2020 JSRegExp *re;
2021 JSSubString *sep, tmp;
2022 jsdouble d;
2023 jsint i, j;
2024 uint32 len, limit;
2025
2026 NORMALIZE_THIS(cx, vp, str);
2027
2028 arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL);
2029 if (!arrayobj)
2030 return JS_FALSE;
2031 *vp = OBJECT_TO_JSVAL(arrayobj);
2032
2033 if (argc == 0) {
2034 v = STRING_TO_JSVAL(str);
2035 ok = OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(0), &v);
2036 } else {
2037 if (VALUE_IS_REGEXP(cx, vp[2])) {
2038 re = (JSRegExp *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(vp[2]));
2039 sep = &tmp;
2040
2041 /* Set a magic value so we can detect a successful re match. */
2042 sep->chars = NULL;
2043 sep->length = 0;
2044 } else {
2045 JSString *str2 = js_ValueToString(cx, vp[2]);
2046 if (!str2)
2047 return JS_FALSE;
2048 vp[2] = STRING_TO_JSVAL(str2);
2049
2050 /*
2051 * Point sep at a local copy of str2's header because find_split
2052 * will modify sep->length.
2053 */
2054 JSSTRING_CHARS_AND_LENGTH(str2, tmp.chars, tmp.length);
2055 sep = &tmp;
2056 re = NULL;
2057 }
2058
2059 /* Use the second argument as the split limit, if given. */
2060 limited = (argc > 1) && !JSVAL_IS_VOID(vp[3]);
2061 limit = 0; /* Avoid warning. */
2062 if (limited) {
2063 d = js_ValueToNumber(cx, &vp[3]);
2064 if (JSVAL_IS_NULL(vp[3]))
2065 return JS_FALSE;
2066
2067 /* Clamp limit between 0 and 1 + string length. */
2068 limit = js_DoubleToECMAUint32(d);
2069 if (limit > JSSTRING_LENGTH(str))
2070 limit = 1 + JSSTRING_LENGTH(str);
2071 }
2072
2073 len = i = 0;
2074 while ((j = find_split(cx, str, re, &i, sep)) >= 0) {
2075 if (limited && len >= limit)
2076 break;
2077 sub = js_NewDependentString(cx, str, i, (size_t)(j - i));
2078 if (!sub)
2079 return JS_FALSE;
2080 v = STRING_TO_JSVAL(sub);
2081 if (!JS_SetElement(cx, arrayobj, len, &v))
2082 return JS_FALSE;
2083 len++;
2084
2085 /*
2086 * Imitate perl's feature of including parenthesized substrings
2087 * that matched part of the delimiter in the new array, after the
2088 * split substring that was delimited.
2089 */
2090 if (re && sep->chars) {
2091 uintN num;
2092 JSSubString *parsub;
2093
2094 for (num = 0; num < cx->regExpStatics.parenCount; num++) {
2095 if (limited && len >= limit)
2096 break;
2097 parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num);
2098 sub = js_NewStringCopyN(cx, parsub->chars, parsub->length);
2099 if (!sub)
2100 return JS_FALSE;
2101 v = STRING_TO_JSVAL(sub);
2102 if (!JS_SetElement(cx, arrayobj, len, &v))
2103 return JS_FALSE;
2104 len++;
2105 }
2106 sep->chars = NULL;
2107 }
2108 i = j + sep->length;
2109 }
2110 ok = (j != -2);
2111 }
2112 return ok;
2113 }
2114
2115 #ifdef JS_TRACER
2116 static JSObject* FASTCALL
2117 String_p_split(JSContext* cx, JSString* str, JSString* sepstr)
2118 {
2119 // FIXME: Avoid building and then parsing this array.
2120 jsval vp[4] = { JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(sepstr), JSVAL_VOID };
2121 if (!str_split(cx, 2, vp))
2122 return NULL;
2123 JS_ASSERT(JSVAL_IS_OBJECT(vp[0]));
2124 return JSVAL_TO_OBJECT(vp[0]);
2125 }
2126 #endif
2127
2128 #if JS_HAS_PERL_SUBSTR
2129 static JSBool
2130 str_substr(JSContext *cx, uintN argc, jsval *vp)
2131 {
2132 JSString *str;
2133 jsdouble d;
2134 jsdouble length, begin, end;
2135
2136 NORMALIZE_THIS(cx, vp, str);
2137 if (argc != 0) {
2138 d = js_ValueToNumber(cx, &vp[2]);
2139 if (JSVAL_IS_NULL(vp[2]))
2140 return JS_FALSE;
2141 length = JSSTRING_LENGTH(str);
2142 begin = js_DoubleToInteger(d);
2143 if (begin < 0) {
2144 begin += length;
2145 if (begin < 0)
2146 begin = 0;
2147 } else if (begin > length) {
2148 begin = length;
2149 }
2150
2151 if (argc == 1) {
2152 end = length;
2153 } else {
2154 d = js_ValueToNumber(cx, &vp[3]);
2155 if (JSVAL_IS_NULL(vp[3]))
2156 return JS_FALSE;
2157 end = js_DoubleToInteger(d);
2158 if (end < 0)
2159 end = 0;
2160 end += begin;
2161 if (end > length)
2162 end = length;
2163 }
2164
2165 str = js_NewDependentString(cx, str,
2166 (size_t)begin,
2167 (size_t)(end - begin));
2168 if (!str)
2169 return JS_FALSE;
2170 }
2171 *vp = STRING_TO_JSVAL(str);
2172 return JS_TRUE;
2173 }
2174 #endif /* JS_HAS_PERL_SUBSTR */
2175
2176 /*
2177 * Python-esque sequence operations.
2178 */
2179 static JSBool
2180 str_concat(JSContext *cx, uintN argc, jsval *vp)
2181 {
2182 JSString *str, *str2;
2183 jsval *argv;
2184 uintN i;
2185
2186 NORMALIZE_THIS(cx, vp, str);
2187
2188 for (i = 0, argv = vp + 2; i < argc; i++) {
2189 str2 = js_ValueToString(cx, argv[i]);
2190 if (!str2)
2191 return JS_FALSE;
2192 argv[i] = STRING_TO_JSVAL(str2);
2193
2194 str = js_ConcatStrings(cx, str, str2);
2195 if (!str)
2196 return JS_FALSE;
2197 }
2198
2199 *vp = STRING_TO_JSVAL(str);
2200 return JS_TRUE;
2201 }
2202
2203 #ifdef JS_TRACER
2204 static JSString* FASTCALL
2205 String_p_concat_1int(JSContext* cx, JSString* str, int32 i)
2206 {
2207 // FIXME: should be able to use stack buffer and avoid istr...
2208 JSString* istr = js_NumberToString(cx, i);
2209 if (!istr)
2210 return NULL;
2211 return js_ConcatStrings(cx, str, istr);
2212 }
2213
2214 static JSString* FASTCALL
2215 String_p_concat_2str(JSContext* cx, JSString* str, JSString* a, JSString* b)
2216 {
2217 str = js_ConcatStrings(cx, str, a);
2218 if (str)
2219 return js_ConcatStrings(cx, str, b);
2220 return NULL;
2221 }
2222
2223 static JSString* FASTCALL
2224 String_p_concat_3str(JSContext* cx, JSString* str, JSString* a, JSString* b, JSString* c)
2225 {
2226 str = js_ConcatStrings(cx, str, a);
2227 if (str) {
2228 str = js_ConcatStrings(cx, str, b);
2229 if (str)
2230 return js_ConcatStrings(cx, str, c);
2231 }
2232 return NULL;
2233 }
2234 #endif
2235
2236 static JSBool
2237 str_slice(JSContext *cx, uintN argc, jsval *vp)
2238 {
2239 jsval t, v;
2240 JSString *str;
2241
2242 t = vp[1];
2243 v = vp[2];
2244 if (argc == 1 && JSVAL_IS_STRING(t) && JSVAL_IS_INT(v)) {
2245 size_t begin, end, length;
2246
2247 str = JSVAL_TO_STRING(t);
2248 begin = JSVAL_TO_INT(v);
2249 end = JSSTRING_LENGTH(str);
2250 if (begin <= end) {
2251 length = end - begin;
2252 if (length == 0) {
2253 str = cx->runtime->emptyString;
2254 } else {
2255 str = (length == 1)
2256 ? js_GetUnitString(cx, str, begin)
2257 : js_NewDependentString(cx, str, begin, length);
2258 if (!str)
2259 return JS_FALSE;
2260 }
2261 *vp = STRING_TO_JSVAL(str);
2262 return JS_TRUE;
2263 }
2264 }
2265
2266 NORMALIZE_THIS(cx, vp, str);
2267
2268 if (argc != 0) {
2269 double begin, end, length;
2270
2271 begin = js_ValueToNumber(cx, &vp[2]);
2272 if (JSVAL_IS_NULL(vp[2]))
2273 return JS_FALSE;
2274 begin = js_DoubleToInteger(begin);
2275 length = JSSTRING_LENGTH(str);
2276 if (begin < 0) {
2277 begin += length;
2278 if (begin < 0)
2279 begin = 0;
2280 } else if (begin > length) {
2281 begin = length;
2282 }
2283
2284 if (argc == 1) {
2285 end = length;
2286 } else {
2287 end = js_ValueToNumber(cx, &vp[3]);
2288 if (JSVAL_IS_NULL(vp[3]))
2289 return JS_FALSE;
2290 end = js_DoubleToInteger(end);
2291 if (end < 0) {
2292 end += length;
2293 if (end < 0)
2294 end = 0;
2295 } else if (end > length) {
2296 end = length;
2297 }
2298 if (end < begin)
2299 end = begin;
2300 }
2301
2302 str = js_NewDependentString(cx, str,
2303 (size_t)begin,
2304 (size_t)(end - begin));
2305 if (!str)
2306 return JS_FALSE;
2307 }
2308 *vp = STRING_TO_JSVAL(str);
2309 return JS_TRUE;
2310 }
2311
2312 #if JS_HAS_STR_HTML_HELPERS
2313 /*
2314 * HTML composition aids.
2315 */
2316 static JSBool
2317 tagify(JSContext *cx, const char *begin, JSString *param, const char *end,
2318 jsval *vp)
2319 {
2320 JSString *str;
2321 jschar *tagbuf;
2322 size_t beglen, endlen, parlen, taglen;
2323 size_t i, j;
2324
2325 NORMALIZE_THIS(cx, vp, str);
2326
2327 if (!end)
2328 end = begin;
2329
2330 beglen = strlen(begin);
2331 taglen = 1 + beglen + 1; /* '<begin' + '>' */
2332 parlen = 0; /* Avoid warning. */
2333 if (param) {
2334 parlen = JSSTRING_LENGTH(param);
2335 taglen += 2 + parlen + 1; /* '="param"' */
2336 }
2337 endlen = strlen(end);
2338 taglen += JSSTRING_LENGTH(str) + 2 + endlen + 1; /* 'str</end>' */
2339
2340 if (taglen >= ~(size_t)0 / sizeof(jschar)) {
2341 js_ReportAllocationOverflow(cx);
2342 return JS_FALSE;
2343 }
2344
2345 tagbuf = (jschar *) JS_malloc(cx, (taglen + 1) * sizeof(jschar));
2346 if (!tagbuf)
2347 return JS_FALSE;
2348
2349 j = 0;
2350 tagbuf[j++] = '<';
2351 for (i = 0; i < beglen; i++)
2352 tagbuf[j++] = (jschar)begin[i];
2353 if (param) {
2354 tagbuf[j++] = '=';
2355 tagbuf[j++] = '"';
2356 js_strncpy(&tagbuf[j], JSSTRING_CHARS(param), parlen);
2357 j += parlen;
2358 tagbuf[j++] = '"';
2359 }
2360 tagbuf[j++] = '>';
2361 js_strncpy(&tagbuf[j], JSSTRING_CHARS(str), JSSTRING_LENGTH(str));
2362 j += JSSTRING_LENGTH(str);
2363 tagbuf[j++] = '<';
2364 tagbuf[j++] = '/';
2365 for (i = 0; i < endlen; i++)
2366 tagbuf[j++] = (jschar)end[i];
2367 tagbuf[j++] = '>';
2368 JS_ASSERT(j == taglen);
2369 tagbuf[j] = 0;
2370
2371 str = js_NewString(cx, tagbuf, taglen);
2372 if (!str) {
2373 free((char *)tagbuf);
2374 return JS_FALSE;
2375 }
2376 *vp = STRING_TO_JSVAL(str);
2377 return JS_TRUE;
2378 }
2379
2380 static JSBool
2381 tagify_value(JSContext *cx, uintN argc, jsval *vp,
2382 const char *begin, const char *end)
2383 {
2384 JSString *param;
2385
2386 param = ArgToRootedString(cx, argc, vp, 0);
2387 if (!param)
2388 return JS_FALSE;
2389 return tagify(cx, begin, param, end, vp);
2390 }
2391
2392 static JSBool
2393 str_bold(JSContext *cx, uintN argc, jsval *vp)
2394 {
2395 return tagify(cx, "b", NULL, NULL, vp);
2396 }
2397
2398 static JSBool
2399 str_italics(JSContext *cx, uintN argc, jsval *vp)
2400 {
2401 return tagify(cx, "i", NULL, NULL, vp);
2402 }
2403
2404 static JSBool
2405 str_fixed(JSContext *cx, uintN argc, jsval *vp)
2406 {
2407 return tagify(cx, "tt", NULL, NULL, vp);
2408 }
2409
2410 static JSBool
2411 str_fontsize(JSContext *cx, uintN argc, jsval *vp)
2412 {
2413 return tagify_value(cx, argc, vp, "font size", "font");
2414 }
2415
2416 static JSBool
2417 str_fontcolor(JSContext *cx, uintN argc, jsval *vp)
2418 {
2419 return tagify_value(cx, argc, vp, "font color", "font");
2420 }
2421
2422 static JSBool
2423 str_link(JSContext *cx, uintN argc, jsval *vp)
2424 {
2425 return tagify_value(cx, argc, vp, "a href", "a");
2426 }
2427
2428 static JSBool
2429 str_anchor(JSContext *cx, uintN argc, jsval *vp)
2430 {
2431 return tagify_value(cx, argc, vp, "a name", "a");
2432 }
2433
2434 static JSBool
2435 str_strike(JSContext *cx, uintN argc, jsval *vp)
2436 {
2437 return tagify(cx, "strike", NULL, NULL, vp);
2438 }
2439
2440 static JSBool
2441 str_small(JSContext *cx, uintN argc, jsval *vp)
2442 {
2443 return tagify(cx, "small", NULL, NULL, vp);
2444 }
2445
2446 static JSBool
2447 str_big(JSContext *cx, uintN argc, jsval *vp)
2448 {
2449 return tagify(cx, "big", NULL, NULL, vp);
2450 }
2451
2452 static JSBool
2453 str_blink(JSContext *cx, uintN argc, jsval *vp)
2454 {
2455 return tagify(cx, "blink", NULL, NULL, vp);
2456 }
2457
2458 static JSBool
2459 str_sup(JSContext *cx, uintN argc, jsval *vp)
2460 {
2461 return tagify(cx, "sup", NULL, NULL, vp);
2462 }
2463
2464 static JSBool
2465 str_sub(JSContext *cx, uintN argc, jsval *vp)
2466 {
2467 return tagify(cx, "sub", NULL, NULL, vp);
2468 }
2469 #endif /* JS_HAS_STR_HTML_HELPERS */
2470
2471 #ifdef JS_TRACER
2472 JSString* FASTCALL
2473 js_String_getelem(JSContext* cx, JSString* str, int32 i)
2474 {
2475 if ((size_t)i >= JSSTRING_LENGTH(str))
2476 return NULL;
2477 return js_GetUnitString(cx, str, (size_t)i);
2478 }
2479 #endif
2480
2481 JS_DEFINE_CALLINFO_2(extern, BOOL, js_EqualStrings, STRING, STRING, 1, 1)
2482 JS_DEFINE_CALLINFO_2(extern, INT32, js_CompareStrings, STRING, STRING, 1, 1)
2483
2484 JS_DEFINE_TRCINFO_1(str_toString,
2485 (2, (extern, STRING_FAIL, String_p_toString, CONTEXT, THIS, 1, 1)))
2486 JS_DEFINE_TRCINFO_2(str_substring,
2487 (4, (static, STRING_FAIL, String_p_substring, CONTEXT, THIS_STRING, INT32, INT32, 1, 1)),
2488 (3, (static, STRING_FAIL, String_p_substring_1, CONTEXT, THIS_STRING, INT32, 1, 1)))
2489 JS_DEFINE_TRCINFO_1(str_charAt,
2490 (3, (extern, STRING_FAIL, js_String_getelem, CONTEXT, THIS_STRING, INT32, 1, 1)))
2491 JS_DEFINE_TRCINFO_1(str_charCodeAt,
2492 (2, (extern, INT32_FAIL, js_String_p_charCodeAt, THIS_STRING, INT32, 1, 1)))
2493 JS_DEFINE_TRCINFO_4(str_concat,
2494 (3, (static, STRING_FAIL, String_p_concat_1int, CONTEXT, THIS_STRING, INT32, 1, 1)),
2495 (3, (extern, STRING_FAIL, js_ConcatStrings, CONTEXT, THIS_STRING, STRING, 1, 1)),
2496 (4, (static, STRING_FAIL, String_p_concat_2str, CONTEXT, THIS_STRING, STRING, STRING, 1, 1)),
2497 (5, (static, STRING_FAIL, String_p_concat_3str, CONTEXT, THIS_STRING, STRING, STRING, STRING, 1, 1)))
2498 JS_DEFINE_TRCINFO_2(str_match,
2499 (4, (static, OBJECT_FAIL_VOID, String_p_match, CONTEXT, THIS_STRING, PC, REGEXP, 1, 1)),
2500 (4, (static, OBJECT_FAIL_VOID, String_p_match_obj, CONTEXT, THIS, PC, REGEXP, 1, 1)))
2501 JS_DEFINE_TRCINFO_3(str_replace,
2502 (4, (static, STRING_FAIL, String_p_replace_str, CONTEXT, THIS_STRING, REGEXP, STRING, 1, 1)),
2503 (4, (static, STRING_FAIL, String_p_replace_str2, CONTEXT, THIS_STRING, STRING, STRING, 1, 1)),
2504 (5, (static, STRING_FAIL, String_p_replace_str3, CONTEXT, THIS_STRING, STRING, STRING, STRING, 1, 1)))
2505 JS_DEFINE_TRCINFO_1(str_split,
2506 (3, (static, OBJECT_FAIL_NULL, String_p_split, CONTEXT, THIS_STRING, STRING, 0, 0)))
2507 JS_DEFINE_TRCINFO_1(str_toLowerCase,
2508 (2, (extern, STRING_FAIL, js_toLowerCase, CONTEXT, THIS_STRING, 1, 1)))
2509 JS_DEFINE_TRCINFO_1(str_toUpperCase,
2510 (2, (extern, STRING_FAIL, js_toUpperCase, CONTEXT, THIS_STRING, 1, 1)))
2511
2512 #define GENERIC JSFUN_GENERIC_NATIVE
2513 #define PRIMITIVE JSFUN_THISP_PRIMITIVE
2514 #define GENERIC_PRIMITIVE (GENERIC | PRIMITIVE)
2515
2516 static JSFunctionSpec string_methods[] = {
2517 #if JS_HAS_TOSOURCE
2518 JS_FN("quote", str_quote, 0,GENERIC_PRIMITIVE),
2519 JS_FN(js_toSource_str, str_toSource, 0,JSFUN_THISP_STRING),
2520 #endif
2521
2522 /* Java-like methods. */
2523 JS_TN(js_toString_str, str_toString, 0,JSFUN_THISP_STRING, str_toString_trcinfo),
2524 JS_FN(js_valueOf_str, str_toString, 0,JSFUN_THISP_STRING),
2525 JS_FN(js_toJSON_str, str_toString, 0,JSFUN_THISP_STRING),
2526 JS_TN("substring", str_substring, 2,GENERIC_PRIMITIVE, str_substring_trcinfo),
2527 JS_TN("toLowerCase", str_toLowerCase, 0,GENERIC_PRIMITIVE, str_toLowerCase_trcinfo),
2528 JS_TN("toUpperCase", str_toUpperCase, 0,GENERIC_PRIMITIVE, str_toUpperCase_trcinfo),
2529 JS_TN("charAt", str_charAt, 1,GENERIC_PRIMITIVE, str_charAt_trcinfo),
2530 JS_TN("charCodeAt", str_charCodeAt, 1,GENERIC_PRIMITIVE, str_charCodeAt_trcinfo),
2531 JS_FN("indexOf", str_indexOf, 1,GENERIC_PRIMITIVE),
2532 JS_FN("lastIndexOf", str_lastIndexOf, 1,GENERIC_PRIMITIVE),
2533 JS_FN("trim", str_trim, 0,GENERIC_PRIMITIVE),
2534 JS_FN("trimLeft", str_trimLeft, 0,GENERIC_PRIMITIVE),
2535 JS_FN("trimRight", str_trimRight, 0,GENERIC_PRIMITIVE),
2536 JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,GENERIC_PRIMITIVE),
2537 JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,GENERIC_PRIMITIVE),
2538 JS_FN("localeCompare", str_localeCompare, 1,GENERIC_PRIMITIVE),
2539
2540 /* Perl-ish methods (search is actually Python-esque). */
2541 JS_TN("match", str_match, 1,GENERIC_PRIMITIVE, str_match_trcinfo),
2542 JS_FN("search", str_search, 1,GENERIC_PRIMITIVE),
2543 JS_TN("replace", str_replace, 2,GENERIC_PRIMITIVE, str_replace_trcinfo),
2544 JS_TN("split", str_split, 2,GENERIC_PRIMITIVE, str_split_trcinfo),
2545 #if JS_HAS_PERL_SUBSTR
2546 JS_FN("substr", str_substr, 2,GENERIC_PRIMITIVE),
2547 #endif
2548
2549 /* Python-esque sequence methods. */
2550 JS_TN("concat", str_concat, 1,GENERIC_PRIMITIVE, str_concat_trcinfo),
2551 JS_FN("slice", str_slice, 2,GENERIC_PRIMITIVE),
2552
2553 /* HTML string methods. */
2554 #if JS_HAS_STR_HTML_HELPERS
2555 JS_FN("bold", str_bold, 0,PRIMITIVE),
2556 JS_FN("italics", str_italics, 0,PRIMITIVE),
2557 JS_FN("fixed", str_fixed, 0,PRIMITIVE),
2558 JS_FN("fontsize", str_fontsize, 1,PRIMITIVE),
2559 JS_FN("fontcolor", str_fontcolor, 1,PRIMITIVE),
2560 JS_FN("link", str_link, 1,PRIMITIVE),
2561 JS_FN("anchor", str_anchor, 1,PRIMITIVE),
2562 JS_FN("strike", str_strike, 0,PRIMITIVE),
2563 JS_FN("small", str_small, 0,PRIMITIVE),
2564 JS_FN("big", str_big, 0,PRIMITIVE),
2565 JS_FN("blink", str_blink, 0,PRIMITIVE),
2566 JS_FN("sup", str_sup, 0,PRIMITIVE),
2567 JS_FN("sub", str_sub, 0,PRIMITIVE),
2568 #endif
2569
2570 JS_FS_END
2571 };
2572
2573 static JSBool
2574 String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
2575 {
2576 JSString *str;
2577
2578 if (argc > 0) {
2579 str = js_ValueToString(cx, argv[0]);
2580 if (!str)
2581 return JS_FALSE;
2582 argv[0] = STRING_TO_JSVAL(str);
2583 } else {
2584 str = cx->runtime->emptyString;
2585 }
2586 if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
2587 *rval = STRING_TO_JSVAL(str);
2588 return JS_TRUE;
2589 }
2590 STOBJ_SET_SLOT(obj, JSSLOT_PRIVATE, STRING_TO_JSVAL(str));
2591 return JS_TRUE;
2592 }
2593
2594 static JSBool
2595 str_fromCharCode(JSContext *cx, uintN argc, jsval *vp)
2596 {
2597 jsval *argv;
2598 uintN i;
2599 uint16 code;
2600 jschar *chars;
2601 JSString *str;
2602
2603 argv = vp + 2;
2604 JS_ASSERT(argc < ARRAY_INIT_LIMIT);
2605 if (argc == 1 &&
2606 (code = js_ValueToUint16(cx, &argv[0])) < UNIT_STRING_LIMIT) {
2607 str = js_GetUnitStringForChar(cx, code);
2608 if (!str)
2609 return JS_FALSE;
2610 *vp = STRING_TO_JSVAL(str);
2611 return JS_TRUE;
2612 }
2613 chars = (jschar *) JS_malloc(cx, (argc + 1) * sizeof(jschar));
2614 if (!chars)
2615 return JS_FALSE;
2616 for (i = 0; i < argc; i++) {
2617 code = js_ValueToUint16(cx, &argv[i]);
2618 if (JSVAL_IS_NULL(argv[i])) {
2619 JS_free(cx, chars);
2620 return JS_FALSE;
2621 }
2622 chars[i] = (jschar)code;
2623 }
2624 chars[i] = 0;
2625 str = js_NewString(cx, chars, argc);
2626 if (!str) {
2627 JS_free(cx, chars);
2628 return JS_FALSE;
2629 }
2630 *vp = STRING_TO_JSVAL(str);
2631 return JS_TRUE;
2632 }
2633
2634 #ifdef JS_TRACER
2635 static JSString* FASTCALL
2636 String_fromCharCode(JSContext* cx, int32 i)
2637 {
2638 JS_ASSERT(JS_ON_TRACE(cx));
2639 jschar c = (jschar)i;
2640 if (c < UNIT_STRING_LIMIT)
2641 return js_GetUnitStringForChar(cx, c);
2642 return js_NewStringCopyN(cx, &c, 1);
2643 }
2644 #endif
2645
2646 JS_DEFINE_TRCINFO_1(str_fromCharCode,
2647 (2, (static, STRING_FAIL, String_fromCharCode, CONTEXT, INT32, 1, 1)))
2648
2649 static JSFunctionSpec string_static_methods[] = {
2650 JS_TN("fromCharCode", str_fromCharCode, 1, 0, str_fromCharCode_trcinfo),
2651 JS_FS_END
2652 };
2653
2654 static JSHashNumber
2655 js_hash_string_pointer(const void *key)
2656 {
2657 return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS;
2658 }
2659
2660 JSBool
2661 js_InitRuntimeStringState(JSContext *cx)
2662 {
2663 JSRuntime *rt;
2664
2665 rt = cx->runtime;
2666 rt->emptyString = ATOM_TO_STRING(rt->atomState.emptyAtom);
2667 return JS_TRUE;
2668 }
2669
2670 JSBool
2671 js_InitDeflatedStringCache(JSRuntime *rt)
2672 {
2673 JSHashTable *cache;
2674
2675 /* Initialize string cache */
2676 JS_ASSERT(!rt->deflatedStringCache);
2677 cache = JS_NewHashTable(8, js_hash_string_pointer,
2678 JS_CompareValues, JS_CompareValues,
2679 NULL, NULL);
2680 if (!cache)
2681 return JS_FALSE;
2682 rt->deflatedStringCache = cache;
2683
2684 #ifdef JS_THREADSAFE
2685 JS_ASSERT(!rt->deflatedStringCacheLock);
2686 rt->deflatedStringCacheLock = JS_NEW_LOCK();
2687 if (!rt->deflatedStringCacheLock)
2688 return JS_FALSE;
2689 #endif
2690 return JS_TRUE;
2691 }
2692
2693 #define UNIT_STRING_SPACE(sp) ((jschar *) ((sp) + UNIT_STRING_LIMIT))
2694 #define UNIT_STRING_SPACE_RT(rt) UNIT_STRING_SPACE((rt)->unitStrings)
2695
2696 #define IN_UNIT_STRING_SPACE(sp,cp) \
2697 ((size_t)((cp) - UNIT_STRING_SPACE(sp)) < 2 * UNIT_STRING_LIMIT)
2698 #define IN_UNIT_STRING_SPACE_RT(rt,cp) \
2699 IN_UNIT_STRING_SPACE((rt)->unitStrings, cp)
2700
2701 JSString *
2702 js_GetUnitStringForChar(JSContext *cx, jschar c)
2703 {
2704 jschar *cp, i;
2705 JSRuntime *rt;
2706 JSString **sp;
2707
2708 JS_ASSERT(c < UNIT_STRING_LIMIT);
2709 rt = cx->runtime;
2710 if (!rt->unitStrings) {
2711 sp = (JSString **) calloc(UNIT_STRING_LIMIT * sizeof(JSString *) +
2712 UNIT_STRING_LIMIT * 2 * sizeof(jschar),
2713 1);
2714 if (!sp) {
2715 JS_ReportOutOfMemory(cx);
2716 return NULL;
2717 }
2718 cp = UNIT_STRING_SPACE(sp);
2719 for (i = 0; i < UNIT_STRING_LIMIT; i++) {
2720 *cp = i;
2721 cp += 2;
2722 }
2723 JS_LOCK_GC(rt);
2724 if (!rt->unitStrings) {
2725 rt->unitStrings = sp;
2726 JS_UNLOCK_GC(rt);
2727 } else {
2728 JS_UNLOCK_GC(rt);
2729 free(sp);
2730 }
2731 }
2732 if (!rt->unitStrings[c]) {
2733 JSString *str;
2734
2735 cp = UNIT_STRING_SPACE_RT(rt);
2736 str = js_NewString(cx, cp + 2 * c, 1);
2737 if (!str)
2738 return NULL;
2739 JS_LOCK_GC(rt);
2740 if (!rt->unitStrings[c])
2741 rt->unitStrings[c] = str;
2742 JS_UNLOCK_GC(rt);
2743 }
2744 return rt->unitStrings[c];
2745 }
2746
2747 JSString *
2748 js_GetUnitString(JSContext *cx, JSString *str, size_t index)
2749 {
2750 jschar c;
2751
2752 JS_ASSERT(index < JSSTRING_LENGTH(str));
2753 c = JSSTRING_CHARS(str)[index];
2754 if (c >= UNIT_STRING_LIMIT)
2755 return js_NewDependentString(cx, str, index, 1);
2756 return js_GetUnitStringForChar(cx, c);
2757 }
2758
2759 void
2760 js_FinishUnitStrings(JSRuntime *rt)
2761 {
2762 free(rt->unitStrings);
2763 rt->unitStrings = NULL;
2764 }
2765
2766 void
2767 js_FinishRuntimeStringState(JSContext *cx)
2768 {
2769 cx->runtime->emptyString = NULL;
2770 }
2771
2772 void
2773 js_FinishDeflatedStringCache(JSRuntime *rt)
2774 {
2775 if (rt->deflatedStringCache) {
2776 JS_HashTableDestroy(rt->deflatedStringCache);
2777 rt->deflatedStringCache = NULL;
2778 }
2779 #ifdef JS_THREADSAFE
2780 if (rt->deflatedStringCacheLock) {
2781 JS_DESTROY_LOCK(rt->deflatedStringCacheLock);
2782 rt->deflatedStringCacheLock = NULL;
2783 }
2784 #endif
2785 }
2786
2787 JSObject *
2788 js_InitStringClass(JSContext *cx, JSObject *obj)
2789 {
2790 JSObject *proto;
2791
2792 /* Define the escape, unescape functions in the global object. */
2793 if (!JS_DefineFunctions(cx, obj, string_functions))
2794 return NULL;
2795
2796 proto = JS_InitClass(cx, obj, NULL, &js_StringClass, String, 1,
2797 string_props, string_methods,
2798 NULL, string_static_methods);
2799 if (!proto)
2800 return NULL;
2801 STOBJ_SET_SLOT(proto, JSSLOT_PRIVATE,
2802 STRING_TO_JSVAL(cx->runtime->emptyString));
2803 return proto;
2804 }
2805
2806 JSString *
2807 js_NewString(JSContext *cx, jschar *chars, size_t length)
2808 {
2809 JSString *str;
2810
2811 if (length > JSSTRING_LENGTH_MASK) {
2812 js_ReportAllocationOverflow(cx);
2813 return NULL;
2814 }
2815
2816 str = (JSString *) js_NewGCThing(cx, GCX_STRING, sizeof(JSString));
2817 if (!str)
2818 return NULL;
2819 JSFLATSTR_INIT(str, chars, length);
2820 #ifdef DEBUG
2821 {
2822 JSRuntime *rt = cx->runtime;
2823 JS_RUNTIME_METER(rt, liveStrings);
2824 JS_RUNTIME_METER(rt, totalStrings);
2825 JS_LOCK_RUNTIME_VOID(rt,
2826 (rt->lengthSum += (double)length,
2827 rt->lengthSquaredSum += (double)length * (double)length));
2828 }
2829 #endif
2830 return str;
2831 }
2832
2833 JSString *
2834 js_NewDependentString(JSContext *cx, JSString *base, size_t start,
2835 size_t length)
2836 {
2837 JSString *ds;
2838
2839 if (length == 0)
2840 return cx->runtime->emptyString;
2841
2842 if (start == 0 && length == JSSTRING_LENGTH(base))
2843 return base;
2844
2845 if (start > JSSTRDEP_START_MASK ||
2846 (start != 0 && length > JSSTRDEP_LENGTH_MASK)) {
2847 return js_NewStringCopyN(cx, JSSTRING_CHARS(base) + start, length);
2848 }
2849
2850 ds = (JSString *)js_NewGCThing(cx, GCX_STRING, sizeof(JSString));
2851 if (!ds)
2852 return NULL;
2853 if (start == 0)
2854 JSPREFIX_INIT(ds, base, length);
2855 else
2856 JSSTRDEP_INIT(ds, base, start, length);
2857 #ifdef DEBUG
2858 {
2859 JSRuntime *rt = cx->runtime;
2860 JS_RUNTIME_METER(rt, liveDependentStrings);
2861 JS_RUNTIME_METER(rt, totalDependentStrings);
2862 JS_RUNTIME_METER(rt, liveStrings);
2863 JS_RUNTIME_METER(rt, totalStrings);
2864 JS_LOCK_RUNTIME_VOID(rt,
2865 (rt->strdepLengthSum += (double)length,
2866 rt->strdepLengthSquaredSum += (double)length * (double)length));
2867 JS_LOCK_RUNTIME_VOID(rt,
2868 (rt->lengthSum += (double)length,
2869 rt->lengthSquaredSum += (double)length * (double)length));
2870 }
2871 #endif
2872 return ds;
2873 }
2874
2875 #ifdef DEBUG
2876 #include <math.h>
2877
2878 void printJSStringStats(JSRuntime *rt)
2879 {
2880 double mean, sigma;
2881
2882 mean = JS_MeanAndStdDev(rt->totalStrings, rt->lengthSum,
2883 rt->lengthSquaredSum, &sigma);
2884
2885 fprintf(stderr, "%lu total strings, mean length %g (sigma %g)\n",
2886 (unsigned long)rt->totalStrings, mean, sigma);
2887
2888 mean = JS_MeanAndStdDev(rt->totalDependentStrings, rt->strdepLengthSum,
2889 rt->strdepLengthSquaredSum, &sigma);
2890
2891 fprintf(stderr, "%lu total dependent strings, mean length %g (sigma %g)\n",
2892 (unsigned long)rt->totalDependentStrings, mean, sigma);
2893 }
2894 #endif
2895
2896 JSString *
2897 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n)
2898 {
2899 jschar *news;
2900 JSString *str;
2901
2902 news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
2903 if (!news)
2904 return NULL;
2905 js_strncpy(news, s, n);
2906 news[n] = 0;
2907 str = js_NewString(cx, news, n);
2908 if (!str)
2909 JS_free(cx, news);
2910 return str;
2911 }
2912
2913 JSString *
2914 js_NewStringCopyZ(JSContext *cx, const jschar *s)
2915 {
2916 size_t n, m;
2917 jschar *news;
2918 JSString *str;
2919
2920 n = js_strlen(s);
2921 m = (n + 1) * sizeof(jschar);
2922 news = (jschar *) JS_malloc(cx, m);
2923 if (!news)
2924 return NULL;
2925 memcpy(news, s, m);
2926 str = js_NewString(cx, news, n);
2927 if (!str)
2928 JS_free(cx, news);
2929 return str;
2930 }
2931
2932 void
2933 js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str)
2934 {
2935 JSHashNumber hash;
2936 JSHashEntry *he, **hep;
2937
2938 hash = js_hash_string_pointer(str);
2939 JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
2940 hep = JS_HashTableRawLookup(rt->deflatedStringCache, hash, str);
2941 he = *hep;
2942 if (he) {
2943 #ifdef DEBUG
2944 rt->deflatedStringCacheBytes -= JSSTRING_LENGTH(str);
2945 #endif
2946 free(he->value);
2947 JS_HashTableRawRemove(rt->deflatedStringCache, hep, he);
2948 }
2949 JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
2950 }
2951
2952 static JSStringFinalizeOp str_finalizers[GCX_NTYPES - GCX_EXTERNAL_STRING] = {
2953 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
2954 };
2955
2956 intN
2957 js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
2958 JSStringFinalizeOp newop)
2959 {
2960 uintN i;
2961
2962 for (i = 0; i != JS_ARRAY_LENGTH(str_finalizers); i++) {
2963 if (str_finalizers[i] == oldop) {
2964 str_finalizers[i] = newop;
2965 return (intN) i;
2966 }
2967 }
2968 return -1;
2969 }
2970
2971 /*
2972 * cx is NULL when we are called from js_FinishAtomState to force the
2973 * finalization of the permanently interned strings.
2974 */
2975 void
2976 js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx)
2977 {
2978 jschar *chars;
2979 JSBool valid;
2980 JSStringFinalizeOp finalizer;
2981
2982 JS_RUNTIME_UNMETER(rt, liveStrings);
2983 if (JSSTRING_IS_DEPENDENT(str)) {
2984 /* A dependent string can not be external and must be valid. */
2985 JS_ASSERT(type < 0);
2986 JS_ASSERT(JSSTRDEP_BASE(str));
2987 JS_RUNTIME_UNMETER(rt, liveDependentStrings);
2988 valid = JS_TRUE;
2989 } else {
2990 /* A stillborn string has null chars, so is not valid. */
2991 chars = JSFLATSTR_CHARS(str);
2992 valid = (chars != NULL);
2993 if (valid) {
2994 if (IN_UNIT_STRING_SPACE_RT(rt, chars)) {
2995 JS_ASSERT(rt->unitStrings[*chars] == str);
2996 JS_ASSERT(type < 0);
2997 rt->unitStrings[*chars] = NULL;
2998 } else if (type < 0) {
2999 free(chars);
3000 } else {
3001 JS_ASSERT((uintN) type < JS_ARRAY_LENGTH(str_finalizers));
3002 finalizer = str_finalizers[type];
3003 if (finalizer) {
3004 /*
3005 * Assume that the finalizer for the permanently interned
3006 * string knows how to deal with null context.
3007 */
3008 finalizer(cx, str);
3009 }
3010 }
3011 }
3012 }
3013 if (valid)
3014 js_PurgeDeflatedStringCache(rt, str);
3015 }
3016
3017 JS_FRIEND_API(const char *)
3018 js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun)
3019 {
3020 JSString *str;
3021
3022 str = v2sfun(cx, v);
3023 if (!str)
3024 return NULL;
3025 str = js_QuoteString(cx, str, 0);
3026 if (!str)
3027 return NULL;
3028 return js_GetStringBytes(cx, str);
3029 }
3030
3031 JS_FRIEND_API(JSString *)
3032 js_ValueToString(JSContext *cx, jsval v)
3033 {
3034 JSObject *obj;
3035 JSString *str;
3036
3037 if (JSVAL_IS_OBJECT(v)) {
3038 obj = JSVAL_TO_OBJECT(v);
3039 if (!obj)
3040 return ATOM_TO_STRING(cx->runtime->atomState.nullAtom);
3041 if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v))
3042 return NULL;
3043 }
3044 if (JSVAL_IS_STRING(v)) {
3045 str = JSVAL_TO_STRING(v);
3046 } else if (JSVAL_IS_INT(v)) {
3047 str = js_NumberToString(cx, JSVAL_TO_INT(v));
3048 } else if (JSVAL_IS_DOUBLE(v)) {
3049 str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(v));
3050 } else if (JSVAL_IS_BOOLEAN(v)) {
3051 str = js_BooleanToString(cx, JSVAL_TO_BOOLEAN(v));
3052 } else {
3053 str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
3054 }
3055 return str;
3056 }
3057
3058 JS_FRIEND_API(JSString *)
3059 js_ValueToSource(JSContext *cx, jsval v)
3060 {
3061 JSTempValueRooter tvr;
3062 JSString *str;
3063
3064 if (JSVAL_IS_VOID(v))
3065 return ATOM_TO_STRING(cx->runtime->atomState.void0Atom);
3066 if (JSVAL_IS_STRING(v))
3067 return js_QuoteString(cx, JSVAL_TO_STRING(v), '"');
3068 if (JSVAL_IS_PRIMITIVE(v)) {
3069 /* Special case to preserve negative zero, _contra_ toString. */
3070 if (JSVAL_IS_DOUBLE(v) && JSDOUBLE_IS_NEGZERO(*JSVAL_TO_DOUBLE(v))) {
3071 /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
3072 static const jschar js_negzero_ucNstr[] = {'-', '0'};
3073
3074 return js_NewStringCopyN(cx, js_negzero_ucNstr, 2);
3075 }
3076 return js_ValueToString(cx, v);
3077 }
3078
3079 JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
3080 if (!js_TryMethod(cx, JSVAL_TO_OBJECT(v),
3081 cx->runtime->atomState.toSourceAtom,
3082 0, NULL, &tvr.u.value)) {
3083 str = NULL;
3084 } else {
3085 str = js_ValueToString(cx, tvr.u.value);
3086 }
3087 JS_POP_TEMP_ROOT(cx, &tvr);
3088 return str;
3089 }
3090
3091 /*
3092 * str is not necessarily a GC thing here.
3093 */
3094 uint32
3095 js_HashString(JSString *str)
3096 {
3097 const jschar *s;
3098 size_t n;
3099 uint32 h;
3100
3101 JSSTRING_CHARS_AND_LENGTH(str, s, n);
3102 for (h = 0; n; s++, n--)
3103 h = JS_ROTATE_LEFT32(h, 4) ^ *s;
3104 return h;
3105 }
3106
3107 /*
3108 * str is not necessarily a GC thing here.
3109 */
3110 JSBool JS_FASTCALL
3111 js_EqualStrings(JSString *str1, JSString *str2)
3112 {
3113 size_t n;
3114 const jschar *s1, *s2;
3115
3116 JS_ASSERT(str1);
3117 JS_ASSERT(str2);
3118
3119 /* Fast case: pointer equality could be a quick win. */
3120 if (str1 == str2)
3121 return JS_TRUE;
3122
3123 n = JSSTRING_LENGTH(str1);
3124 if (n != JSSTRING_LENGTH(str2))
3125 return JS_FALSE;
3126
3127 if (n == 0)
3128 return JS_TRUE;