| |
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| |
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
| |
3 * This Source Code Form is subject to the terms of the Mozilla Public |
| |
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
| |
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
6 |
| |
7 #include "vm/RegExpObject.h" |
| |
8 |
| |
9 #include "mozilla/MemoryReporting.h" |
| |
10 |
| |
11 #include "frontend/TokenStream.h" |
| |
12 #include "vm/MatchPairs.h" |
| |
13 #include "vm/RegExpStatics.h" |
| |
14 #include "vm/StringBuffer.h" |
| |
15 #include "vm/TraceLogging.h" |
| |
16 #include "vm/Xdr.h" |
| |
17 #include "yarr/YarrSyntaxChecker.h" |
| |
18 |
| |
19 #include "jsobjinlines.h" |
| |
20 |
| |
21 #include "vm/Shape-inl.h" |
| |
22 |
| |
23 using namespace js; |
| |
24 |
| |
25 using mozilla::DebugOnly; |
| |
26 using js::frontend::TokenStream; |
| |
27 |
| |
28 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD); |
| |
29 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB); |
| |
30 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE); |
| |
31 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY); |
| |
32 |
| |
33 /* RegExpObjectBuilder */ |
| |
34 |
| |
35 RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj) |
| |
36 : cx(cx), reobj_(cx, reobj) |
| |
37 {} |
| |
38 |
| |
39 bool |
| |
40 RegExpObjectBuilder::getOrCreate() |
| |
41 { |
| |
42 if (reobj_) |
| |
43 return true; |
| |
44 |
| |
45 // Note: RegExp objects are always allocated in the tenured heap. This is |
| |
46 // not strictly required, but simplifies embedding them in jitcode. |
| |
47 JSObject *obj = NewBuiltinClassInstance(cx, &RegExpObject::class_, TenuredObject); |
| |
48 if (!obj) |
| |
49 return false; |
| |
50 obj->initPrivate(nullptr); |
| |
51 |
| |
52 reobj_ = &obj->as<RegExpObject>(); |
| |
53 return true; |
| |
54 } |
| |
55 |
| |
56 bool |
| |
57 RegExpObjectBuilder::getOrCreateClone(HandleTypeObject type) |
| |
58 { |
| |
59 JS_ASSERT(!reobj_); |
| |
60 JS_ASSERT(type->clasp() == &RegExpObject::class_); |
| |
61 |
| |
62 JSObject *parent = type->proto().toObject()->getParent(); |
| |
63 |
| |
64 // Note: RegExp objects are always allocated in the tenured heap. This is |
| |
65 // not strictly required, but simplifies embedding them in jitcode. |
| |
66 JSObject *clone = NewObjectWithType(cx->asJSContext(), type, parent, TenuredObject); |
| |
67 if (!clone) |
| |
68 return false; |
| |
69 clone->initPrivate(nullptr); |
| |
70 |
| |
71 reobj_ = &clone->as<RegExpObject>(); |
| |
72 return true; |
| |
73 } |
| |
74 |
| |
75 RegExpObject * |
| |
76 RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared) |
| |
77 { |
| |
78 if (!getOrCreate()) |
| |
79 return nullptr; |
| |
80 |
| |
81 if (!reobj_->init(cx, source, shared.getFlags())) |
| |
82 return nullptr; |
| |
83 |
| |
84 reobj_->setShared(cx, shared); |
| |
85 return reobj_; |
| |
86 } |
| |
87 |
| |
88 RegExpObject * |
| |
89 RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags) |
| |
90 { |
| |
91 if (!getOrCreate()) |
| |
92 return nullptr; |
| |
93 |
| |
94 return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr; |
| |
95 } |
| |
96 |
| |
97 RegExpObject * |
| |
98 RegExpObjectBuilder::clone(Handle<RegExpObject *> other) |
| |
99 { |
| |
100 RootedTypeObject type(cx, other->type()); |
| |
101 if (!getOrCreateClone(type)) |
| |
102 return nullptr; |
| |
103 |
| |
104 /* |
| |
105 * Check that the RegExpShared for the original is okay to use in |
| |
106 * the clone -- if the |RegExpStatics| provides more flags we'll |
| |
107 * need a different |RegExpShared|. |
| |
108 */ |
| |
109 RegExpStatics *res = other->getProto()->getParent()->as<GlobalObject>().getRegExpStatics(); |
| |
110 RegExpFlag origFlags = other->getFlags(); |
| |
111 RegExpFlag staticsFlags = res->getFlags(); |
| |
112 if ((origFlags & staticsFlags) != staticsFlags) { |
| |
113 RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); |
| |
114 Rooted<JSAtom *> source(cx, other->getSource()); |
| |
115 return build(source, newFlags); |
| |
116 } |
| |
117 |
| |
118 RegExpGuard g(cx); |
| |
119 if (!other->getShared(cx, &g)) |
| |
120 return nullptr; |
| |
121 |
| |
122 Rooted<JSAtom *> source(cx, other->getSource()); |
| |
123 return build(source, *g); |
| |
124 } |
| |
125 |
| |
126 /* MatchPairs */ |
| |
127 |
| |
128 bool |
| |
129 MatchPairs::initArray(size_t pairCount) |
| |
130 { |
| |
131 JS_ASSERT(pairCount > 0); |
| |
132 |
| |
133 /* Guarantee adequate space in buffer. */ |
| |
134 if (!allocOrExpandArray(pairCount)) |
| |
135 return false; |
| |
136 |
| |
137 /* Initialize all MatchPair objects to invalid locations. */ |
| |
138 for (size_t i = 0; i < pairCount; i++) { |
| |
139 pairs_[i].start = -1; |
| |
140 pairs_[i].limit = -1; |
| |
141 } |
| |
142 |
| |
143 return true; |
| |
144 } |
| |
145 |
| |
146 bool |
| |
147 MatchPairs::initArrayFrom(MatchPairs ©From) |
| |
148 { |
| |
149 JS_ASSERT(copyFrom.pairCount() > 0); |
| |
150 |
| |
151 if (!allocOrExpandArray(copyFrom.pairCount())) |
| |
152 return false; |
| |
153 |
| |
154 for (size_t i = 0; i < pairCount_; i++) { |
| |
155 JS_ASSERT(copyFrom[i].check()); |
| |
156 pairs_[i].start = copyFrom[i].start; |
| |
157 pairs_[i].limit = copyFrom[i].limit; |
| |
158 } |
| |
159 |
| |
160 return true; |
| |
161 } |
| |
162 |
| |
163 void |
| |
164 MatchPairs::displace(size_t disp) |
| |
165 { |
| |
166 if (disp == 0) |
| |
167 return; |
| |
168 |
| |
169 for (size_t i = 0; i < pairCount_; i++) { |
| |
170 JS_ASSERT(pairs_[i].check()); |
| |
171 pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp; |
| |
172 pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp; |
| |
173 } |
| |
174 } |
| |
175 |
| |
176 bool |
| |
177 ScopedMatchPairs::allocOrExpandArray(size_t pairCount) |
| |
178 { |
| |
179 /* Array expansion is forbidden, but array reuse is acceptable. */ |
| |
180 if (pairCount_) { |
| |
181 JS_ASSERT(pairs_); |
| |
182 JS_ASSERT(pairCount_ == pairCount); |
| |
183 return true; |
| |
184 } |
| |
185 |
| |
186 JS_ASSERT(!pairs_); |
| |
187 pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount); |
| |
188 if (!pairs_) |
| |
189 return false; |
| |
190 |
| |
191 pairCount_ = pairCount; |
| |
192 return true; |
| |
193 } |
| |
194 |
| |
195 bool |
| |
196 VectorMatchPairs::allocOrExpandArray(size_t pairCount) |
| |
197 { |
| |
198 if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount)) |
| |
199 return false; |
| |
200 |
| |
201 pairs_ = &vec_[0]; |
| |
202 pairCount_ = pairCount; |
| |
203 return true; |
| |
204 } |
| |
205 |
| |
206 /* RegExpObject */ |
| |
207 |
| |
208 static void |
| |
209 regexp_trace(JSTracer *trc, JSObject *obj) |
| |
210 { |
| |
211 /* |
| |
212 * We have to check both conditions, since: |
| |
213 * 1. During TraceRuntime, isHeapBusy() is true |
| |
214 * 2. When a write barrier executes, IS_GC_MARKING_TRACER is true. |
| |
215 */ |
| |
216 if (trc->runtime()->isHeapBusy() && IS_GC_MARKING_TRACER(trc)) |
| |
217 obj->setPrivate(nullptr); |
| |
218 } |
| |
219 |
| |
220 const Class RegExpObject::class_ = { |
| |
221 js_RegExp_str, |
| |
222 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
| |
223 JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) | |
| |
224 JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), |
| |
225 JS_PropertyStub, /* addProperty */ |
| |
226 JS_DeletePropertyStub, /* delProperty */ |
| |
227 JS_PropertyStub, /* getProperty */ |
| |
228 JS_StrictPropertyStub, /* setProperty */ |
| |
229 JS_EnumerateStub, /* enumerate */ |
| |
230 JS_ResolveStub, |
| |
231 JS_ConvertStub, |
| |
232 nullptr, /* finalize */ |
| |
233 nullptr, /* call */ |
| |
234 nullptr, /* hasInstance */ |
| |
235 nullptr, /* construct */ |
| |
236 regexp_trace |
| |
237 }; |
| |
238 |
| |
239 RegExpObject * |
| |
240 RegExpObject::create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length, |
| |
241 RegExpFlag flags, TokenStream *tokenStream) |
| |
242 { |
| |
243 RegExpFlag staticsFlags = res->getFlags(); |
| |
244 return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream); |
| |
245 } |
| |
246 |
| |
247 RegExpObject * |
| |
248 RegExpObject::createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags, |
| |
249 TokenStream *tokenStream) |
| |
250 { |
| |
251 RootedAtom source(cx, AtomizeChars(cx, chars, length)); |
| |
252 if (!source) |
| |
253 return nullptr; |
| |
254 |
| |
255 return createNoStatics(cx, source, flags, tokenStream); |
| |
256 } |
| |
257 |
| |
258 RegExpObject * |
| |
259 RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags, |
| |
260 TokenStream *tokenStream) |
| |
261 { |
| |
262 if (!RegExpShared::checkSyntax(cx, tokenStream, source)) |
| |
263 return nullptr; |
| |
264 |
| |
265 RegExpObjectBuilder builder(cx); |
| |
266 return builder.build(source, flags); |
| |
267 } |
| |
268 |
| |
269 bool |
| |
270 RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g) |
| |
271 { |
| |
272 Rooted<RegExpObject*> self(cx, this); |
| |
273 |
| |
274 JS_ASSERT(!maybeShared()); |
| |
275 if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g)) |
| |
276 return false; |
| |
277 |
| |
278 self->setShared(cx, **g); |
| |
279 return true; |
| |
280 } |
| |
281 |
| |
282 Shape * |
| |
283 RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> self) |
| |
284 { |
| |
285 JS_ASSERT(self->nativeEmpty()); |
| |
286 |
| |
287 JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0); |
| |
288 JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1); |
| |
289 JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1); |
| |
290 JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1); |
| |
291 JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1); |
| |
292 JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1); |
| |
293 |
| |
294 /* The lastIndex property alone is writable but non-configurable. */ |
| |
295 if (!self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT)) |
| |
296 return nullptr; |
| |
297 |
| |
298 /* Remaining instance properties are non-writable and non-configurable. */ |
| |
299 unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY; |
| |
300 if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs)) |
| |
301 return nullptr; |
| |
302 if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs)) |
| |
303 return nullptr; |
| |
304 if (!self->addDataProperty(cx, cx->names().ignoreCase, IGNORE_CASE_FLAG_SLOT, attrs)) |
| |
305 return nullptr; |
| |
306 if (!self->addDataProperty(cx, cx->names().multiline, MULTILINE_FLAG_SLOT, attrs)) |
| |
307 return nullptr; |
| |
308 return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs); |
| |
309 } |
| |
310 |
| |
311 bool |
| |
312 RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags) |
| |
313 { |
| |
314 Rooted<RegExpObject *> self(cx, this); |
| |
315 |
| |
316 if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self)) |
| |
317 return false; |
| |
318 |
| |
319 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() == |
| |
320 LAST_INDEX_SLOT); |
| |
321 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() == |
| |
322 SOURCE_SLOT); |
| |
323 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() == |
| |
324 GLOBAL_FLAG_SLOT); |
| |
325 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() == |
| |
326 IGNORE_CASE_FLAG_SLOT); |
| |
327 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().multiline))->slot() == |
| |
328 MULTILINE_FLAG_SLOT); |
| |
329 JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().sticky))->slot() == |
| |
330 STICKY_FLAG_SLOT); |
| |
331 |
| |
332 /* |
| |
333 * If this is a re-initialization with an existing RegExpShared, 'flags' |
| |
334 * may not match getShared()->flags, so forget the RegExpShared. |
| |
335 */ |
| |
336 self->JSObject::setPrivate(nullptr); |
| |
337 |
| |
338 self->zeroLastIndex(); |
| |
339 self->setSource(source); |
| |
340 self->setGlobal(flags & GlobalFlag); |
| |
341 self->setIgnoreCase(flags & IgnoreCaseFlag); |
| |
342 self->setMultiline(flags & MultilineFlag); |
| |
343 self->setSticky(flags & StickyFlag); |
| |
344 return true; |
| |
345 } |
| |
346 |
| |
347 JSFlatString * |
| |
348 RegExpObject::toString(JSContext *cx) const |
| |
349 { |
| |
350 JSAtom *src = getSource(); |
| |
351 StringBuffer sb(cx); |
| |
352 if (size_t len = src->length()) { |
| |
353 if (!sb.reserve(len + 2)) |
| |
354 return nullptr; |
| |
355 sb.infallibleAppend('/'); |
| |
356 sb.infallibleAppend(src->chars(), len); |
| |
357 sb.infallibleAppend('/'); |
| |
358 } else { |
| |
359 if (!sb.append("/(?:)/")) |
| |
360 return nullptr; |
| |
361 } |
| |
362 if (global() && !sb.append('g')) |
| |
363 return nullptr; |
| |
364 if (ignoreCase() && !sb.append('i')) |
| |
365 return nullptr; |
| |
366 if (multiline() && !sb.append('m')) |
| |
367 return nullptr; |
| |
368 if (sticky() && !sb.append('y')) |
| |
369 return nullptr; |
| |
370 |
| |
371 return sb.finishString(); |
| |
372 } |
| |
373 |
| |
374 /* RegExpShared */ |
| |
375 |
| |
376 RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber) |
| |
377 : source(source), flags(flags), parenCount(0), |
| |
378 #if ENABLE_YARR_JIT |
| |
379 codeBlock(), |
| |
380 #endif |
| |
381 bytecode(nullptr), activeUseCount(0), gcNumberWhenUsed(gcNumber) |
| |
382 {} |
| |
383 |
| |
384 RegExpShared::~RegExpShared() |
| |
385 { |
| |
386 #if ENABLE_YARR_JIT |
| |
387 codeBlock.release(); |
| |
388 #endif |
| |
389 js_delete<BytecodePattern>(bytecode); |
| |
390 } |
| |
391 |
| |
392 void |
| |
393 RegExpShared::reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error) |
| |
394 { |
| |
395 switch (error) { |
| |
396 case JSC::Yarr::NoError: |
| |
397 MOZ_ASSUME_UNREACHABLE("Called reportYarrError with value for no error"); |
| |
398 #define COMPILE_EMSG(__code, __msg) \ |
| |
399 case JSC::Yarr::__code: \ |
| |
400 if (ts) \ |
| |
401 ts->reportError(__msg); \ |
| |
402 else \ |
| |
403 JS_ReportErrorFlagsAndNumberUC(cx->asJSContext(), \ |
| |
404 JSREPORT_ERROR, js_GetErrorMessage, nullptr, __msg); \ |
| |
405 return |
| |
406 COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX); |
| |
407 COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER); |
| |
408 COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER); |
| |
409 COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN); |
| |
410 COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN); |
| |
411 COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */ |
| |
412 COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE); |
| |
413 COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE); |
| |
414 COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE); |
| |
415 COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER); |
| |
416 COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH); |
| |
417 COMPILE_EMSG(RuntimeError, JSMSG_REGEXP_RUNTIME_ERROR); |
| |
418 #undef COMPILE_EMSG |
| |
419 default: |
| |
420 MOZ_ASSUME_UNREACHABLE("Unknown Yarr error code"); |
| |
421 } |
| |
422 } |
| |
423 |
| |
424 bool |
| |
425 RegExpShared::checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source) |
| |
426 { |
| |
427 ErrorCode error = JSC::Yarr::checkSyntax(*source); |
| |
428 if (error == JSC::Yarr::NoError) |
| |
429 return true; |
| |
430 |
| |
431 reportYarrError(cx, tokenStream, error); |
| |
432 return false; |
| |
433 } |
| |
434 |
| |
435 bool |
| |
436 RegExpShared::compile(JSContext *cx, bool matchOnly) |
| |
437 { |
| |
438 if (!sticky()) |
| |
439 return compile(cx, *source, matchOnly); |
| |
440 |
| |
441 /* |
| |
442 * The sticky case we implement hackily by prepending a caret onto the front |
| |
443 * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp. |
| |
444 */ |
| |
445 static const jschar prefix[] = {'^', '(', '?', ':'}; |
| |
446 static const jschar postfix[] = {')'}; |
| |
447 |
| |
448 using mozilla::ArrayLength; |
| |
449 StringBuffer sb(cx); |
| |
450 if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix))) |
| |
451 return false; |
| |
452 sb.infallibleAppend(prefix, ArrayLength(prefix)); |
| |
453 sb.infallibleAppend(source->chars(), source->length()); |
| |
454 sb.infallibleAppend(postfix, ArrayLength(postfix)); |
| |
455 |
| |
456 JSAtom *fakeySource = sb.finishAtom(); |
| |
457 if (!fakeySource) |
| |
458 return false; |
| |
459 |
| |
460 return compile(cx, *fakeySource, matchOnly); |
| |
461 } |
| |
462 |
| |
463 bool |
| |
464 RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly) |
| |
465 { |
| |
466 /* Parse the pattern. */ |
| |
467 ErrorCode yarrError; |
| |
468 YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError); |
| |
469 if (yarrError) { |
| |
470 reportYarrError(cx, nullptr, yarrError); |
| |
471 return false; |
| |
472 } |
| |
473 this->parenCount = yarrPattern.m_numSubpatterns; |
| |
474 |
| |
475 #if ENABLE_YARR_JIT |
| |
476 if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) { |
| |
477 JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx); |
| |
478 if (!execAlloc) |
| |
479 return false; |
| |
480 |
| |
481 JSGlobalData globalData(execAlloc); |
| |
482 YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly |
| |
483 : JSC::Yarr::IncludeSubpatterns; |
| |
484 |
| |
485 jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode); |
| |
486 |
| |
487 /* Unset iff the Yarr JIT compilation was successful. */ |
| |
488 if (!codeBlock.isFallBack()) |
| |
489 return true; |
| |
490 } |
| |
491 codeBlock.setFallBack(true); |
| |
492 #endif |
| |
493 |
| |
494 WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx); |
| |
495 if (!bumpAlloc) { |
| |
496 js_ReportOutOfMemory(cx); |
| |
497 return false; |
| |
498 } |
| |
499 |
| |
500 bytecode = byteCompile(yarrPattern, bumpAlloc).get(); |
| |
501 return true; |
| |
502 } |
| |
503 |
| |
504 bool |
| |
505 RegExpShared::compileIfNecessary(JSContext *cx) |
| |
506 { |
| |
507 if (hasCode() || hasBytecode()) |
| |
508 return true; |
| |
509 return compile(cx, false); |
| |
510 } |
| |
511 |
| |
512 bool |
| |
513 RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx) |
| |
514 { |
| |
515 if (hasMatchOnlyCode() || hasBytecode()) |
| |
516 return true; |
| |
517 return compile(cx, true); |
| |
518 } |
| |
519 |
| |
520 RegExpRunStatus |
| |
521 RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, |
| |
522 size_t *lastIndex, MatchPairs &matches) |
| |
523 { |
| |
524 TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); |
| |
525 |
| |
526 { |
| |
527 /* Compile the code at point-of-use. */ |
| |
528 AutoTraceLog logCompile(logger, TraceLogger::YarrCompile); |
| |
529 if (!compileIfNecessary(cx)) |
| |
530 return RegExpRunStatus_Error; |
| |
531 } |
| |
532 |
| |
533 /* Ensure sufficient memory for output vector. */ |
| |
534 if (!matches.initArray(pairCount())) |
| |
535 return RegExpRunStatus_Error; |
| |
536 |
| |
537 /* |
| |
538 * |displacement| emulates sticky mode by matching from this offset |
| |
539 * into the char buffer and subtracting the delta off at the end. |
| |
540 */ |
| |
541 size_t origLength = length; |
| |
542 size_t start = *lastIndex; |
| |
543 size_t displacement = 0; |
| |
544 |
| |
545 if (sticky()) { |
| |
546 displacement = start; |
| |
547 chars += displacement; |
| |
548 length -= displacement; |
| |
549 start = 0; |
| |
550 } |
| |
551 |
| |
552 unsigned *outputBuf = matches.rawBuf(); |
| |
553 unsigned result; |
| |
554 |
| |
555 #if ENABLE_YARR_JIT |
| |
556 if (codeBlock.isFallBack()) { |
| |
557 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); |
| |
558 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); |
| |
559 } else { |
| |
560 AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); |
| |
561 result = codeBlock.execute(chars, start, length, (int *)outputBuf).start; |
| |
562 } |
| |
563 #else |
| |
564 { |
| |
565 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); |
| |
566 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); |
| |
567 } |
| |
568 #endif |
| |
569 |
| |
570 if (result == JSC::Yarr::offsetError) { |
| |
571 reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError); |
| |
572 return RegExpRunStatus_Error; |
| |
573 } |
| |
574 |
| |
575 if (result == JSC::Yarr::offsetNoMatch) |
| |
576 return RegExpRunStatus_Success_NotFound; |
| |
577 |
| |
578 matches.displace(displacement); |
| |
579 matches.checkAgainst(origLength); |
| |
580 *lastIndex = matches[0].limit; |
| |
581 return RegExpRunStatus_Success; |
| |
582 } |
| |
583 |
| |
584 RegExpRunStatus |
| |
585 RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length, |
| |
586 size_t *lastIndex, MatchPair &match) |
| |
587 { |
| |
588 TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); |
| |
589 |
| |
590 { |
| |
591 /* Compile the code at point-of-use. */ |
| |
592 AutoTraceLog logCompile(logger, TraceLogger::YarrCompile); |
| |
593 if (!compileMatchOnlyIfNecessary(cx)) |
| |
594 return RegExpRunStatus_Error; |
| |
595 } |
| |
596 |
| |
597 #ifdef DEBUG |
| |
598 const size_t origLength = length; |
| |
599 #endif |
| |
600 size_t start = *lastIndex; |
| |
601 size_t displacement = 0; |
| |
602 |
| |
603 if (sticky()) { |
| |
604 displacement = start; |
| |
605 chars += displacement; |
| |
606 length -= displacement; |
| |
607 start = 0; |
| |
608 } |
| |
609 |
| |
610 #if ENABLE_YARR_JIT |
| |
611 if (!codeBlock.isFallBack()) { |
| |
612 AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); |
| |
613 MatchResult result = codeBlock.execute(chars, start, length); |
| |
614 if (!result) |
| |
615 return RegExpRunStatus_Success_NotFound; |
| |
616 |
| |
617 match = MatchPair(result.start, result.end); |
| |
618 match.displace(displacement); |
| |
619 *lastIndex = match.limit; |
| |
620 return RegExpRunStatus_Success; |
| |
621 } |
| |
622 #endif |
| |
623 |
| |
624 /* |
| |
625 * The JIT could not be used, so fall back to the Yarr interpreter. |
| |
626 * Unfortunately, the interpreter does not have a MatchOnly mode, so a |
| |
627 * temporary output vector must be provided. |
| |
628 */ |
| |
629 JS_ASSERT(hasBytecode()); |
| |
630 ScopedMatchPairs matches(&cx->tempLifoAlloc()); |
| |
631 if (!matches.initArray(pairCount())) |
| |
632 return RegExpRunStatus_Error; |
| |
633 |
| |
634 unsigned result; |
| |
635 { |
| |
636 AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); |
| |
637 result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf()); |
| |
638 } |
| |
639 |
| |
640 if (result == JSC::Yarr::offsetError) { |
| |
641 reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError); |
| |
642 return RegExpRunStatus_Error; |
| |
643 } |
| |
644 |
| |
645 if (result == JSC::Yarr::offsetNoMatch) |
| |
646 return RegExpRunStatus_Success_NotFound; |
| |
647 |
| |
648 match = MatchPair(result, matches[0].limit); |
| |
649 match.displace(displacement); |
| |
650 |
| |
651 #ifdef DEBUG |
| |
652 matches.displace(displacement); |
| |
653 matches.checkAgainst(origLength); |
| |
654 #endif |
| |
655 |
| |
656 *lastIndex = match.limit; |
| |
657 return RegExpRunStatus_Success; |
| |
658 } |
| |
659 |
| |
660 /* RegExpCompartment */ |
| |
661 |
| |
662 RegExpCompartment::RegExpCompartment(JSRuntime *rt) |
| |
663 : map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr) |
| |
664 {} |
| |
665 |
| |
666 RegExpCompartment::~RegExpCompartment() |
| |
667 { |
| |
668 JS_ASSERT_IF(map_.initialized(), map_.empty()); |
| |
669 JS_ASSERT_IF(inUse_.initialized(), inUse_.empty()); |
| |
670 } |
| |
671 |
| |
672 JSObject * |
| |
673 RegExpCompartment::createMatchResultTemplateObject(JSContext *cx) |
| |
674 { |
| |
675 JS_ASSERT(!matchResultTemplateObject_); |
| |
676 |
| |
677 /* Create template array object */ |
| |
678 RootedObject templateObject(cx, NewDenseUnallocatedArray(cx, 0, nullptr, TenuredObject)); |
| |
679 if (!templateObject) |
| |
680 return matchResultTemplateObject_; // = nullptr |
| |
681 |
| |
682 /* Set dummy index property */ |
| |
683 RootedValue index(cx, Int32Value(0)); |
| |
684 if (!baseops::DefineProperty(cx, templateObject, cx->names().index, index, |
| |
685 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) |
| |
686 return matchResultTemplateObject_; // = nullptr |
| |
687 |
| |
688 /* Set dummy input property */ |
| |
689 RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString)); |
| |
690 if (!baseops::DefineProperty(cx, templateObject, cx->names().input, inputVal, |
| |
691 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) |
| |
692 return matchResultTemplateObject_; // = nullptr |
| |
693 |
| |
694 // Make sure that the properties are in the right slots. |
| |
695 DebugOnly<Shape *> shape = templateObject->lastProperty(); |
| |
696 JS_ASSERT(shape->previous()->slot() == 0 && |
| |
697 shape->previous()->propidRef() == NameToId(cx->names().index)); |
| |
698 JS_ASSERT(shape->slot() == 1 && |
| |
699 shape->propidRef() == NameToId(cx->names().input)); |
| |
700 |
| |
701 matchResultTemplateObject_ = templateObject; |
| |
702 |
| |
703 return matchResultTemplateObject_; |
| |
704 } |
| |
705 |
| |
706 bool |
| |
707 RegExpCompartment::init(JSContext *cx) |
| |
708 { |
| |
709 if (!map_.init(0) || !inUse_.init(0)) { |
| |
710 if (cx) |
| |
711 js_ReportOutOfMemory(cx); |
| |
712 return false; |
| |
713 } |
| |
714 |
| |
715 return true; |
| |
716 } |
| |
717 |
| |
718 /* See the comment on RegExpShared lifetime in RegExpObject.h. */ |
| |
719 void |
| |
720 RegExpCompartment::sweep(JSRuntime *rt) |
| |
721 { |
| |
722 #ifdef DEBUG |
| |
723 for (Map::Range r = map_.all(); !r.empty(); r.popFront()) |
| |
724 JS_ASSERT(inUse_.has(r.front().value())); |
| |
725 #endif |
| |
726 |
| |
727 map_.clear(); |
| |
728 |
| |
729 for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) { |
| |
730 RegExpShared *shared = e.front(); |
| |
731 if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) { |
| |
732 js_delete(shared); |
| |
733 e.removeFront(); |
| |
734 } |
| |
735 } |
| |
736 |
| |
737 if (matchResultTemplateObject_ && |
| |
738 IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet())) |
| |
739 { |
| |
740 matchResultTemplateObject_ = nullptr; |
| |
741 } |
| |
742 } |
| |
743 |
| |
744 void |
| |
745 RegExpCompartment::clearTables() |
| |
746 { |
| |
747 JS_ASSERT(inUse_.empty()); |
| |
748 map_.clear(); |
| |
749 } |
| |
750 |
| |
751 bool |
| |
752 RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g) |
| |
753 { |
| |
754 Key key(source, flags); |
| |
755 Map::AddPtr p = map_.lookupForAdd(key); |
| |
756 if (p) { |
| |
757 g->init(*p->value()); |
| |
758 return true; |
| |
759 } |
| |
760 |
| |
761 uint64_t gcNumber = cx->zone()->gcNumber(); |
| |
762 ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber)); |
| |
763 if (!shared) |
| |
764 return false; |
| |
765 |
| |
766 /* Add to RegExpShared sharing hashmap. */ |
| |
767 if (!map_.add(p, key, shared)) { |
| |
768 js_ReportOutOfMemory(cx); |
| |
769 return false; |
| |
770 } |
| |
771 |
| |
772 /* Add to list of all RegExpShared objects in this RegExpCompartment. */ |
| |
773 if (!inUse_.put(shared)) { |
| |
774 map_.remove(key); |
| |
775 js_ReportOutOfMemory(cx); |
| |
776 return false; |
| |
777 } |
| |
778 |
| |
779 /* Since error deletes |shared|, only guard |shared| on success. */ |
| |
780 g->init(*shared.forget()); |
| |
781 return true; |
| |
782 } |
| |
783 |
| |
784 bool |
| |
785 RegExpCompartment::get(JSContext *cx, HandleAtom atom, JSString *opt, RegExpGuard *g) |
| |
786 { |
| |
787 RegExpFlag flags = RegExpFlag(0); |
| |
788 if (opt && !ParseRegExpFlags(cx, opt, &flags)) |
| |
789 return false; |
| |
790 |
| |
791 return get(cx, atom, flags, g); |
| |
792 } |
| |
793 |
| |
794 size_t |
| |
795 RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) |
| |
796 { |
| |
797 size_t n = 0; |
| |
798 n += map_.sizeOfExcludingThis(mallocSizeOf); |
| |
799 n += inUse_.sizeOfExcludingThis(mallocSizeOf); |
| |
800 return n; |
| |
801 } |
| |
802 |
| |
803 /* Functions */ |
| |
804 |
| |
805 JSObject * |
| |
806 js::CloneRegExpObject(JSContext *cx, JSObject *obj_) |
| |
807 { |
| |
808 RegExpObjectBuilder builder(cx); |
| |
809 Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>()); |
| |
810 JSObject *res = builder.clone(regex); |
| |
811 JS_ASSERT_IF(res, res->type() == regex->type()); |
| |
812 return res; |
| |
813 } |
| |
814 |
| |
815 bool |
| |
816 js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut) |
| |
817 { |
| |
818 size_t n = flagStr->length(); |
| |
819 const jschar *s = flagStr->getChars(cx); |
| |
820 if (!s) |
| |
821 return false; |
| |
822 |
| |
823 *flagsOut = RegExpFlag(0); |
| |
824 for (size_t i = 0; i < n; i++) { |
| |
825 #define HANDLE_FLAG(name_) \ |
| |
826 JS_BEGIN_MACRO \ |
| |
827 if (*flagsOut & (name_)) \ |
| |
828 goto bad_flag; \ |
| |
829 *flagsOut = RegExpFlag(*flagsOut | (name_)); \ |
| |
830 JS_END_MACRO |
| |
831 switch (s[i]) { |
| |
832 case 'i': HANDLE_FLAG(IgnoreCaseFlag); break; |
| |
833 case 'g': HANDLE_FLAG(GlobalFlag); break; |
| |
834 case 'm': HANDLE_FLAG(MultilineFlag); break; |
| |
835 case 'y': HANDLE_FLAG(StickyFlag); break; |
| |
836 default: |
| |
837 bad_flag: |
| |
838 { |
| |
839 char charBuf[2]; |
| |
840 charBuf[0] = char(s[i]); |
| |
841 charBuf[1] = '\0'; |
| |
842 JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr, |
| |
843 JSMSG_BAD_REGEXP_FLAG, charBuf); |
| |
844 return false; |
| |
845 } |
| |
846 } |
| |
847 #undef HANDLE_FLAG |
| |
848 } |
| |
849 return true; |
| |
850 } |
| |
851 |
| |
852 template<XDRMode mode> |
| |
853 bool |
| |
854 js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp) |
| |
855 { |
| |
856 /* NB: Keep this in sync with CloneScriptRegExpObject. */ |
| |
857 |
| |
858 RootedAtom source(xdr->cx()); |
| |
859 uint32_t flagsword = 0; |
| |
860 |
| |
861 if (mode == XDR_ENCODE) { |
| |
862 JS_ASSERT(objp); |
| |
863 RegExpObject &reobj = (*objp)->as<RegExpObject>(); |
| |
864 source = reobj.getSource(); |
| |
865 flagsword = reobj.getFlags(); |
| |
866 } |
| |
867 if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword)) |
| |
868 return false; |
| |
869 if (mode == XDR_DECODE) { |
| |
870 RegExpFlag flags = RegExpFlag(flagsword); |
| |
871 RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr); |
| |
872 if (!reobj) |
| |
873 return false; |
| |
874 |
| |
875 objp->init(reobj); |
| |
876 } |
| |
877 return true; |
| |
878 } |
| |
879 |
| |
880 template bool |
| |
881 js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp); |
| |
882 |
| |
883 template bool |
| |
884 js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp); |
| |
885 |
| |
886 JSObject * |
| |
887 js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj) |
| |
888 { |
| |
889 /* NB: Keep this in sync with XDRScriptRegExpObject. */ |
| |
890 |
| |
891 RootedAtom source(cx, reobj.getSource()); |
| |
892 return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr); |
| |
893 } |