1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99 ft=cpp:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla SpiderMonkey JavaScript code.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * the Mozilla Foundation.
21 : * Portions created by the Initial Developer are Copyright (C) 2011
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Chris Leary <cdleary@mozilla.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * 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 : #include "frontend/TokenStream.h"
42 : #include "vm/RegExpStatics.h"
43 : #include "vm/MatchPairs.h"
44 :
45 : #include "jsobjinlines.h"
46 :
47 : #include "vm/RegExpObject-inl.h"
48 : #include "vm/RegExpStatics-inl.h"
49 : #include "vm/StringBuffer-inl.h"
50 :
51 : using namespace js;
52 : using js::detail::RegExpCode;
53 :
54 : JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
55 : JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
56 : JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
57 : JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
58 :
59 : /* RegExpObjectBuilder */
60 :
61 599401 : RegExpObjectBuilder::RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj)
62 599401 : : cx(cx), reobj_(reobj)
63 599401 : {}
64 :
65 : bool
66 599264 : RegExpObjectBuilder::getOrCreate()
67 : {
68 599264 : if (reobj_)
69 559575 : return true;
70 :
71 39689 : JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
72 39689 : if (!obj)
73 0 : return false;
74 39689 : obj->initPrivate(NULL);
75 :
76 39689 : reobj_ = &obj->asRegExp();
77 39689 : return true;
78 : }
79 :
80 : bool
81 551614 : RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
82 : {
83 551614 : JS_ASSERT(!reobj_);
84 :
85 551614 : JSObject *clone = NewObjectWithGivenProto(cx, &RegExpClass, proto, proto->getParent());
86 551614 : if (!clone)
87 0 : return false;
88 551614 : clone->initPrivate(NULL);
89 :
90 551614 : reobj_ = &clone->asRegExp();
91 551614 : return true;
92 : }
93 :
94 : RegExpObject *
95 551164 : RegExpObjectBuilder::build(JSAtom *source, RegExpShared &shared)
96 : {
97 551164 : if (!getOrCreate())
98 0 : return NULL;
99 :
100 551164 : if (!reobj_->init(cx, source, shared.getFlags()))
101 0 : return NULL;
102 :
103 551164 : reobj_->setShared(cx, shared);
104 551164 : return reobj_;
105 : }
106 :
107 : RegExpObject *
108 48100 : RegExpObjectBuilder::build(JSAtom *source, RegExpFlag flags)
109 : {
110 48100 : if (!getOrCreate())
111 0 : return NULL;
112 :
113 48100 : return reobj_->init(cx, source, flags) ? reobj_ : NULL;
114 : }
115 :
116 : RegExpObject *
117 551614 : RegExpObjectBuilder::clone(RegExpObject *other, RegExpObject *proto)
118 : {
119 551614 : if (!getOrCreateClone(proto))
120 0 : return NULL;
121 :
122 : /*
123 : * Check that the RegExpShared for the original is okay to use in
124 : * the clone -- if the |RegExpStatics| provides more flags we'll
125 : * need a different |RegExpShared|.
126 : */
127 551614 : RegExpStatics *res = cx->regExpStatics();
128 551614 : RegExpFlag origFlags = other->getFlags();
129 551614 : RegExpFlag staticsFlags = res->getFlags();
130 551614 : if ((origFlags & staticsFlags) != staticsFlags) {
131 450 : RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
132 450 : return build(other->getSource(), newFlags);
133 : }
134 :
135 1102328 : RegExpGuard g;
136 551164 : if (!other->getShared(cx, &g))
137 0 : return NULL;
138 :
139 551164 : return build(other->getSource(), *g);
140 : }
141 :
142 : /* MatchPairs */
143 :
144 : MatchPairs *
145 3528974 : MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount)
146 : {
147 3528974 : void *mem = alloc.alloc(calculateSize(backingPairCount));
148 3528974 : if (!mem)
149 0 : return NULL;
150 :
151 3528974 : return new (mem) MatchPairs(pairCount);
152 : }
153 :
154 : inline void
155 2566819 : MatchPairs::checkAgainst(size_t inputLength)
156 : {
157 : #if DEBUG
158 6984902 : for (size_t i = 0; i < pairCount(); ++i) {
159 4418083 : MatchPair p = pair(i);
160 4418083 : p.check();
161 4418083 : if (p.isUndefined())
162 984944 : continue;
163 3433139 : JS_ASSERT(size_t(p.limit) <= inputLength);
164 : }
165 : #endif
166 2566819 : }
167 :
168 : /* detail::RegExpCode */
169 :
170 : #if ENABLE_YARR_JIT
171 : void
172 92 : RegExpCode::reportYarrError(JSContext *cx, TokenStream *ts, ErrorCode error)
173 : {
174 92 : switch (error) {
175 : case JSC::Yarr::NoError:
176 0 : JS_NOT_REACHED("Called reportYarrError with value for no error");
177 : return;
178 : #define COMPILE_EMSG(__code, __msg) \
179 : case JSC::Yarr::__code: \
180 : if (ts) \
181 : ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, __msg); \
182 : else \
183 : JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, __msg); \
184 : return
185 0 : COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX);
186 0 : COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER);
187 9 : COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER);
188 0 : COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN);
189 0 : COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN);
190 0 : COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */
191 2 : COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE);
192 45 : COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE);
193 9 : COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE);
194 9 : COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER);
195 18 : COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH);
196 : #undef COMPILE_EMSG
197 : default:
198 0 : JS_NOT_REACHED("Unknown Yarr error code");
199 : }
200 : }
201 :
202 : #else /* !ENABLE_YARR_JIT */
203 :
204 : void
205 : RegExpCode::reportPCREError(JSContext *cx, int error)
206 : {
207 : #define REPORT(msg_) \
208 : JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, msg_); \
209 : return
210 : switch (error) {
211 : case -2: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
212 : case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred.");
213 : case 1: REPORT(JSMSG_TRAILING_SLASH);
214 : case 2: REPORT(JSMSG_TRAILING_SLASH);
215 : case 3: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
216 : case 4: REPORT(JSMSG_BAD_QUANTIFIER);
217 : case 5: REPORT(JSMSG_BAD_QUANTIFIER);
218 : case 6: REPORT(JSMSG_BAD_CLASS_RANGE);
219 : case 7: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
220 : case 8: REPORT(JSMSG_BAD_CLASS_RANGE);
221 : case 9: REPORT(JSMSG_BAD_QUANTIFIER);
222 : case 10: REPORT(JSMSG_UNMATCHED_RIGHT_PAREN);
223 : case 11: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
224 : case 12: REPORT(JSMSG_UNMATCHED_RIGHT_PAREN);
225 : case 13: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
226 : case 14: REPORT(JSMSG_MISSING_PAREN);
227 : case 15: REPORT(JSMSG_BAD_BACKREF);
228 : case 16: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
229 : case 17: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
230 : default:
231 : JS_NOT_REACHED("Precondition violation: unknown PCRE error code.");
232 : }
233 : #undef REPORT
234 : }
235 :
236 : #endif /* ENABLE_YARR_JIT */
237 :
238 : bool
239 58244 : RegExpCode::compile(JSContext *cx, JSLinearString &pattern, unsigned *parenCount, RegExpFlag flags)
240 : {
241 : #if ENABLE_YARR_JIT
242 : /* Parse the pattern. */
243 : ErrorCode yarrError;
244 : YarrPattern yarrPattern(pattern, bool(flags & IgnoreCaseFlag), bool(flags & MultilineFlag),
245 116488 : &yarrError);
246 58244 : if (yarrError) {
247 0 : reportYarrError(cx, NULL, yarrError);
248 0 : return false;
249 : }
250 58244 : *parenCount = yarrPattern.m_numSubpatterns;
251 :
252 : /*
253 : * The YARR JIT compiler attempts to compile the parsed pattern. If
254 : * it cannot, it informs us via |codeBlock.isFallBack()|, in which
255 : * case we have to bytecode compile it.
256 : */
257 :
258 : #ifdef JS_METHODJIT
259 58244 : if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
260 58226 : JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecutableAllocator(cx);
261 58226 : if (!execAlloc) {
262 0 : js_ReportOutOfMemory(cx);
263 0 : return false;
264 : }
265 :
266 58226 : JSGlobalData globalData(execAlloc);
267 58226 : jitCompile(yarrPattern, &globalData, codeBlock);
268 58226 : if (!codeBlock.isFallBack())
269 55269 : return true;
270 : }
271 : #endif
272 :
273 2975 : WTF::BumpPointerAllocator *bumpAlloc = cx->runtime->getBumpPointerAllocator(cx);
274 2975 : if (!bumpAlloc) {
275 0 : js_ReportOutOfMemory(cx);
276 0 : return false;
277 : }
278 :
279 2975 : codeBlock.setFallBack(true);
280 2975 : byteCode = byteCompile(yarrPattern, bumpAlloc).get();
281 2975 : return true;
282 : #else /* !defined(ENABLE_YARR_JIT) */
283 : int error = 0;
284 : compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
285 : ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
286 : multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
287 : parenCount, &error);
288 : if (error) {
289 : reportPCREError(cx, error);
290 : return false;
291 : }
292 : return true;
293 : #endif
294 : }
295 :
296 : RegExpRunStatus
297 3528974 : RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
298 : int *output, size_t outputCount)
299 : {
300 : int result;
301 : #if ENABLE_YARR_JIT
302 : (void) cx; /* Unused. */
303 3528974 : if (codeBlock.isFallBack())
304 67716 : result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
305 : else
306 3461258 : result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
307 : #else
308 : result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
309 : #endif
310 :
311 3528974 : if (result == -1)
312 962155 : return RegExpRunStatus_Success_NotFound;
313 :
314 : #if !ENABLE_YARR_JIT
315 : if (result < 0) {
316 : reportPCREError(cx, result);
317 : return RegExpRunStatus_Error;
318 : }
319 : #endif
320 :
321 2566819 : JS_ASSERT(result >= 0);
322 2566819 : return RegExpRunStatus_Success;
323 : }
324 :
325 : /* RegExpObject */
326 :
327 : static void
328 259642 : regexp_trace(JSTracer *trc, JSObject *obj)
329 : {
330 : /*
331 : * We have to check both conditions, since:
332 : * 1. During TraceRuntime, gcRunning is set
333 : * 2. When a write barrier executes, IS_GC_MARKING_TRACER is true.
334 : */
335 259642 : if (trc->runtime->gcRunning && IS_GC_MARKING_TRACER(trc))
336 258887 : obj->setPrivate(NULL);
337 259642 : }
338 :
339 : Class js::RegExpClass = {
340 : js_RegExp_str,
341 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
342 : JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
343 : JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
344 : JS_PropertyStub, /* addProperty */
345 : JS_PropertyStub, /* delProperty */
346 : JS_PropertyStub, /* getProperty */
347 : JS_StrictPropertyStub, /* setProperty */
348 : JS_EnumerateStub, /* enumerate */
349 : JS_ResolveStub,
350 : JS_ConvertStub,
351 : NULL, /* finalize */
352 : NULL, /* checkAccess */
353 : NULL, /* call */
354 : NULL, /* construct */
355 : NULL, /* hasInstance */
356 : regexp_trace
357 : };
358 :
359 58244 : RegExpShared::RegExpShared(JSRuntime *rt, RegExpFlag flags)
360 58244 : : parenCount(0), flags(flags), activeUseCount(0), gcNumberWhenUsed(rt->gcNumber)
361 58244 : {}
362 :
363 : RegExpObject *
364 6150 : RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
365 : RegExpFlag flags, TokenStream *tokenStream)
366 : {
367 6150 : RegExpFlag staticsFlags = res->getFlags();
368 6150 : return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
369 : }
370 :
371 : RegExpObject *
372 34063 : RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
373 : TokenStream *tokenStream)
374 : {
375 34063 : JSAtom *source = js_AtomizeChars(cx, chars, length);
376 34063 : if (!source)
377 0 : return NULL;
378 :
379 34063 : return createNoStatics(cx, source, flags, tokenStream);
380 : }
381 :
382 : RegExpObject *
383 38045 : RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
384 : TokenStream *tokenStream)
385 : {
386 38045 : if (!RegExpCode::checkSyntax(cx, tokenStream, source))
387 9 : return NULL;
388 :
389 38036 : RegExpObjectBuilder builder(cx);
390 38036 : return builder.build(source, flags);
391 : }
392 :
393 : bool
394 39836 : RegExpObject::createShared(JSContext *cx, RegExpGuard *g)
395 : {
396 39836 : JS_ASSERT(!maybeShared());
397 39836 : if (!cx->compartment->regExps.get(cx, getSource(), getFlags(), g))
398 0 : return false;
399 :
400 39836 : setShared(cx, **g);
401 39836 : return true;
402 : }
403 :
404 : Shape *
405 15519 : RegExpObject::assignInitialShape(JSContext *cx)
406 : {
407 15519 : JS_ASSERT(isRegExp());
408 15519 : JS_ASSERT(nativeEmpty());
409 :
410 : JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
411 : JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
412 : JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
413 : JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
414 : JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
415 : JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
416 :
417 : /* The lastIndex property alone is writable but non-configurable. */
418 31038 : if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lastIndexAtom),
419 15519 : LAST_INDEX_SLOT, JSPROP_PERMANENT))
420 : {
421 0 : return NULL;
422 : }
423 :
424 : /* Remaining instance properties are non-writable and non-configurable. */
425 77595 : if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.sourceAtom),
426 15519 : SOURCE_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
427 15519 : !addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.globalAtom),
428 15519 : GLOBAL_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
429 15519 : !addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.ignoreCaseAtom),
430 15519 : IGNORE_CASE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
431 15519 : !addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.multilineAtom),
432 15519 : MULTILINE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY))
433 : {
434 0 : return NULL;
435 : }
436 :
437 15519 : return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.stickyAtom),
438 15519 : STICKY_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
439 : }
440 :
441 : inline bool
442 599264 : RegExpObject::init(JSContext *cx, JSAtom *source, RegExpFlag flags)
443 : {
444 599264 : if (nativeEmpty()) {
445 15519 : if (isDelegate()) {
446 0 : if (!assignInitialShape(cx))
447 0 : return false;
448 : } else {
449 15519 : Shape *shape = assignInitialShape(cx);
450 15519 : if (!shape)
451 0 : return false;
452 15519 : EmptyShape::insertInitialShape(cx, shape, getProto());
453 : }
454 15519 : JS_ASSERT(!nativeEmpty());
455 : }
456 :
457 1198528 : DebugOnly<JSAtomState *> atomState = &cx->runtime->atomState;
458 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->lastIndexAtom))->slot() == LAST_INDEX_SLOT);
459 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot() == SOURCE_SLOT);
460 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot() == GLOBAL_FLAG_SLOT);
461 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot() ==
462 599264 : IGNORE_CASE_FLAG_SLOT);
463 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot() ==
464 599264 : MULTILINE_FLAG_SLOT);
465 599264 : JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot() == STICKY_FLAG_SLOT);
466 :
467 : /*
468 : * If this is a re-initialization with an existing RegExpShared, 'flags'
469 : * may not match getShared()->flags, so forget the RegExpShared.
470 : */
471 599264 : JSObject::setPrivate(NULL);
472 :
473 599264 : zeroLastIndex();
474 599264 : setSource(source);
475 599264 : setGlobal(flags & GlobalFlag);
476 599264 : setIgnoreCase(flags & IgnoreCaseFlag);
477 599264 : setMultiline(flags & MultilineFlag);
478 599264 : setSticky(flags & StickyFlag);
479 599264 : return true;
480 : }
481 :
482 : RegExpRunStatus
483 0 : RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
484 : MatchPairs **output)
485 : {
486 0 : RegExpGuard g;
487 0 : if (!getShared(cx, &g))
488 0 : return RegExpRunStatus_Error;
489 0 : return g->execute(cx, chars, length, lastIndex, output);
490 : }
491 :
492 : JSFlatString *
493 649 : RegExpObject::toString(JSContext *cx) const
494 : {
495 649 : JSAtom *src = getSource();
496 1298 : StringBuffer sb(cx);
497 649 : if (size_t len = src->length()) {
498 649 : if (!sb.reserve(len + 2))
499 0 : return NULL;
500 649 : sb.infallibleAppend('/');
501 649 : sb.infallibleAppend(src->chars(), len);
502 649 : sb.infallibleAppend('/');
503 : } else {
504 0 : if (!sb.append("/(?:)/"))
505 0 : return NULL;
506 : }
507 649 : if (global() && !sb.append('g'))
508 0 : return NULL;
509 649 : if (ignoreCase() && !sb.append('i'))
510 0 : return NULL;
511 649 : if (multiline() && !sb.append('m'))
512 0 : return NULL;
513 649 : if (sticky() && !sb.append('y'))
514 0 : return NULL;
515 :
516 649 : return sb.finishString();
517 : }
518 :
519 : /* RegExpShared */
520 :
521 : bool
522 58244 : RegExpShared::compile(JSContext *cx, JSAtom *source)
523 : {
524 58244 : if (!sticky())
525 58215 : return code.compile(cx, *source, &parenCount, getFlags());
526 :
527 : /*
528 : * The sticky case we implement hackily by prepending a caret onto the front
529 : * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
530 : */
531 : static const jschar prefix[] = {'^', '(', '?', ':'};
532 : static const jschar postfix[] = {')'};
533 :
534 : using mozilla::ArrayLength;
535 58 : StringBuffer sb(cx);
536 29 : if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
537 0 : return false;
538 29 : sb.infallibleAppend(prefix, ArrayLength(prefix));
539 29 : sb.infallibleAppend(source->chars(), source->length());
540 29 : sb.infallibleAppend(postfix, ArrayLength(postfix));
541 :
542 29 : JSAtom *fakeySource = sb.finishAtom();
543 29 : if (!fakeySource)
544 0 : return false;
545 29 : return code.compile(cx, *fakeySource, &parenCount, getFlags());
546 : }
547 :
548 : RegExpRunStatus
549 3528974 : RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
550 : MatchPairs **output)
551 : {
552 3528974 : const size_t origLength = length;
553 3528974 : size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
554 :
555 3528974 : LifoAlloc &alloc = cx->tempLifoAlloc();
556 3528974 : MatchPairs *matchPairs = MatchPairs::create(alloc, pairCount(), backingPairCount);
557 3528974 : if (!matchPairs)
558 0 : return RegExpRunStatus_Error;
559 :
560 : /*
561 : * |displacement| emulates sticky mode by matching from this offset
562 : * into the char buffer and subtracting the delta off at the end.
563 : */
564 3528974 : size_t start = *lastIndex;
565 3528974 : size_t displacement = 0;
566 :
567 3528974 : if (sticky()) {
568 54 : displacement = *lastIndex;
569 54 : chars += displacement;
570 54 : length -= displacement;
571 54 : start = 0;
572 : }
573 :
574 : RegExpRunStatus status = code.execute(cx, chars, length, start,
575 3528974 : matchPairs->buffer(), backingPairCount);
576 :
577 3528974 : switch (status) {
578 : case RegExpRunStatus_Error:
579 0 : return status;
580 : case RegExpRunStatus_Success_NotFound:
581 962155 : *output = matchPairs;
582 962155 : return status;
583 : default:
584 2566819 : JS_ASSERT(status == RegExpRunStatus_Success);
585 : }
586 :
587 2566819 : matchPairs->displace(displacement);
588 2566819 : matchPairs->checkAgainst(origLength);
589 :
590 2566819 : *lastIndex = matchPairs->pair(0).limit;
591 2566819 : *output = matchPairs;
592 :
593 2566819 : return RegExpRunStatus_Success;
594 : }
595 :
596 : /* RegExpCompartment */
597 :
598 45576 : RegExpCompartment::RegExpCompartment(JSRuntime *rt)
599 45576 : : map_(rt)
600 45576 : {}
601 :
602 91142 : RegExpCompartment::~RegExpCompartment()
603 : {
604 45571 : map_.empty();
605 45571 : }
606 :
607 : bool
608 45576 : RegExpCompartment::init(JSContext *cx)
609 : {
610 45576 : if (!map_.init()) {
611 0 : js_ReportOutOfMemory(cx);
612 0 : return false;
613 : }
614 :
615 45576 : return true;
616 : }
617 :
618 : void
619 122478 : RegExpCompartment::sweep(JSRuntime *rt)
620 : {
621 180805 : for (Map::Enum e(map_); !e.empty(); e.popFront()) {
622 : /* See the comment on RegExpShared lifetime in RegExpObject.h. */
623 58327 : RegExpShared *shared = e.front().value;
624 58327 : if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) {
625 58244 : Foreground::delete_(shared);
626 58244 : e.removeFront();
627 : }
628 : }
629 122478 : }
630 :
631 : inline bool
632 86595 : RegExpCompartment::get(JSContext *cx, JSAtom *keyAtom, JSAtom *source, RegExpFlag flags, Type type,
633 : RegExpGuard *g)
634 : {
635 86595 : Key key(keyAtom, flags, type);
636 86595 : Map::AddPtr p = map_.lookupForAdd(key);
637 86595 : if (p) {
638 28351 : g->init(*p->value);
639 28351 : return true;
640 : }
641 :
642 58244 : RegExpShared *shared = cx->runtime->new_<RegExpShared>(cx->runtime, flags);
643 58244 : if (!shared)
644 0 : goto error;
645 :
646 58244 : if (!shared->compile(cx, source))
647 0 : goto error;
648 :
649 : /* Re-lookup in case there was a GC. */
650 58244 : if (!map_.relookupOrAdd(p, key, shared))
651 0 : goto error;
652 :
653 : /*
654 : * Since 'error' deletes 'shared', only guard 'shared' on success. This is
655 : * safe since 'shared' cannot be deleted by GC until after the call to
656 : * map_.add() directly above.
657 : */
658 58244 : g->init(*shared);
659 58244 : return true;
660 :
661 : error:
662 0 : Foreground::delete_(shared);
663 0 : js_ReportOutOfMemory(cx);
664 0 : return false;
665 : }
666 :
667 : bool
668 86595 : RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
669 : {
670 86595 : return get(cx, source, source, flags, Normal, g);
671 : }
672 :
673 : bool
674 0 : RegExpCompartment::getHack(JSContext *cx, JSAtom *source, JSAtom *hackedSource, RegExpFlag flags,
675 : RegExpGuard *g)
676 : {
677 0 : return get(cx, source, hackedSource, flags, Hack, g);
678 : }
679 :
680 : bool
681 0 : RegExpCompartment::lookupHack(JSAtom *source, RegExpFlag flags, JSContext *cx, RegExpGuard *g)
682 : {
683 0 : if (Map::Ptr p = map_.lookup(Key(source, flags, Hack))) {
684 0 : g->init(*p->value);
685 0 : return true;
686 : }
687 0 : return false;
688 : }
689 :
690 : bool
691 46759 : RegExpCompartment::get(JSContext *cx, JSAtom *atom, JSString *opt, RegExpGuard *g)
692 : {
693 46759 : RegExpFlag flags = RegExpFlag(0);
694 46759 : if (opt && !ParseRegExpFlags(cx, opt, &flags))
695 0 : return false;
696 :
697 46759 : return get(cx, atom, flags, g);
698 : }
699 :
700 : /* Functions */
701 :
702 : JSObject *
703 551614 : js::CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
704 : {
705 551614 : JS_ASSERT(obj->isRegExp());
706 551614 : JS_ASSERT(proto->isRegExp());
707 :
708 551614 : RegExpObjectBuilder builder(cx);
709 551614 : return builder.clone(&obj->asRegExp(), &proto->asRegExp());
710 : }
711 :
712 : bool
713 27544 : js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
714 : {
715 27544 : size_t n = flagStr->length();
716 27544 : const jschar *s = flagStr->getChars(cx);
717 27544 : if (!s)
718 0 : return false;
719 :
720 27544 : *flagsOut = RegExpFlag(0);
721 55097 : for (size_t i = 0; i < n; i++) {
722 : #define HANDLE_FLAG(name_) \
723 : JS_BEGIN_MACRO \
724 : if (*flagsOut & (name_)) \
725 : goto bad_flag; \
726 : *flagsOut = RegExpFlag(*flagsOut | (name_)); \
727 : JS_END_MACRO
728 27553 : switch (s[i]) {
729 397 : case 'i': HANDLE_FLAG(IgnoreCaseFlag); break;
730 27029 : case 'g': HANDLE_FLAG(GlobalFlag); break;
731 127 : case 'm': HANDLE_FLAG(MultilineFlag); break;
732 0 : case 'y': HANDLE_FLAG(StickyFlag); break;
733 : default:
734 : bad_flag:
735 : {
736 : char charBuf[2];
737 0 : charBuf[0] = char(s[i]);
738 0 : charBuf[1] = '\0';
739 : JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
740 0 : JSMSG_BAD_REGEXP_FLAG, charBuf);
741 0 : return false;
742 : }
743 : }
744 : #undef HANDLE_FLAG
745 : }
746 27544 : return true;
747 : }
748 :
749 : #if JS_HAS_XDR
750 : # include "jsxdrapi.h"
751 :
752 : bool
753 18528 : js::XDRScriptRegExpObject(JSXDRState *xdr, HeapPtrObject *objp)
754 : {
755 18528 : JSAtom *source = 0;
756 18528 : uint32_t flagsword = 0;
757 :
758 18528 : if (xdr->mode == JSXDR_ENCODE) {
759 14546 : JS_ASSERT(objp);
760 14546 : RegExpObject &reobj = (*objp)->asRegExp();
761 14546 : source = reobj.getSource();
762 14546 : flagsword = reobj.getFlags();
763 : }
764 18528 : if (!js_XDRAtom(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
765 0 : return false;
766 18528 : if (xdr->mode == JSXDR_DECODE) {
767 3982 : RegExpFlag flags = RegExpFlag(flagsword);
768 3982 : RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, source, flags, NULL);
769 3982 : if (!reobj)
770 0 : return false;
771 :
772 3982 : if (!reobj->clearParent(xdr->cx))
773 0 : return false;
774 3982 : if (!reobj->clearType(xdr->cx))
775 0 : return false;
776 3982 : objp->init(reobj);
777 : }
778 18528 : return true;
779 : }
780 : #endif /* !JS_HAS_XDR */
781 :
|