/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the * provisions of the GPL are applicable instead of those above. * If you wish to allow use of your version of this file only * under the terms of the GPL and not to allow others to use your * version of this file under the NPL, indicate your decision by * deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete * the provisions above, a recipient may use your version of this * file under either the NPL or the GPL. */ /* * JS regular expressions, after Perl. */ #include "jsstddef.h" #include #include #include "jstypes.h" #include "jsarena.h" /* Added by JSIFY */ #include "jsutil.h" /* Added by JSIFY */ #include "jsapi.h" #include "jsarray.h" #include "jsatom.h" #include "jscntxt.h" #include "jsconfig.h" #include "jsfun.h" #include "jsgc.h" #include "jsinterp.h" #include "jslock.h" #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jsregexp.h" #include "jsscan.h" #include "jsstr.h" #ifdef XP_MAC #include #endif #if JS_HAS_REGEXPS typedef enum REOp { REOP_EMPTY = 0, /* match rest of input against rest of r.e. */ REOP_ALT = 1, /* alternative subexpressions in kid and next */ REOP_BOL = 2, /* beginning of input (or line if multiline) */ REOP_EOL = 3, /* end of input (or line if multiline) */ REOP_WBDRY = 4, /* match "" at word boundary */ REOP_WNONBDRY = 5, /* match "" at word non-boundary */ REOP_QUANT = 6, /* quantified atom: atom{1,2} */ REOP_STAR = 7, /* zero or more occurrences of kid */ REOP_PLUS = 8, /* one or more occurrences of kid */ REOP_OPT = 9, /* optional subexpression in kid */ REOP_LPAREN = 10, /* left paren bytecode: kid is u.num'th sub-regexp */ REOP_RPAREN = 11, /* right paren bytecode */ REOP_DOT = 12, /* stands for any character */ REOP_CCLASS = 13, /* character class: [a-f] */ REOP_DIGIT = 14, /* match a digit char: [0-9] */ REOP_NONDIGIT = 15, /* match a non-digit char: [^0-9] */ REOP_ALNUM = 16, /* match an alphanumeric char: [0-9a-z_A-Z] */ REOP_NONALNUM = 17, /* match a non-alphanumeric char: [^0-9a-z_A-Z] */ REOP_SPACE = 18, /* match a whitespace char */ REOP_NONSPACE = 19, /* match a non-whitespace char */ REOP_BACKREF = 20, /* back-reference (e.g., \1) to a parenthetical */ REOP_FLAT = 21, /* match a flat string */ REOP_FLAT1 = 22, /* match a single char */ REOP_JUMP = 23, /* for deoptimized closure loops */ REOP_DOTSTAR = 24, /* optimize .* to use a single opcode */ REOP_ANCHOR = 25, /* like .* but skips left context to unanchored r.e. */ REOP_EOLONLY = 26, /* $ not preceded by any pattern */ REOP_UCFLAT = 27, /* flat Unicode string; len immediate counts chars */ REOP_UCFLAT1 = 28, /* single Unicode char */ REOP_UCCLASS = 29, /* Unicode character class, vector of chars to match */ REOP_NUCCLASS = 30, /* negated Unicode character class */ REOP_BACKREFi = 31, /* case-independent REOP_BACKREF */ REOP_FLATi = 32, /* case-independent REOP_FLAT */ REOP_FLAT1i = 33, /* case-independent REOP_FLAT1 */ REOP_UCFLATi = 34, /* case-independent REOP_UCFLAT */ REOP_UCFLAT1i = 35, /* case-independent REOP_UCFLAT1 */ REOP_ANCHOR1 = 36, /* first-char discriminating REOP_ANCHOR */ REOP_NCCLASS = 37, /* negated 8-bit character class */ REOP_DOTSTARMIN = 38, /* ungreedy version of REOP_DOTSTAR */ REOP_LPARENNON = 39, /* non-capturing version of REOP_LPAREN */ REOP_RPARENNON = 40, /* non-capturing version of REOP_RPAREN */ REOP_ASSERT = 41, /* zero width positive lookahead assertion */ REOP_ASSERT_NOT = 42, /* zero width negative lookahead assertion */ REOP_END } REOp; #define REOP_FLATLEN_MAX 255 /* maximum length of FLAT string */ struct RENode { uint8 op; /* packed r.e. op bytecode */ uint8 flags; /* flags, see below */ #ifdef DEBUG uint16 offset; /* bytecode offset */ #endif RENode *next; /* next in concatenation order */ void *kid; /* first operand */ union { void *kid2; /* second operand */ jsint num; /* could be a number */ jschar chr; /* or a character */ struct { /* or a quantifier range */ uint16 min; uint16 max; } range; struct { /* or a Unicode character class */ uint16 kidlen; /* length of string at kid, in jschars */ uint16 bmsize; /* bitmap size, based on max char code */ uint8 *bitmap; } ucclass; } u; }; #define REOP(ren) ((REOp)(ren)->op) #define RENODE_ANCHORED 0x01 /* anchored at the front */ #define RENODE_SINGLE 0x02 /* matches a single char */ #define RENODE_NONEMPTY 0x04 /* does not match empty string */ #define RENODE_ISNEXT 0x08 /* ren is next after at least one node */ #define RENODE_GOODNEXT 0x10 /* ren->next is a tree-like edge in the graph */ #define RENODE_ISJOIN 0x20 /* ren is a join point in the graph */ #define RENODE_REALLOK 0x40 /* REOP_FLAT owns tempPool space to realloc */ #define RENODE_MINIMAL 0x80 /* un-greedy matching for ? * + {} */ typedef struct CompilerState { JSContext *context; JSTokenStream *tokenStream; /* For reporting errors */ const jschar *cpbegin; const jschar *cpend; const jschar *cp; uintN flags; uintN parenCount; size_t progLength; } CompilerState; #ifdef DEBUG #include static char *reopname[] = { "empty", "alt", "bol", "eol", "wbdry", "wnonbdry", "quant", "star", "plus", "opt", "lparen", "rparen", "dot", "cclass", "digit", "nondigit", "alnum", "nonalnum", "space", "nonspace", "backref", "flat", "flat1", "jump", "dotstar", "anchor", "eolonly", "ucflat", "ucflat1", "ucclass", "nucclass", "backrefi", "flati", "flat1i", "ucflati", "ucflat1i", "anchor1", "ncclass", "dotstar_min", "lparen_non", "rparen_non", "assert", "assert_not", "end" }; static void PrintChar(jschar c) { if (c >> 8) printf("\\u%04X", c); else #if !defined XP_PC || !defined _MSC_VER || _MSC_VER > 800 putchar((char)c); #else /* XXX is there a better way with MSVC1.52? */ printf("%c", c); #endif } static int gOffset = 0; static JSBool DumpRegExp(JSContext *cx, RENode *ren) { static int level; JSBool ok; int i, len; jschar *cp; char *cstr; if (level == 0) printf("level offset flags description\n"); level++; ok = JS_TRUE; do { printf("%5d %6d %6d %c%c%c%c%c%c%c %s", level, (int)ren->offset, (ren->next) ? (int)ren->next->offset : -1, (ren->flags & RENODE_ANCHORED) ? 'A' : '-', (ren->flags & RENODE_SINGLE) ? 'S' : '-', (ren->flags & RENODE_NONEMPTY) ? 'F' : '-', /* F for full */ (ren->flags & RENODE_ISNEXT) ? 'N' : '-', /* N for next */ (ren->flags & RENODE_GOODNEXT) ? 'G' : '-', (ren->flags & RENODE_ISJOIN) ? 'J' : '-', (ren->flags & RENODE_MINIMAL) ? 'M' : '-', reopname[ren->op]); switch (REOP(ren)) { case REOP_ALT: printf(" %d\n", ren->next->offset); ok = DumpRegExp(cx, (RENode*) ren->kid); if (!ok) goto out; break; case REOP_STAR: case REOP_PLUS: case REOP_OPT: case REOP_ANCHOR1: case REOP_ASSERT: case REOP_ASSERT_NOT: printf("\n"); ok = DumpRegExp(cx, (RENode*) ren->kid); if (!ok) goto out; break; case REOP_QUANT: printf(" next %d min %d max %d\n", ren->next->offset, ren->u.range.min, ren->u.range.max); ok = DumpRegExp(cx, (RENode*) ren->kid); if (!ok) goto out; break; case REOP_LPAREN: printf(" num %d\n", (int)ren->u.num); ok = DumpRegExp(cx, (RENode*) ren->kid); if (!ok) goto out; break; case REOP_LPARENNON: printf("\n"); ok = DumpRegExp(cx, (RENode*) ren->kid); if (!ok) goto out; break; case REOP_RPAREN: printf(" num %d\n", (int)ren->u.num); break; case REOP_CCLASS: len = (jschar *)ren->u.kid2 - (jschar *)ren->kid; cstr = js_DeflateString(cx, (jschar *)ren->kid, len); if (!cstr) { ok = JS_FALSE; goto out; } printf(" [%s]\n", cstr); JS_free(cx, cstr); break; case REOP_BACKREF: printf(" num %d\n", (int)ren->u.num); break; case REOP_FLAT: len = (jschar *)ren->u.kid2 - (jschar *)ren->kid; cstr = js_DeflateString(cx, (jschar *)ren->kid, len); if (!cstr) { ok = JS_FALSE; goto out; } printf(" %s (%d)\n", cstr, len); JS_free(cx, cstr); break; case REOP_FLAT1: printf(" %c ('\\%o')\n", (char)ren->u.chr, ren->u.chr); break; case REOP_JUMP: printf(" %d\n", ren->next->offset); break; case REOP_UCFLAT: cp = (jschar*) ren->kid; len = (jschar *)ren->u.kid2 - cp; for (i = 0; i < len; i++) PrintChar(cp[i]); printf("\n"); break; case REOP_UCFLAT1: PrintChar(ren->u.chr); printf("\n"); break; case REOP_UCCLASS: cp = (jschar*) ren->kid; len = ren->u.ucclass.kidlen; printf(" ["); for (i = 0; i < len; i++) PrintChar(cp[i]); printf("]\n"); break; default: printf("\n"); break; } if (!(ren->flags & RENODE_GOODNEXT)) break; } while ((ren = ren->next) != NULL); out: level--; return ok; } #endif /* DEBUG */ static RENode * NewRENode(CompilerState *state, REOp op, void *kid) { JSContext *cx; RENode *ren; cx = state->context; ren = (RENode*) JS_malloc(cx, sizeof *ren); if (!ren) { JS_ReportOutOfMemory(cx); return NULL; } ren->op = (uint8)op; ren->flags = 0; #ifdef DEBUG ren->offset = gOffset++; #endif ren->next = NULL; ren->kid = kid; return ren; } static JSBool FixNext(CompilerState *state, RENode *ren1, RENode *ren2, RENode *oldnext) { JSBool goodnext; RENode *next, *kid, *ren; goodnext = ren2 && !(ren2->flags & RENODE_ISNEXT); /* * Find the final node in a list of alternatives, or concatenations, or * even a concatenation of alternatives followed by non-alternatives (e.g. * ((x|y)z)w where ((x|y)z) is ren1 and w is ren2). */ for (; (next = ren1->next) != NULL && next != oldnext; ren1 = next) { if (REOP(ren1) == REOP_ALT) { /* Find the end of this alternative's operand list. */ kid = (RENode*) ren1->kid; if (REOP(kid) == REOP_JUMP) continue; for (ren = kid; ren->next; ren = ren->next) JS_ASSERT(REOP(ren) != REOP_ALT); /* Append a jump node to all but the last alternative. */ ren->next = NewRENode(state, REOP_JUMP, NULL); if (!ren->next) return JS_FALSE; ren->next->flags |= RENODE_ISNEXT; ren->flags |= RENODE_GOODNEXT; /* Recur to fix all descendent nested alternatives. */ if (!FixNext(state, kid, ren2, oldnext)) return JS_FALSE; } } /* * Now ren1 points to the last alternative, or to the final node on a * concatenation list. Set its next link to ren2, flagging a join point * if appropriate. */ if (ren2) { if (!(ren2->flags & RENODE_ISNEXT)) ren2->flags |= RENODE_ISNEXT; else ren2->flags |= RENODE_ISJOIN; } ren1->next = ren2; if (goodnext) ren1->flags |= RENODE_GOODNEXT; /* * The following ops have a kid subtree through which to recur. Here is * where we fix the next links under the final ALT node's kid. */ switch (REOP(ren1)) { case REOP_ALT: case REOP_QUANT: case REOP_STAR: case REOP_PLUS: case REOP_OPT: case REOP_LPAREN: case REOP_LPARENNON: case REOP_ASSERT: case REOP_ASSERT_NOT: if (!FixNext(state, (RENode*) ren1->kid, ren2, oldnext)) return JS_FALSE; break; default:; } return JS_TRUE; } static JSBool SetNext(CompilerState *state, RENode *ren1, RENode *ren2) { return FixNext(state, ren1, ren2, NULL); } /* * Parser forward declarations. */ typedef RENode *REParser(CompilerState *state); static REParser ParseRegExp; static REParser ParseAltern; static REParser ParseItem; static REParser ParseQuantAtom; static REParser ParseAtom; /* * Top-down regular expression grammar, based closely on Perl4. * * regexp: altern A regular expression is one or more * altern '|' regexp alternatives separated by vertical bar. */ static RENode * ParseRegExp(CompilerState *state) { RENode *ren, *kid, *ren1, *ren2; const jschar *cp; ren = ParseAltern(state); if (!ren) return NULL; cp = state->cp; if ((cp < state->cpend) && (*cp == '|')) { kid = ren; ren = NewRENode(state, REOP_ALT, kid); if (!ren) return NULL; ren->flags = kid->flags & (RENODE_ANCHORED | RENODE_NONEMPTY); ren1 = ren; do { /* (balance: */ state->cp = ++cp; if (*cp == '|' || *cp == ')') { kid = NewRENode(state, REOP_EMPTY, NULL); } else { kid = ParseAltern(state); cp = state->cp; } if (!kid) return NULL; ren2 = NewRENode(state, REOP_ALT, kid); if (!ren2) return NULL; ren1->next = ren2; ren1->flags |= RENODE_GOODNEXT; ren2->flags = (kid->flags & (RENODE_ANCHORED | RENODE_NONEMPTY)) | RENODE_ISNEXT; ren1 = ren2; } while ((cp < state->cpend) && (*cp == '|')); } return ren; } /* * altern: item An alternative is one or more items, * item altern concatenated together. */ static RENode * ParseAltern(CompilerState *state) { RENode *ren, *ren1, *ren2; uintN flags; const jschar *cp; jschar c; ren = ren1 = ParseItem(state); if (!ren) return NULL; flags = 0; cp = state->cp; /* (balance: */ while ((cp < state->cpend) && (c = *cp) != '|' && c != ')') { ren2 = ParseItem(state); if (!ren2) return NULL; if (!SetNext(state, ren1, ren2)) return NULL; flags |= ren2->flags; ren1 = ren2; cp = state->cp; } /* * Propagate NONEMPTY to the front of a concatenation list, so that the * first alternative in (^a|b) is considered non-empty. The first node * in a list may match the empty string (as ^ does), but if the list is * non-empty, then the first node's NONEMPTY flag must be set. */ ren->flags |= flags & RENODE_NONEMPTY; return ren; } /* * item: assertion An item is either an assertion or * quantatom a quantified atom. * * assertion: '^' Assertions match beginning of string * (or line if the class static property * RegExp.multiline is true). * '$' End of string (or line if the class * static property RegExp.multiline is * true). * '\b' Word boundary (between \w and \W). * '\B' Word non-boundary. */ static RENode * ParseItem(CompilerState *state) { const jschar *cp; RENode *ren; REOp op; cp = state->cp; if (cp < state->cpend) switch (*cp) { case '^': state->cp = cp + 1; ren = NewRENode(state, REOP_BOL, NULL); if (ren) ren->flags |= RENODE_ANCHORED; return ren; case '$': state->cp = cp + 1; return NewRENode(state, (cp == state->cpbegin || ((cp[-1] == '(' || cp[-1] == '|') && /*balance)*/ (cp - 1 == state->cpbegin || cp[-2] != '\\'))) ? REOP_EOLONLY : REOP_EOL, NULL); case '\\': switch (*++cp) { case 'b': op = REOP_WBDRY; break; case 'B': op = REOP_WNONBDRY; break; default: return ParseQuantAtom(state); } /* * Word boundaries and non-boundaries are flagged as non-empty so they * will be prefixed by an anchoring node. */ state->cp = cp + 1; ren = NewRENode(state, op, NULL); if (ren) ren->flags |= RENODE_NONEMPTY; return ren; default:; } return ParseQuantAtom(state); } /* * quantatom: atom An unquantified atom. * quantatom '{' n ',' m '}' * Atom must occur between n and m times. * quantatom '{' n ',' '}' Atom must occur at least n times. * quantatom '{' n '}' Atom must occur exactly n times. * quantatom '*' Zero or more times (same as {0,}). * quantatom '+' One or more times (same as {1,}). * quantatom '?' Zero or one time (same as {0,1}). * * any of which can be optionally followed by '?' for ungreedy */ static RENode * ParseQuantAtom(CompilerState *state) { RENode *ren, *ren2; const jschar *cp, *up; jschar c; uint32 min, max; ren = ParseAtom(state); if (!ren) return NULL; cp = state->cp; loop: if (cp < state->cpend) switch (*cp) { case '{': c = *++cp; if (!JS7_ISDEC(c)) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_BAD_QUANTIFIER, state->cp); return NULL; } min = (uint32)JS7_UNDEC(c); for (c = *++cp; JS7_ISDEC(c); c = *++cp) { min = 10 * min + (uint32)JS7_UNDEC(c); if (min >> 16) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_MIN_TOO_BIG, state->cp); return NULL; } } if (*cp == ',') { up = ++cp; if (JS7_ISDEC(*cp)) { max = (uint32)JS7_UNDEC(*cp); for (c = *++cp; JS7_ISDEC(c); c = *++cp) { max = 10 * max + (uint32)JS7_UNDEC(c); if (max >> 16) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_MAX_TOO_BIG, up); return NULL; } } if (max == 0) goto zero_quant; if (min > max) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_OUT_OF_ORDER, up); return NULL; } } else { /* 0 means no upper bound. */ max = 0; } } else { /* Exactly n times. */ if (min == 0) { zero_quant: js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_ZERO_QUANTIFIER, state->cp); return NULL; } max = min; } if (*cp != '}') { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_UNTERM_QUANTIFIER, state->cp); return NULL; } cp++; ren2 = NewRENode(state, REOP_QUANT, ren); if (!ren2) return NULL; if (min > 0 && (ren->flags & RENODE_NONEMPTY)) ren2->flags |= RENODE_NONEMPTY; ren2->u.range.min = (uint16)min; ren2->u.range.max = (uint16)max; ren = ren2; goto parseMinimalFlag; case '*': cp++; ren = NewRENode(state, REOP_STAR, ren); parseMinimalFlag : if (*cp == '?') { ren->flags |= RENODE_MINIMAL; cp++; } goto loop; case '+': cp++; ren2 = NewRENode(state, REOP_PLUS, ren); if (!ren2) return NULL; if (ren->flags & RENODE_NONEMPTY) ren2->flags |= RENODE_NONEMPTY; ren = ren2; goto parseMinimalFlag; case '?': cp++; ren = NewRENode(state, REOP_OPT, ren); goto parseMinimalFlag; } state->cp = cp; return ren; } /* * atom: '(' regexp ')' A parenthesized regexp (what matched * can be addressed using a backreference, * see '\' n below). * '.' Matches any char except '\n'. * '[' classlist ']' A character class. * '[' '^' classlist ']' A negated character class. * '\f' Form Feed. * '\n' Newline (Line Feed). * '\r' Carriage Return. * '\t' Horizontal Tab. * '\v' Vertical Tab. * '\d' A digit (same as [0-9]). * '\D' A non-digit. * '\w' A word character, [0-9a-z_A-Z]. * '\W' A non-word character. * '\s' A whitespace character, [ \b\f\n\r\t\v]. * '\S' A non-whitespace character. * '\' n A backreference to the nth (n decimal * and positive) parenthesized expression. * '\' octal An octal escape sequence (octal must be * two or three digits long, unless it is * 0 for the null character). * '\x' hex A hex escape (hex must be two digits). * '\c' ctrl A control character, ctrl is a letter. * '\' literalatomchar Any character except one of the above * that follow '\' in an atom. * otheratomchar Any character not first among the other * atom right-hand sides. */ static jschar metachars[] = { '|', '^', '$', '{', '*', '+', '?', '(', ')', '.', '[', '\\', '}', 0 }; static jschar closurechars[] = { '{', '*', '+', '?', 0 /* balance} */ }; static RENode * ParseAtom(CompilerState *state) { const jschar *cp, *ocp; uintN tmp, num, len; RENode *ren, *ren2; jschar c; REOp op; cp = ocp = state->cp; /* handle /|a/ by returning an empty node for the leftside */ if ((cp == state->cpend) || (*cp == '|')) return NewRENode(state, REOP_EMPTY, NULL); ren = NULL; /* suppress warning */ switch (*cp) { case '(': num = -1; /* suppress warning */ op = REOP_END; if (cp[1] == '?') { switch (cp[2]) { case ':' : op = REOP_LPARENNON; break; case '=' : op = REOP_ASSERT; break; case '!' : op = REOP_ASSERT_NOT; break; } } if (op == REOP_END) { num = state->parenCount++; /* \1 is numbered 0, etc. */ op = REOP_LPAREN; cp++; } else cp += 3; state->cp = cp; /* Handle empty paren */ if (*cp == ')') { ren2 = NewRENode(state, REOP_EMPTY, NULL); } else { ren2 = ParseRegExp(state); if (!ren2) return NULL; cp = state->cp; if (*cp != ')') { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_MISSING_PAREN, ocp); return NULL; } } cp++; ren = NewRENode(state, op, ren2); if (!ren) return NULL; ren->flags = ren2->flags & (RENODE_ANCHORED | RENODE_NONEMPTY); ren->u.num = num; if ((op == REOP_LPAREN) || (op == REOP_LPARENNON)) { /* Assume RPAREN ops immediately succeed LPAREN ops */ ren2 = NewRENode(state, (REOp)(op + 1), NULL); if (!ren2 || !SetNext(state, ren, ren2)) return NULL; ren2->u.num = num; } break; case '.': cp++; op = REOP_DOT; if (*cp == '*') { cp++; op = REOP_DOTSTAR; if (*cp == '?') { cp++; op = REOP_DOTSTARMIN; } } ren = NewRENode(state, op, NULL); if (ren && op == REOP_DOT) ren->flags = RENODE_SINGLE | RENODE_NONEMPTY; break; case '[': ++cp; ren = NewRENode(state, REOP_CCLASS, (void *)cp); if (!ren) return NULL; do { if (cp == state->cpend) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_UNTERM_CLASS, ocp); return NULL; } if ((c = *cp++) == ']') break; if (c == '\\' && (cp+1 != state->cpend)) cp++; } while (JS_TRUE); ren->u.kid2 = (void *)(cp - 1); ren->u.ucclass.bitmap = NULL; /* Since we rule out [] and [^], we can set the non-empty flag. */ ren->flags = RENODE_SINGLE | RENODE_NONEMPTY; break; case '\\': c = *++cp; if (cp == state->cpend) { js_ReportCompileErrorNumber(state->context, state->tokenStream, NULL, JSREPORT_ERROR, JSMSG_TRAILING_SLASH); return NULL; } switch (c) { case 'f': case 'n': case 'r': case 't': case 'v': ren = NewRENode(state, REOP_FLAT1, NULL); c = js_strchr(js_EscapeMap, c)[-1]; break; case 'd': ren = NewRENode(state, REOP_DIGIT, NULL); break; case 'D': ren = NewRENode(state, REOP_NONDIGIT, NULL); break; case 'w': ren = NewRENode(state, REOP_ALNUM, NULL); break; case 'W': ren = NewRENode(state, REOP_NONALNUM, NULL); break; case 's': ren = NewRENode(state, REOP_SPACE, NULL); break; case 'S': ren = NewRENode(state, REOP_NONSPACE, NULL); break; case '0': if (JS_HAS_STRICT_OPTION(state->context)) c = 0; else { do_octal: num = 0; while ('0' <= (c = *++cp) && c <= '7') { tmp = 8 * num + (uintN)JS7_UNDEC(c); if (tmp > 0377) break; num = tmp; } cp--; ren = NewRENode(state, REOP_FLAT1, NULL); c = (jschar)num; } break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': num = (uintN)JS7_UNDEC(c); for (c = *++cp; JS7_ISDEC(c); c = *++cp) num = 10 * num - (uintN)JS7_UNDEC(c); if (num > 9 && num > state->parenCount && !JS_HAS_STRICT_OPTION(state->context)) { cp = ocp; goto do_octal; } cp--; ren = NewRENode(state, REOP_BACKREF, NULL); if (!ren) return NULL; ren->u.num = num - 1; /* \1 is numbered 0, etc. */ /* Avoid common chr- and flags-setting code after switch. */ ren->flags = RENODE_NONEMPTY; goto bump_cp; case 'x': ocp = cp; c = *++cp; if (JS7_ISHEX(c)) { num = JS7_UNHEX(c); c = *++cp; if (JS7_ISHEX(c)) { num <<= 4; num += JS7_UNHEX(c); } else { if (state->context->version != JSVERSION_DEFAULT && state->context->version <= JSVERSION_1_4) cp--; /* back up so cp points to last hex char */ else goto nothex; /* ECMA 2 insists on pairs */ } } else { nothex: cp = ocp; /* \xZZ is xZZ (Perl does \0ZZ!) */ num = 'x'; } ren = NewRENode(state, REOP_FLAT1, NULL); c = (jschar)num; break; case 'c': c = *++cp; if (!JS7_ISLET(c)) { cp -= 2; ocp = cp; goto do_flat; } c = JS_TOUPPER(c); c = JS_TOCTRL(c); ren = NewRENode(state, REOP_FLAT1, NULL); break; case 'u': if (JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3]) && JS7_ISHEX(cp[4])) { c = (((((JS7_UNHEX(cp[1]) << 4) + JS7_UNHEX(cp[2])) << 4) + JS7_UNHEX(cp[3])) << 4) + JS7_UNHEX(cp[4]); cp += 4; ren = NewRENode(state, REOP_FLAT1, NULL); break; } /* Unlike Perl \xZZ, we take \uZZZ to be literal-u then ZZZ. */ ocp = cp; goto do_flat; default: ocp = cp; goto do_flat; } /* Common chr- and flags-setting code for escape opcodes. */ if (ren) { ren->u.chr = c; ren->flags = RENODE_SINGLE | RENODE_NONEMPTY; } bump_cp: /* Skip to next unparsed char. */ cp++; break; default: do_flat: while ((c = *++cp) && (cp != state->cpend) && !js_strchr(metachars, c)) ; len = (uintN)(cp - ocp); if ((cp != state->cpend) && len > 1 && js_strchr(closurechars, c)) { cp--; len--; } if (len > REOP_FLATLEN_MAX) { len = REOP_FLATLEN_MAX; cp = ocp + len; } ren = NewRENode(state, (len == 1) ? REOP_FLAT1 : REOP_FLAT, (void *)ocp); if (!ren) return NULL; ren->flags = RENODE_NONEMPTY; if (len > 1) { ren->u.kid2 = (void *)cp; } else { ren->flags |= RENODE_SINGLE; ren->u.chr = *ocp; } break; } state->cp = cp; return ren; } JSRegExp * js_NewRegExp(JSContext *cx, JSTokenStream *ts, JSString *str, uintN flags, JSBool flat) { JSRegExp *re; void *mark; CompilerState state; RENode *ren, *ren2, *end; size_t resize; const jschar *cp; size_t len; re = NULL; mark = JS_ARENA_MARK(&cx->tempPool); state.context = cx; state.tokenStream = ts; state.cpbegin = state.cp = JSSTRING_CHARS(str); state.cpend = state.cp + JSSTRING_LENGTH(str); state.flags = flags; state.parenCount = 0; state.progLength = 0; len = JSSTRING_LENGTH(str); if (len != 0 && flat) { cp = state.cpbegin; ren = NULL; do { ren2 = NewRENode(&state, (len == 1) ? REOP_FLAT1 : REOP_FLAT, (void *)cp); if (!ren2) goto out; ren2->flags = RENODE_NONEMPTY; if (len > 1) { if (len > REOP_FLATLEN_MAX) { cp += REOP_FLATLEN_MAX; len -= REOP_FLATLEN_MAX; } else { cp += len; len = 0; } ren2->u.kid2 = (void *)cp; } else { ren2->flags |= RENODE_SINGLE; ren2->u.chr = *cp; len = 0; } if (ren) { if (!SetNext(&state, ren, ren2)) goto out; } else ren = ren2; } while (len > 0); } else ren = ParseRegExp(&state); if (!ren) goto out; end = NewRENode(&state, REOP_END, NULL); if (!end || !SetNext(&state, ren, end)) goto out; #ifdef DEBUG_notme if (!DumpRegExp(cx, ren)) goto out; #endif resize = sizeof *re + state.progLength - 1; re = (JSRegExp *) JS_malloc(cx, JS_ROUNDUP(resize, sizeof(jsword))); if (!re) goto out; re->nrefs = 1; re->parenCount = state.parenCount; re->flags = flags; re->source = str; re->ren = ren; out: JS_ARENA_RELEASE(&cx->tempPool, mark); return re; } JSRegExp * js_NewRegExpOpt(JSContext *cx, JSTokenStream *ts, JSString *str, JSString *opt, JSBool flat) { uintN flags; jschar *s; size_t i, n; flags = 0; if (opt) { s = JSSTRING_CHARS(opt); for (i = 0, n = JSSTRING_LENGTH(opt); i < n; i++) { switch (s[i]) { case 'g': flags |= JSREG_GLOB; break; case 'i': flags |= JSREG_FOLD; break; case 'm': flags |= JSREG_MULTILINE; break; default: { char charBuf[2] = " "; charBuf[0] = (char)s[i]; js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_BAD_FLAG, charBuf); return NULL; } } } } return js_NewRegExp(cx, ts, str, flags, flat); } static void freeRENtree(JSContext *cx, RENode *ren, RENode *stop) { while (ren && (ren != stop)) { RENode *n; switch (ren->op) { case REOP_ALT: { RENode *altStop = (RENode *)(ren->next); while (altStop && (altStop->op == REOP_ALT)) altStop = altStop->next; freeRENtree(cx, (RENode *)(ren->kid), altStop); } break; case REOP_QUANT: case REOP_PLUS: case REOP_STAR: case REOP_OPT: case REOP_LPAREN: case REOP_LPARENNON: case REOP_ASSERT: case REOP_ASSERT_NOT: freeRENtree(cx, (RENode *)(ren->kid), (RENode *)(ren->next)); break; case REOP_CCLASS: if (ren->u.ucclass.bitmap) JS_free(cx, ren->u.ucclass.bitmap); break; } n = ren->next; JS_free(cx, ren); ren = n; } } #define HOLD_REGEXP(cx, re) JS_ATOMIC_INCREMENT(&(re)->nrefs) #define DROP_REGEXP(cx, re) js_DestroyRegExp(cx, re) void js_DestroyRegExp(JSContext *cx, JSRegExp *re) { if (JS_ATOMIC_DECREMENT(&re->nrefs) == 0) { freeRENtree(cx, re->ren, NULL); JS_free(cx, re); } } typedef struct MatchState { JSContext *context; /* for access to regExpStatics */ JSBool anchoring; /* true if multiline anchoring ^/$ */ jsbytecode *pcend; /* pc limit (fencepost) */ const jschar *cpbegin, *cpend; /* cp base address and limit */ size_t start; /* offset from cpbegin to start at */ ptrdiff_t skipped; /* chars skipped anchoring this r.e. */ uint8 flags; /* flags */ uintN parenCount; /* number of paren substring matches */ JSSubString *maybeParens; /* possible paren substring pointers */ JSSubString *parens; /* certain paren substring matches */ JSBool ok; /* indicates runtime error during matching */ } MatchState; /* * Returns updated cp on match, null on mismatch. */ static JSBool matchChar(int flags, jschar c, jschar c2) { if (c == c2) return JS_TRUE; else if ((flags & JSREG_FOLD) != 0) { return ((c = JS_TOUPPER(c)) == (c2 = JS_TOUPPER(c2))) || (JS_TOLOWER(c) == JS_TOLOWER(c2)); } else return JS_FALSE; } static const jschar *matchRENodes(MatchState *state, RENode *ren, RENode *stop, const jschar *cp); typedef struct { MatchState *state; RENode *kid; RENode *next; RENode *stop; int kidCount; int maxKid; } GreedyState; static const jschar *greedyRecurse(GreedyState *grState, const jschar *cp, const jschar *previousKid) { const jschar *kidMatch; const jschar *match; int num = grState->state->parenCount; #ifdef XP_MAC if (StackSpace() < 16384) { JS_ReportOutOfMemory (grState->state->context); grState->state->ok = JS_FALSE; return NULL; } #endif /* * When the kid match fails, we reset the parencount and run any * previously succesful kid in order to restablish it's paren * contents. */ kidMatch = matchRENodes(grState->state, grState->kid, grState->next, cp); if (kidMatch == NULL) { match = matchRENodes(grState->state, grState->next, grState->stop, cp); if (match) { grState->state->parenCount = num; if (previousKid != NULL) matchRENodes(grState->state, grState->kid, grState->next, previousKid); return cp; } return NULL; } else { if (kidMatch == cp) { if (previousKid != NULL) matchRENodes(grState->state, grState->kid, grState->next, previousKid); return kidMatch; /* no point pursuing an empty match forever */ } if (!grState->maxKid || (++grState->kidCount < grState->maxKid)) { match = greedyRecurse(grState, kidMatch, cp); if (match) return match; if (grState->maxKid) --grState->kidCount; } grState->state->parenCount = num; if (matchRENodes(grState->state, grState->next, grState->stop, kidMatch)) { matchRENodes(grState->state, grState->kid, grState->next, cp); return kidMatch; } return NULL; } } static const jschar *matchGreedyKid(MatchState *state, RENode *ren, RENode *stop, int kidCount, const jschar *cp, const jschar *previousKid) { GreedyState grState; const jschar *match; grState.state = state; grState.kid = (RENode *)ren->kid; grState.next = ren->next; grState.kidCount = kidCount; grState.maxKid = (ren->op == REOP_QUANT) ? ren->u.range.max : 0; /* We try to match the sub-tree to completion first, and if that doesn't work, match only up to the given end of the sub-tree. */ grState.stop = NULL; match = greedyRecurse(&grState, cp, previousKid); if (match || !stop) return match; grState.kidCount = kidCount; grState.stop = stop; return greedyRecurse(&grState, cp, previousKid); } static const jschar *matchNonGreedyKid(MatchState *state, RENode *ren, int kidCount, int maxKid, const jschar *cp) { const jschar *kidMatch; const jschar *match; match = matchRENodes(state, ren->next, NULL, cp); if (match != NULL) return cp; kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch == NULL) return NULL; if (kidMatch == cp) return kidMatch; /* no point pursuing an empty match forever */ return matchNonGreedyKid(state, ren, kidCount, maxKid, kidMatch); } static void calcBMSize(MatchState *state, RENode *ren) { const jschar *cp = (const jschar *) ren->kid; const jschar *cp2 = (const jschar *) ren->u.kid2; uintN maxc = 0; jschar c, c2; while (cp < cp2) { c = *cp++; if (c == '\\') { if (cp + 5 <= cp2 && *cp == 'u' && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3]) && JS7_ISHEX(cp[4])) { c = (((((JS7_UNHEX(cp[1]) << 4) + JS7_UNHEX(cp[2])) << 4) + JS7_UNHEX(cp[3])) << 4) + JS7_UNHEX(cp[4]); cp += 5; } else { /* * For the not whitespace, not word or not digit cases * we widen the range to the complete unicode range. */ if ((*cp == 'S') || (*cp == 'W') || (*cp == 'D')) { maxc = 65535; break; /* leave now, it can't get worse */ } if (maxc < 255) maxc = 255; /* * Octal and hex escapes can't be > 255. Skip this * backslash and let the loop pass over the remaining * escape sequence as if it were text to match. */ continue; } } if (state->flags & JSREG_FOLD) { /* * Don't assume that lowercase are above uppercase, or * that c is either even when c has upper and lowercase * versions. */ if ((c2 = JS_TOUPPER(c)) > maxc) maxc = c2; if ((c2 = JS_TOLOWER(c2)) > maxc) maxc = c2; } if (c > maxc) maxc = c; } ren->u.ucclass.bmsize = (uint16)((size_t)(maxc + JS_BITS_PER_BYTE) / JS_BITS_PER_BYTE); } static JSBool buildBitmap(MatchState *state, RENode *ren) { uintN i, n, b, c, lastc, foldc, nchars; uint8 *bitmap; uint8 fill; JSBool inrange; const jschar *cp = (const jschar *) ren->kid; const jschar *end = (const jschar *) ren->u.kid2; const jschar *ocp; calcBMSize(state, ren); ren->u.ucclass.bitmap = bitmap = (uint8*) JS_malloc(state->context, ren->u.ucclass.bmsize); if (!bitmap) { JS_ReportOutOfMemory(state->context); return JS_FALSE; } if (*cp == '^') { fill = 0xff; cp++; } else { fill = 0; } n = (uintN)ren->u.ucclass.bmsize; for (i = 0; i < n; i++) bitmap[i] = fill; nchars = n * JS_BITS_PER_BYTE; /* Split ops up into statements to keep MSVC1.52 from crashing. */ #define MATCH_BIT(c) { i = (c) >> 3; b = (c) & 7; b = 1 << b; \ if (fill) bitmap[i] &= ~b; else bitmap[i] |= b; } lastc = nchars; inrange = JS_FALSE; while (cp < end) { c = (uintN) *cp++; if (c == '\\') { c = *cp++; switch (c) { case 'b': case 'f': case 'n': case 'r': case 't': case 'v': c = js_strchr(js_EscapeMap, (jschar)c)[-1]; break; #define CHECK_RANGE() if (inrange) { MATCH_BIT(lastc); MATCH_BIT('-'); \ inrange = JS_FALSE; } case 'd': CHECK_RANGE(); for (c = '0'; c <= '9'; c++) MATCH_BIT(c); continue; case 'D': CHECK_RANGE(); for (c = 0; c < '0'; c++) MATCH_BIT(c); for (c = '9' + 1; c < nchars; c++) MATCH_BIT(c); continue; case 'w': CHECK_RANGE(); for (c = 0; c < nchars; c++) if (JS_ISWORD(c)) MATCH_BIT(c); continue; case 'W': CHECK_RANGE(); for (c = 0; c < nchars; c++) if (!JS_ISWORD(c)) MATCH_BIT(c); continue; case 's': CHECK_RANGE(); for (c = 0; c < nchars; c++) if (JS_ISSPACE(c)) MATCH_BIT(c); continue; case 'S': CHECK_RANGE(); for (c = 0; c < nchars; c++) if (!JS_ISSPACE(c)) MATCH_BIT(c); continue; #undef CHECK_RANGE case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': n = JS7_UNDEC(c); ocp = cp - 2; c = *cp; if ('0' <= c && c <= '7') { cp++; n = 8 * n + JS7_UNDEC(c); c = *cp; if ('0' <= c && c <= '7') { cp++; i = 8 * n + JS7_UNDEC(c); if (i <= 0377) n = i; else cp--; } } c = n; break; case 'x': ocp = cp; c = *cp++; if (JS7_ISHEX(c)) { n = JS7_UNHEX((char) c); c = *cp++; if (JS7_ISHEX(c)) { n <<= 4; n += JS7_UNHEX((char) c); } } else { cp = ocp; /* \xZZ is xZZ (Perl does \0ZZ!) */ n = 'x'; } c = n; break; case 'u': if (JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3])) { n = (((((JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1])) << 4) + JS7_UNHEX(cp[2])) << 4) + JS7_UNHEX(cp[3]); c = n; cp += 4; } break; case 'c': c = *cp++; c = JS_TOUPPER(c); c = JS_TOCTRL(c); break; } } if (inrange) { if (lastc > c) { JS_ReportErrorNumber(state->context, js_GetErrorMessage, NULL, JSMSG_BAD_CLASS_RANGE); return JS_FALSE; } inrange = JS_FALSE; } else { /* Set lastc so we match just c's bit in the for loop. */ lastc = c; /* [balance: */ if (*cp == '-' && cp + 1 < end && cp[1] != ']') { cp++; inrange = JS_TRUE; continue; } } /* Match characters in the range [lastc, c]. */ for (; lastc <= c; lastc++) { MATCH_BIT(lastc); if (state->flags & JSREG_FOLD) { /* * Must do both upper and lower for Turkish dotless i, * Georgian, etc. */ foldc = JS_TOUPPER(lastc); MATCH_BIT(foldc); foldc = JS_TOLOWER(foldc); MATCH_BIT(foldc); } } lastc = c; } #define IS_LINE_TERMINATOR(c) ( (c == '\n') \ || (c == '\r') \ || (c == LINE_SEPARATOR) \ || (c == PARA_SEPARATOR) ) return JS_TRUE; } static const jschar *matchRENodes(MatchState *state, RENode *ren, RENode *stop, const jschar *cp) { const jschar *cp2, *kidMatch, *lastKid, *source, *cpend = state->cpend; jschar c; JSSubString *parsub; uintN i, b, bit, num, length; while ((ren != stop) && (ren != NULL)) { switch (ren->op) { case REOP_EMPTY: break; case REOP_ALT: if (ren->next->op != REOP_ALT) { ren = (RENode *)ren->kid; continue; } num = state->parenCount; kidMatch = matchRENodes(state, (RENode *)ren->kid, stop, cp); if (kidMatch != NULL) return kidMatch; for (i = num; i < state->parenCount; i++) state->parens[i].length = 0; state->parenCount = num; break; case REOP_QUANT: lastKid = NULL; for (num = 0; num < ren->u.range.min; num++) { kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch == NULL) return NULL; lastKid = cp; cp = kidMatch; } if (num == ren->u.range.max) break; if ((ren->flags & RENODE_MINIMAL) == 0) { cp2 = matchGreedyKid(state, ren, stop, num, cp, lastKid); if (cp2 == NULL) { if (lastKid) cp = matchRENodes(state, (RENode *)ren->kid, ren->next, lastKid); } else cp = cp2; } else { cp = matchNonGreedyKid(state, ren, num, ren->u.range.max, cp); if (cp == NULL) return NULL; } break; case REOP_PLUS: kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch == NULL) return NULL; if ((ren->flags & RENODE_MINIMAL) == 0) { cp2 = matchGreedyKid(state, ren, stop, 1, kidMatch, cp); if (cp2 == NULL) cp = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); else cp = cp2; } else cp = matchNonGreedyKid(state, ren, 1, 0, kidMatch); if (cp == NULL) return NULL; break; case REOP_STAR: if ((ren->flags & RENODE_MINIMAL) == 0) { cp2 = matchGreedyKid(state, ren, stop, 0, cp, NULL); if (cp2) cp = cp2; } else { cp = matchNonGreedyKid(state, ren, 0, 0, cp); if (cp == NULL) return NULL; } break; case REOP_OPT: num = state->parenCount; if (ren->flags & RENODE_MINIMAL) { const jschar *restMatch = matchRENodes(state, ren->next, stop, cp); if (restMatch != NULL) return restMatch; } kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch == NULL) { /* need to undo the result of running the kid */ for (i = num; i < state->parenCount; i++) state->parens[i].length = 0; state->parenCount = num; break; } else { const jschar *restMatch = matchRENodes(state, ren->next, stop, kidMatch); if (restMatch == NULL) { /* need to undo the result of running the kid */ for (i = num; i < state->parenCount; i++) state->parens[i].length = 0; state->parenCount = num; break; } else return restMatch; } case REOP_LPARENNON: ren = (RENode *)ren->kid; continue; case REOP_RPARENNON: break; case REOP_LPAREN: num = ren->u.num; ren = (RENode *)ren->kid; parsub = &state->parens[num]; parsub->chars = cp; parsub->length = 0; if (num >= state->parenCount) state->parenCount = num + 1; continue; case REOP_RPAREN: num = ren->u.num; parsub = &state->parens[num]; parsub->length = cp - parsub->chars; break; case REOP_ASSERT: kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch == NULL) return NULL; break; case REOP_ASSERT_NOT: kidMatch = matchRENodes(state, (RENode *)ren->kid, ren->next, cp); if (kidMatch != NULL) return NULL; break; case REOP_BACKREF: num = ren->u.num; if (num >= state->parenCount) { JS_ReportErrorNumber(state->context, js_GetErrorMessage, NULL, JSMSG_BAD_BACKREF); state->ok = JS_FALSE; return NULL; } parsub = &state->parens[num]; if ((cp + parsub->length) > cpend) return NULL; else { cp2 = parsub->chars; for (i = 0; i < parsub->length; i++) { if (!matchChar(state->flags, *cp++, *cp2++)) return NULL; } } break; case REOP_CCLASS: if (cp != cpend) { if (ren->u.ucclass.bitmap == NULL) { if (!buildBitmap(state, ren)) { state->ok = JS_FALSE; return NULL; } } c = *cp; b = (c >> 3); if (b >= ren->u.ucclass.bmsize) { cp2 = (jschar*) ren->kid; if (*cp2 == '^') cp++; else return NULL; } else { bit = c & 7; bit = 1 << bit; if ((ren->u.ucclass.bitmap[b] & bit) != 0) cp++; else return NULL; } } else return NULL; break; case REOP_DOT: if ((cp != cpend) && !IS_LINE_TERMINATOR(*cp)) cp++; else return NULL; break; case REOP_DOTSTARMIN: for (cp2 = cp; cp2 <= cpend; cp2++) { const jschar *cp3 = matchRENodes(state, ren->next, stop, cp2); if (cp3 != NULL) return cp3; if ((cp2 == cpend) || IS_LINE_TERMINATOR(*cp2)) return NULL; } return NULL; case REOP_DOTSTAR: for (cp2 = cp; cp2 < cpend; cp2++) if (IS_LINE_TERMINATOR(*cp2)) break; while (cp2 >= cp) { const jschar *cp3 = matchRENodes(state, ren->next, NULL, cp2); if (cp3 != NULL) { cp = cp2; break; } cp2--; } break; case REOP_WBDRY: if (((cp == state->cpbegin) || !JS_ISWORD(cp[-1])) ^ ((cp >= cpend) || !JS_ISWORD(*cp))) ; /* leave cp */ else return NULL; break; case REOP_WNONBDRY: if (((cp == state->cpbegin) || !JS_ISWORD(cp[-1])) ^ ((cp < cpend) && JS_ISWORD(*cp))) ; /* leave cp */ else return NULL; break; case REOP_EOLONLY: case REOP_EOL: if (cp == cpend) ; /* leave cp */ else { if (state->context->regExpStatics.multiline || ((state->flags & JSREG_MULTILINE) != 0)) if (IS_LINE_TERMINATOR(*cp)) ;/* leave cp */ else return NULL; else return NULL; } break; case REOP_BOL: if (cp != state->cpbegin) { if ((cp < cpend) && (state->context->regExpStatics.multiline || ((state->flags & JSREG_MULTILINE) != 0))) { if (IS_LINE_TERMINATOR(cp[-1])) { break; } } return NULL; } /* leave cp */ break; case REOP_DIGIT: if ((cp != cpend) && JS_ISDIGIT(*cp)) cp++; else return NULL; break; case REOP_NONDIGIT: if ((cp != cpend) && !JS_ISDIGIT(*cp)) cp++; else return NULL; break; case REOP_ALNUM: if ((cp != cpend) && JS_ISWORD(*cp)) cp++; else return NULL; break; case REOP_NONALNUM: if ((cp != cpend) && !JS_ISWORD(*cp)) cp++; else return NULL; break; case REOP_SPACE: if ((cp != cpend) && JS_ISSPACE(*cp)) cp++; else return NULL; break; case REOP_NONSPACE: if ((cp != cpend) && !JS_ISSPACE(*cp)) cp++; else return NULL; break; case REOP_FLAT1: if ((cp != cpend) && matchChar(state->flags, ren->u.chr, *cp)) cp++; else return NULL; break; case REOP_FLAT: source = (jschar*) ren->kid; length = (const jschar *)ren->u.kid2 - source; if ((cp + length) > cpend) return NULL; else { for (i = 0; i < length; i++) { if (!matchChar(state->flags, *cp++, *source++)) return NULL; } } break; case REOP_JUMP: break; case REOP_END: break; default : JS_ASSERT(JS_FALSE); break; } ren = ren->next; } return cp; } static const jschar * MatchRegExp(MatchState *state, RENode *ren, const jschar *cp) { const jschar *cp2, *cp3; /* have to include the position beyond the last character in order to detect end-of-input/line condition */ for (cp2 = cp; cp2 <= state->cpend; cp2++) { state->skipped = cp2 - cp; state->parenCount = 0; cp3 = matchRENodes(state, ren, NULL, cp2); if (!state->ok) return NULL; if (cp3 != NULL) return cp3; } return NULL; } JSBool js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, JSBool test, jsval *rval) { MatchState state; const jschar *cp, *ep; size_t i, length, start; void *mark; JSSubString *parsub, *morepar; JSBool ok; JSRegExpStatics *res; ptrdiff_t matchlen; uintN num, morenum; JSString *parstr, *matchstr; JSObject *obj; /* * Initialize a state struct to minimize recursive argument traffic. */ state.context = cx; state.anchoring = JS_FALSE; state.flags = (uint8) re->flags; /* * It's safe to load from cp because JSStrings have a zero at the end, * and we never let cp get beyond cpend. */ start = *indexp; length = JSSTRING_LENGTH(str); if (start > length) start = length; cp = JSSTRING_CHARS(str); state.cpbegin = cp; state.cpend = cp + length; cp += start; state.start = start; state.skipped = 0; /* * Use the temporary arena pool to grab space for parenthetical matches. * After the JS_ARENA_ALLOCATE early return on error, goto out to be sure * to free this memory. */ length = 2 * sizeof(JSSubString) * re->parenCount; mark = JS_ARENA_MARK(&cx->tempPool); JS_ARENA_ALLOCATE_CAST(parsub, JSSubString *, &cx->tempPool, length); if (!parsub) { JS_ReportOutOfMemory(cx); return JS_FALSE; } memset(parsub, 0, length); state.parenCount = 0; state.maybeParens = parsub; state.parens = parsub + re->parenCount; state.ok = JS_TRUE; /* * Call the recursive matcher to do the real work. Return null on mismatch * whether testing or not. On match, return an extended Array object. */ cp = MatchRegExp(&state, re->ren, cp); if (!(ok = state.ok)) goto out; if (!cp) { *rval = JSVAL_NULL; goto out; } i = PTRDIFF(cp, state.cpbegin, jschar); *indexp = i; matchlen = i - (start + state.skipped); ep = cp; cp -= matchlen; if (test) { /* * Testing for a match and updating cx->regExpStatics: don't allocate * an array object, do return true. */ *rval = JSVAL_TRUE; /* Avoid warning. (gcc doesn't detect that obj is needed iff !test); */ obj = NULL; } else { /* * The array returned on match has element 0 bound to the matched * string, elements 1 through state.parenCount bound to the paren * matches, an index property telling the length of the left context, * and an input property referring to the input string. */ obj = js_NewArrayObject(cx, 0, NULL); if (!obj) { ok = JS_FALSE; goto out; } *rval = OBJECT_TO_JSVAL(obj); #define DEFVAL(val, id) { \ ok = js_DefineProperty(cx, obj, id, val, \ JS_PropertyStub, JS_PropertyStub, \ JSPROP_ENUMERATE, NULL); \ if (!ok) { \ cx->newborn[GCX_OBJECT] = NULL; \ cx->newborn[GCX_STRING] = NULL; \ goto out; \ } \ } matchstr = js_NewStringCopyN(cx, cp, matchlen, 0); if (!matchstr) { cx->newborn[GCX_OBJECT] = NULL; ok = JS_FALSE; goto out; } DEFVAL(STRING_TO_JSVAL(matchstr), INT_TO_JSVAL(0)); } res = &cx->regExpStatics; res->input = str; JS_ASSERT(state.parenCount <= re->parenCount); if (state.parenCount == 0) { res->parenCount = 0; res->lastParen = js_EmptySubString; } else { for (num = 0; num < state.parenCount; num++) { parsub = &state.parens[num]; if (num < 9) { res->parens[num] = *parsub; } else { morenum = num - 9; morepar = res->moreParens; if (!morepar) { res->moreLength = 10; morepar = (JSSubString*) JS_malloc(cx, 10 * sizeof(JSSubString)); } else if (morenum >= res->moreLength) { res->moreLength += 10; morepar = (JSSubString*) JS_realloc(cx, morepar, res->moreLength * sizeof(JSSubString)); } if (!morepar) { cx->newborn[GCX_OBJECT] = NULL; cx->newborn[GCX_STRING] = NULL; ok = JS_FALSE; goto out; } res->moreParens = morepar; morepar[morenum] = *parsub; } if (test) continue; parstr = js_NewStringCopyN(cx, parsub->chars, parsub->length, 0); if (!parstr) { cx->newborn[GCX_OBJECT] = NULL; cx->newborn[GCX_STRING] = NULL; ok = JS_FALSE; goto out; } ok = js_DefineProperty(cx, obj, INT_TO_JSVAL(num + 1), STRING_TO_JSVAL(parstr), NULL, NULL, JSPROP_ENUMERATE, NULL); if (!ok) { cx->newborn[GCX_OBJECT] = NULL; cx->newborn[GCX_STRING] = NULL; goto out; } } res->parenCount = num; res->lastParen = *parsub; } if (!test) { /* * Define the index and input properties last for better for/in loop * order (so they come after the elements). */ DEFVAL(INT_TO_JSVAL(start + state.skipped), (jsid)cx->runtime->atomState.indexAtom); DEFVAL(STRING_TO_JSVAL(str), (jsid)cx->runtime->atomState.inputAtom); } #undef DEFVAL res->lastMatch.chars = cp; res->lastMatch.length = matchlen; if (cx->version == JSVERSION_1_2) { /* * JS1.2 emulated Perl4.0.1.8 (patch level 36) for global regexps used * in scalar contexts, and unintentionally for the string.match "list" * psuedo-context. On "hi there bye", the following would result: * * Language while(/ /g){print("$`");} s/ /$`/g * perl4.036 "hi", "there" "hihitherehi therebye" * perl5 "hi", "hi there" "hihitherehi therebye" * js1.2 "hi", "there" "hihitheretherebye" */ res->leftContext.chars = JSSTRING_CHARS(str) + start; res->leftContext.length = state.skipped; } else { /* * For JS1.3 and ECMAv2, emulate Perl5 exactly: * * js1.3 "hi", "hi there" "hihitherehi therebye" */ res->leftContext.chars = JSSTRING_CHARS(str); res->leftContext.length = start + state.skipped; } res->rightContext.chars = ep; res->rightContext.length = state.cpend - ep; out: JS_ARENA_RELEASE(&cx->tempPool, mark); return ok; } /************************************************************************/ enum regexp_tinyid { REGEXP_SOURCE = -1, REGEXP_GLOBAL = -2, REGEXP_IGNORE_CASE = -3, REGEXP_LAST_INDEX = -4, REGEXP_MULTILINE = -5 }; #define REGEXP_PROP_ATTRS (JSPROP_PERMANENT | JSPROP_SHARED) static JSPropertySpec regexp_props[] = { {"source", REGEXP_SOURCE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, {"global", REGEXP_GLOBAL, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, {"ignoreCase", REGEXP_IGNORE_CASE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, {"lastIndex", REGEXP_LAST_INDEX, REGEXP_PROP_ATTRS,0,0}, {"multiline", REGEXP_MULTILINE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, {0,0,0,0,0} }; static JSBool regexp_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { jsint slot; JSRegExp *re; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); if (slot == REGEXP_LAST_INDEX) return JS_GetReservedSlot(cx, obj, 0, vp); JS_LOCK_OBJ(cx, obj); re = (JSRegExp *) JS_GetInstancePrivate(cx, obj, &js_RegExpClass, NULL); if (re) { switch (slot) { case REGEXP_SOURCE: *vp = STRING_TO_JSVAL(re->source); break; case REGEXP_GLOBAL: *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_GLOB) != 0); break; case REGEXP_IGNORE_CASE: *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_FOLD) != 0); break; case REGEXP_MULTILINE: *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_MULTILINE) != 0); break; } } JS_UNLOCK_OBJ(cx, obj); return JS_TRUE; } static JSBool regexp_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSBool ok; jsint slot; jsdouble lastIndex; ok = JS_TRUE; if (!JSVAL_IS_INT(id)) return ok; slot = JSVAL_TO_INT(id); if (slot == REGEXP_LAST_INDEX) { if (!js_ValueToNumber(cx, *vp, &lastIndex)) return JS_FALSE; lastIndex = js_DoubleToInteger(lastIndex); ok = js_NewNumberValue(cx, lastIndex, vp) && JS_SetReservedSlot(cx, obj, 0, *vp); } return ok; } /* * RegExp class static properties and their Perl counterparts: * * RegExp.input $_ * RegExp.multiline $* * RegExp.lastMatch $& * RegExp.lastParen $+ * RegExp.leftContext $` * RegExp.rightContext $' */ enum regexp_static_tinyid { REGEXP_STATIC_INPUT = -1, REGEXP_STATIC_MULTILINE = -2, REGEXP_STATIC_LAST_MATCH = -3, REGEXP_STATIC_LAST_PAREN = -4, REGEXP_STATIC_LEFT_CONTEXT = -5, REGEXP_STATIC_RIGHT_CONTEXT = -6 }; JSBool js_InitRegExpStatics(JSContext *cx, JSRegExpStatics *res) { JS_ClearRegExpStatics(cx); return js_AddRoot(cx, &res->input, "res->input"); } void js_FreeRegExpStatics(JSContext *cx, JSRegExpStatics *res) { if (res->moreParens) { JS_free(cx, res->moreParens); res->moreParens = NULL; } js_RemoveRoot(cx->runtime, &res->input); } static JSBool regexp_static_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { jsint slot; JSRegExpStatics *res; JSString *str; JSSubString *sub; res = &cx->regExpStatics; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); switch (slot) { case REGEXP_STATIC_INPUT: *vp = res->input ? STRING_TO_JSVAL(res->input) : JS_GetEmptyStringValue(cx); return JS_TRUE; case REGEXP_STATIC_MULTILINE: *vp = BOOLEAN_TO_JSVAL(res->multiline); return JS_TRUE; case REGEXP_STATIC_LAST_MATCH: sub = &res->lastMatch; break; case REGEXP_STATIC_LAST_PAREN: sub = &res->lastParen; break; case REGEXP_STATIC_LEFT_CONTEXT: sub = &res->leftContext; break; case REGEXP_STATIC_RIGHT_CONTEXT: sub = &res->rightContext; break; default: sub = REGEXP_PAREN_SUBSTRING(res, slot); break; } str = js_NewStringCopyN(cx, sub->chars, sub->length, 0); if (!str) return JS_FALSE; *vp = STRING_TO_JSVAL(str); return JS_TRUE; } static JSBool regexp_static_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSRegExpStatics *res; if (!JSVAL_IS_INT(id)) return JS_TRUE; res = &cx->regExpStatics; /* XXX use if-else rather than switch to keep MSVC1.52 from crashing */ if (JSVAL_TO_INT(id) == REGEXP_STATIC_INPUT) { if (!JSVAL_IS_STRING(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) { return JS_FALSE; } res->input = JSVAL_TO_STRING(*vp); } else if (JSVAL_TO_INT(id) == REGEXP_STATIC_MULTILINE) { if (!JSVAL_IS_BOOLEAN(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp)) { return JS_FALSE; } res->multiline = JSVAL_TO_BOOLEAN(*vp); } return JS_TRUE; } static JSPropertySpec regexp_static_props[] = { {"input", REGEXP_STATIC_INPUT, JSPROP_ENUMERATE|JSPROP_SHARED, regexp_static_getProperty, regexp_static_setProperty}, {"multiline", REGEXP_STATIC_MULTILINE, JSPROP_ENUMERATE|JSPROP_SHARED, regexp_static_getProperty, regexp_static_setProperty}, {"lastMatch", REGEXP_STATIC_LAST_MATCH, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"lastParen", REGEXP_STATIC_LAST_PAREN, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"leftContext", REGEXP_STATIC_LEFT_CONTEXT, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"rightContext", REGEXP_STATIC_RIGHT_CONTEXT, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, /* XXX should have block scope and local $1, etc. */ {"$1", 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$2", 1, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$3", 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$4", 3, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$5", 4, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$6", 5, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$7", 6, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$8", 7, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {"$9", 8, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, regexp_static_getProperty, regexp_static_getProperty}, {0,0,0,0,0} }; static void regexp_finalize(JSContext *cx, JSObject *obj) { JSRegExp *re; re = (JSRegExp *) JS_GetPrivate(cx, obj); if (!re) return; js_DestroyRegExp(cx, re); } /* Forward static prototype. */ static JSBool regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); static JSBool regexp_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { return regexp_exec(cx, JSVAL_TO_OBJECT(argv[-2]), argc, argv, rval); } #if JS_HAS_XDR #include "jsxdrapi.h" static JSBool regexp_xdrObject(JSXDRState *xdr, JSObject **objp) { JSRegExp *re; JSString *source; uint8 flags; if (xdr->mode == JSXDR_ENCODE) { re = (JSRegExp *) JS_GetPrivate(xdr->cx, *objp); if (!re) return JS_FALSE; source = re->source; flags = (uint8) re->flags; } if (!JS_XDRString(xdr, &source) || !JS_XDRUint8(xdr, &flags)) { return JS_FALSE; } if (xdr->mode == JSXDR_DECODE) { *objp = js_NewObject(xdr->cx, &js_RegExpClass, NULL, NULL); if (!*objp) return JS_FALSE; re = js_NewRegExp(xdr->cx, NULL, source, flags, JS_FALSE); if (!re) return JS_FALSE; if (!JS_SetPrivate(xdr->cx, *objp, re) || !js_SetLastIndex(xdr->cx, *objp, 0)) { js_DestroyRegExp(xdr->cx, re); return JS_FALSE; } } return JS_TRUE; } #else /* !JS_HAS_XDR */ #define regexp_xdrObject NULL #endif /* !JS_HAS_XDR */ static uint32 regexp_mark(JSContext *cx, JSObject *obj, void *arg) { JSRegExp *re = (JSRegExp *) JS_GetPrivate(cx, obj); if (re) JS_MarkGCThing(cx, re->source, "source", arg); return 0; } JSClass js_RegExpClass = { js_RegExp_str, JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), JS_PropertyStub, JS_PropertyStub, regexp_getProperty, regexp_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, regexp_finalize, NULL, NULL, regexp_call, NULL, regexp_xdrObject, NULL, regexp_mark, 0 }; static JSBool regexp_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSRegExp *re; jschar *chars; size_t length, nflags; uintN flags; JSString *str; if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) return JS_FALSE; JS_LOCK_OBJ(cx, obj); re = (JSRegExp *) JS_GetPrivate(cx, obj); if (!re) { JS_UNLOCK_OBJ(cx, obj); *rval = STRING_TO_JSVAL(cx->runtime->emptyString); return JS_TRUE; } length = JSSTRING_LENGTH(re->source) + 2; nflags = 0; for (flags = re->flags; flags != 0; flags &= flags - 1) nflags++; chars = (jschar*) JS_malloc(cx, (length + nflags + 1) * sizeof(jschar)); if (!chars) { JS_UNLOCK_OBJ(cx, obj); return JS_FALSE; } chars[0] = '/'; js_strncpy(&chars[1], JSSTRING_CHARS(re->source), length - 2); chars[length-1] = '/'; if (nflags) { if (re->flags & JSREG_GLOB) chars[length++] = 'g'; if (re->flags & JSREG_FOLD) chars[length++] = 'i'; if (re->flags & JSREG_MULTILINE) chars[length++] = 'm'; } JS_UNLOCK_OBJ(cx, obj); chars[length] = 0; str = js_NewString(cx, chars, length, 0); if (!str) { JS_free(cx, chars); return JS_FALSE; } *rval = STRING_TO_JSVAL(str); return JS_TRUE; } static JSBool regexp_compile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *opt, *str; JSRegExp *oldre, *re; JSBool ok; JSObject *obj2; if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) return JS_FALSE; opt = NULL; if (argc == 0) { str = cx->runtime->emptyString; } else { if (JSVAL_IS_OBJECT(argv[0])) { /* * If we get passed in a RegExp object we construct a new * RegExp that is a duplicate of it by re-compiling the * original source code. ECMA requires that it be an error * here if the flags are specified. (We must use the flags * from the original RegExp also). */ obj2 = JSVAL_TO_OBJECT(argv[0]); if (obj2 && OBJ_GET_CLASS(cx, obj2) == &js_RegExpClass) { if (argc >= 2 && !JSVAL_IS_VOID(argv[1])) { /* 'flags' passed */ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED); return JS_FALSE; } JS_LOCK_OBJ(cx, obj2); re = (JSRegExp *) JS_GetPrivate(cx, obj2); if (!re) { JS_UNLOCK_OBJ(cx, obj2); return JS_FALSE; } re = js_NewRegExp(cx, NULL, re->source, re->flags, JS_FALSE); JS_UNLOCK_OBJ(cx, obj2); goto madeit; } } str = js_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; argv[0] = STRING_TO_JSVAL(str); if (argc > 1) { if (JSVAL_IS_VOID(argv[1])) { opt = NULL; } else { opt = js_ValueToString(cx, argv[1]); if (!opt) return JS_FALSE; argv[1] = STRING_TO_JSVAL(opt); } } } re = js_NewRegExpOpt(cx, NULL, str, opt, JS_FALSE); madeit: if (!re) return JS_FALSE; JS_LOCK_OBJ(cx, obj); oldre = (JSRegExp *) JS_GetPrivate(cx, obj); ok = JS_SetPrivate(cx, obj, re) && js_SetLastIndex(cx, obj, 0); JS_UNLOCK_OBJ(cx, obj); if (!ok) { js_DestroyRegExp(cx, re); } else { if (oldre) js_DestroyRegExp(cx, oldre); *rval = OBJECT_TO_JSVAL(obj); } return ok; } static JSBool regexp_exec_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, JSBool test, jsval *rval) { JSBool ok; JSRegExp *re; jsdouble lastIndex; JSString *str; size_t i; ok = JS_InstanceOf(cx, obj, &js_RegExpClass, argv); if (!ok) return JS_FALSE; JS_LOCK_OBJ(cx, obj); re = (JSRegExp *) JS_GetPrivate(cx, obj); if (!re) { JS_UNLOCK_OBJ(cx, obj); return JS_TRUE; } /* NB: we must reach out: after this paragraph, in order to drop re. */ HOLD_REGEXP(cx, re); if (re->flags & JSREG_GLOB) { ok = js_GetLastIndex(cx, obj, &lastIndex); } else { lastIndex = 0; } JS_UNLOCK_OBJ(cx, obj); if (!ok) goto out; /* Now that obj is unlocked, it's safe to (potentially) grab the GC lock. */ if (argc == 0) { str = cx->regExpStatics.input; if (!str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_INPUT, JS_GetStringBytes(re->source), (re->flags & JSREG_GLOB) ? "g" : "", (re->flags & JSREG_FOLD) ? "i" : "", (re->flags & JSREG_MULTILINE) ? "m" : ""); ok = JS_FALSE; goto out; } } else { str = js_ValueToString(cx, argv[0]); if (!str) goto out; argv[0] = STRING_TO_JSVAL(str); } if (lastIndex < 0 || JSSTRING_LENGTH(str) < lastIndex) { ok = js_SetLastIndex(cx, obj, 0); *rval = JSVAL_NULL; } else { i = (size_t) lastIndex; ok = js_ExecuteRegExp(cx, re, str, &i, test, rval); if (ok && (re->flags & JSREG_GLOB)) ok = js_SetLastIndex(cx, obj, (*rval == JSVAL_NULL) ? 0 : i); } out: DROP_REGEXP(cx, re); return ok; } static JSBool regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { return regexp_exec_sub(cx, obj, argc, argv, JS_FALSE, rval); } static JSBool regexp_test(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { if (!regexp_exec_sub(cx, obj, argc, argv, JS_TRUE, rval)) return JS_FALSE; if (*rval != JSVAL_TRUE) *rval = JSVAL_FALSE; return JS_TRUE; } static JSFunctionSpec regexp_methods[] = { #if JS_HAS_TOSOURCE {js_toSource_str, regexp_toString, 0,0,0}, #endif {js_toString_str, regexp_toString, 0,0,0}, {"compile", regexp_compile, 1,0,0}, {"exec", regexp_exec, 0,0,0}, {"test", regexp_test, 0,0,0}, {0,0,0,0,0} }; static JSBool RegExp(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { /* * If first arg is regexp and no flags are given, just return the arg. * (regexp_compile detects the regexp + flags case and throws a * TypeError.) See 10.15.3.1. */ if ((argc < 2 || JSVAL_IS_VOID(argv[1])) && JSVAL_IS_OBJECT(argv[0]) && OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[0])) == &js_RegExpClass) { *rval = argv[0]; return JS_TRUE; } /* Otherwise, replace obj with a new RegExp object. */ obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); if (!obj) return JS_FALSE; } return regexp_compile(cx, obj, argc, argv, rval); } JSObject * js_InitRegExpClass(JSContext *cx, JSObject *obj) { JSObject *proto, *ctor; jsval rval; proto = JS_InitClass(cx, obj, NULL, &js_RegExpClass, RegExp, 1, regexp_props, regexp_methods, regexp_static_props, NULL); if (!proto || !(ctor = JS_GetConstructor(cx, proto))) return NULL; if (!JS_AliasProperty(cx, ctor, "input", "$_") || !JS_AliasProperty(cx, ctor, "multiline", "$*") || !JS_AliasProperty(cx, ctor, "lastMatch", "$&") || !JS_AliasProperty(cx, ctor, "lastParen", "$+") || !JS_AliasProperty(cx, ctor, "leftContext", "$`") || !JS_AliasProperty(cx, ctor, "rightContext", "$'")) { goto bad; } /* Give RegExp.prototype private data so it matches the empty string. */ if (!regexp_compile(cx, proto, 0, NULL, &rval)) goto bad; return proto; bad: JS_DeleteProperty(cx, obj, js_RegExpClass.name); return NULL; } JSObject * js_NewRegExpObject(JSContext *cx, JSTokenStream *ts, jschar *chars, size_t length, uintN flags) { JSString *str; JSObject *obj; JSRegExp *re; str = js_NewStringCopyN(cx, chars, length, 0); if (!str) return NULL; re = js_NewRegExp(cx, ts, str, flags, JS_FALSE); if (!re) return NULL; obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); if (!obj || !JS_SetPrivate(cx, obj, re) || !js_SetLastIndex(cx, obj, 0)) { js_DestroyRegExp(cx, re); return NULL; } return obj; } JSObject * js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *parent) { JSObject *clone; JSRegExp *re; JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_RegExpClass); clone = js_NewObject(cx, &js_RegExpClass, NULL, parent); if (!clone) return NULL; re = JS_GetPrivate(cx, obj); if (!JS_SetPrivate(cx, clone, re) || !js_SetLastIndex(cx, clone, 0)) { cx->newborn[GCX_OBJECT] = NULL; return NULL; } HOLD_REGEXP(cx, re); return clone; } JSBool js_GetLastIndex(JSContext *cx, JSObject *obj, jsdouble *lastIndex) { jsval v; return JS_GetReservedSlot(cx, obj, 0, &v) && js_ValueToNumber(cx, v, lastIndex); } JSBool js_SetLastIndex(JSContext *cx, JSObject *obj, jsdouble lastIndex) { jsval v; return js_NewNumberValue(cx, lastIndex, &v) && JS_SetReservedSlot(cx, obj, 0, v); } #endif /* JS_HAS_REGEXPS */