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

Annotation of /trunk/js/jsstr.cpp

Parent Directory Parent Directory | Revision Log Revision Log


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

1 siliconforks 332 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 siliconforks 399 * vim: set ts=8 sw=4 et tw=99:
3 siliconforks 332 *
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 siliconforks 399 #include "jsbuiltins.h"
63 siliconforks 332 #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 siliconforks 399
737     static JSString *
738     SubstringTail(JSContext *cx, JSString *str, jsdouble length, jsdouble begin, jsdouble end)
739 siliconforks 332 {
740 siliconforks 399 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 siliconforks 332 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 siliconforks 399 str = SubstringTail(cx, str, length, begin, end);
783 siliconforks 332 if (!str)
784     return JS_FALSE;
785     }
786     *vp = STRING_TO_JSVAL(str);
787     return JS_TRUE;
788     }
789    
790 siliconforks 399 #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 siliconforks 332 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 siliconforks 399 static JSBool
842     str_toLowerCase(JSContext *cx, uintN argc, jsval *vp)
843 siliconforks 332 {
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 siliconforks 399 return str_toLowerCase(cx, 0, vp);
868 siliconforks 332 }
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 siliconforks 399 static JSBool
892     str_toUpperCase(JSContext *cx, uintN argc, jsval *vp)
893 siliconforks 332 {
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 siliconforks 399 return str_toUpperCase(cx, 0, vp);
918 siliconforks 332 }
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 siliconforks 399 static JSBool
942     str_charAt(JSContext *cx, uintN argc, jsval *vp)
943 siliconforks 332 {
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 siliconforks 399 static JSBool
986     str_charCodeAt(JSContext *cx, uintN argc, jsval *vp)
987 siliconforks 332 {
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 siliconforks 399 #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 siliconforks 332 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 siliconforks 399 * 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 siliconforks 332 * 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 siliconforks 399
1417     JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING);
1418 siliconforks 332 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 siliconforks 399 static JSBool
1441     str_match(JSContext *cx, uintN argc, jsval *vp)
1442 siliconforks 332 {
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 siliconforks 399 #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 siliconforks 332 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 siliconforks 399 * input, which is rooted otherwise via vp[1] in str_replace.
1576 siliconforks 332 */
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 siliconforks 399 static JSBool
1756     str_replace(JSContext *cx, uintN argc, jsval *vp)
1757 siliconforks 332 {
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 siliconforks 399 #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 siliconforks 332 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 siliconforks 399 * Subroutine used by str_split to find the next split point in str, starting
1903 siliconforks 332 * 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 siliconforks 399 * limit argument (see str_split).
1928 siliconforks 332 */
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 siliconforks 399 static JSBool
2014     str_split(JSContext *cx, uintN argc, jsval *vp)
2015 siliconforks 332 {
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 siliconforks 399 #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 siliconforks 332 #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 siliconforks 399 static JSBool
2180     str_concat(JSContext *cx, uintN argc, jsval *vp)
2181 siliconforks 332 {
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 siliconforks 399 #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 siliconforks 332 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 siliconforks 399 #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