js/src/vm/RegExpObject.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial