1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/vm/RegExpStatics.h Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,542 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#ifndef vm_RegExpStatics_h 1.11 +#define vm_RegExpStatics_h 1.12 + 1.13 +#include "gc/Marking.h" 1.14 +#include "vm/MatchPairs.h" 1.15 +#include "vm/RegExpObject.h" 1.16 +#include "vm/Runtime.h" 1.17 + 1.18 +namespace js { 1.19 + 1.20 +class GlobalObject; 1.21 + 1.22 +class RegExpStatics 1.23 +{ 1.24 + /* The latest RegExp output, set after execution. */ 1.25 + VectorMatchPairs matches; 1.26 + HeapPtr<JSLinearString> matchesInput; 1.27 + 1.28 + /* 1.29 + * The previous RegExp input, used to resolve lazy state. 1.30 + * A raw RegExpShared cannot be stored because it may be in 1.31 + * a different compartment via evalcx(). 1.32 + */ 1.33 + HeapPtr<JSAtom> lazySource; 1.34 + RegExpFlag lazyFlags; 1.35 + size_t lazyIndex; 1.36 + 1.37 + /* The latest RegExp input, set before execution. */ 1.38 + HeapPtr<JSString> pendingInput; 1.39 + RegExpFlag flags; 1.40 + 1.41 + /* 1.42 + * If true, |matchesInput| and the |lazy*| fields may be used 1.43 + * to replay the last executed RegExp, and |matches| is invalid. 1.44 + */ 1.45 + bool pendingLazyEvaluation; 1.46 + 1.47 + /* Linkage for preserving RegExpStatics during nested RegExp execution. */ 1.48 + RegExpStatics *bufferLink; 1.49 + bool copied; 1.50 + 1.51 + public: 1.52 + RegExpStatics() : bufferLink(nullptr), copied(false) { clear(); } 1.53 + static JSObject *create(JSContext *cx, GlobalObject *parent); 1.54 + 1.55 + private: 1.56 + bool executeLazy(JSContext *cx); 1.57 + 1.58 + inline void aboutToWrite(); 1.59 + inline void copyTo(RegExpStatics &dst); 1.60 + 1.61 + inline void restore(); 1.62 + bool save(JSContext *cx, RegExpStatics *buffer) { 1.63 + JS_ASSERT(!buffer->copied && !buffer->bufferLink); 1.64 + buffer->bufferLink = bufferLink; 1.65 + bufferLink = buffer; 1.66 + if (!buffer->matches.allocOrExpandArray(matches.length())) { 1.67 + js_ReportOutOfMemory(cx); 1.68 + return false; 1.69 + } 1.70 + return true; 1.71 + } 1.72 + 1.73 + inline void checkInvariants(); 1.74 + 1.75 + /* 1.76 + * Check whether the index at |checkValidIndex| is valid (>= 0). 1.77 + * If so, construct a string for it and place it in |*out|. 1.78 + * If not, place undefined in |*out|. 1.79 + */ 1.80 + bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, MutableHandleValue out); 1.81 + bool createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out); 1.82 + 1.83 + void markFlagsSet(JSContext *cx); 1.84 + 1.85 + struct InitBuffer {}; 1.86 + explicit RegExpStatics(InitBuffer) : bufferLink(nullptr), copied(false) {} 1.87 + 1.88 + friend class PreserveRegExpStatics; 1.89 + friend class AutoRegExpStaticsBuffer; 1.90 + 1.91 + public: 1.92 + /* Mutators. */ 1.93 + inline void updateLazily(JSContext *cx, JSLinearString *input, 1.94 + RegExpShared *shared, size_t lastIndex); 1.95 + inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs); 1.96 + 1.97 + void setMultiline(JSContext *cx, bool enabled) { 1.98 + aboutToWrite(); 1.99 + if (enabled) { 1.100 + flags = RegExpFlag(flags | MultilineFlag); 1.101 + markFlagsSet(cx); 1.102 + } else { 1.103 + flags = RegExpFlag(flags & ~MultilineFlag); 1.104 + } 1.105 + } 1.106 + 1.107 + inline void clear(); 1.108 + 1.109 + /* Corresponds to JSAPI functionality to set the pending RegExp input. */ 1.110 + void reset(JSContext *cx, JSString *newInput, bool newMultiline) { 1.111 + aboutToWrite(); 1.112 + clear(); 1.113 + pendingInput = newInput; 1.114 + setMultiline(cx, newMultiline); 1.115 + checkInvariants(); 1.116 + } 1.117 + 1.118 + inline void setPendingInput(JSString *newInput); 1.119 + 1.120 + public: 1.121 + /* Default match accessor. */ 1.122 + const MatchPairs &getMatches() const { 1.123 + /* Safe: only used by String methods, which do not set lazy mode. */ 1.124 + JS_ASSERT(!pendingLazyEvaluation); 1.125 + return matches; 1.126 + } 1.127 + 1.128 + JSString *getPendingInput() const { return pendingInput; } 1.129 + 1.130 + RegExpFlag getFlags() const { return flags; } 1.131 + bool multiline() const { return flags & MultilineFlag; } 1.132 + 1.133 + /* Returns whether results for a non-empty match are present. */ 1.134 + bool matched() const { 1.135 + /* Safe: only used by String methods, which do not set lazy mode. */ 1.136 + JS_ASSERT(!pendingLazyEvaluation); 1.137 + JS_ASSERT(matches.pairCount() > 0); 1.138 + return matches[0].limit - matches[0].start > 0; 1.139 + } 1.140 + 1.141 + void mark(JSTracer *trc) { 1.142 + /* 1.143 + * Changes to this function must also be reflected in 1.144 + * RegExpStatics::AutoRooter::trace(). 1.145 + */ 1.146 + if (matchesInput) 1.147 + MarkString(trc, &matchesInput, "res->matchesInput"); 1.148 + if (lazySource) 1.149 + MarkString(trc, &lazySource, "res->lazySource"); 1.150 + if (pendingInput) 1.151 + MarkString(trc, &pendingInput, "res->pendingInput"); 1.152 + } 1.153 + 1.154 + /* Value creators. */ 1.155 + 1.156 + bool createPendingInput(JSContext *cx, MutableHandleValue out); 1.157 + bool createLastMatch(JSContext *cx, MutableHandleValue out); 1.158 + bool createLastParen(JSContext *cx, MutableHandleValue out); 1.159 + bool createParen(JSContext *cx, size_t pairNum, MutableHandleValue out); 1.160 + bool createLeftContext(JSContext *cx, MutableHandleValue out); 1.161 + bool createRightContext(JSContext *cx, MutableHandleValue out); 1.162 + 1.163 + /* Infallible substring creators. */ 1.164 + 1.165 + void getParen(size_t pairNum, JSSubString *out) const; 1.166 + void getLastMatch(JSSubString *out) const; 1.167 + void getLastParen(JSSubString *out) const; 1.168 + void getLeftContext(JSSubString *out) const; 1.169 + void getRightContext(JSSubString *out) const; 1.170 +}; 1.171 + 1.172 +class AutoRegExpStaticsBuffer : private JS::CustomAutoRooter 1.173 +{ 1.174 + public: 1.175 + explicit AutoRegExpStaticsBuffer(JSContext *cx 1.176 + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 1.177 + : CustomAutoRooter(cx), statics(RegExpStatics::InitBuffer()) 1.178 + { 1.179 + MOZ_GUARD_OBJECT_NOTIFIER_INIT; 1.180 + } 1.181 + 1.182 + RegExpStatics& getStatics() { return statics; } 1.183 + 1.184 + private: 1.185 + virtual void trace(JSTracer *trc) { 1.186 + if (statics.matchesInput) { 1.187 + MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.matchesInput), 1.188 + "AutoRegExpStaticsBuffer matchesInput"); 1.189 + } 1.190 + if (statics.lazySource) { 1.191 + MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.lazySource), 1.192 + "AutoRegExpStaticsBuffer lazySource"); 1.193 + } 1.194 + if (statics.pendingInput) { 1.195 + MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.pendingInput), 1.196 + "AutoRegExpStaticsBuffer pendingInput"); 1.197 + } 1.198 + } 1.199 + 1.200 + RegExpStatics statics; 1.201 + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 1.202 +}; 1.203 + 1.204 +class PreserveRegExpStatics 1.205 +{ 1.206 + RegExpStatics * const original; 1.207 + AutoRegExpStaticsBuffer buffer; 1.208 + 1.209 + public: 1.210 + explicit PreserveRegExpStatics(JSContext *cx, RegExpStatics *original) 1.211 + : original(original), 1.212 + buffer(cx) 1.213 + {} 1.214 + 1.215 + bool init(JSContext *cx) { 1.216 + return original->save(cx, &buffer.getStatics()); 1.217 + } 1.218 + 1.219 + ~PreserveRegExpStatics() { original->restore(); } 1.220 +}; 1.221 + 1.222 +inline bool 1.223 +RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out) 1.224 +{ 1.225 + /* Private function: caller must perform lazy evaluation. */ 1.226 + JS_ASSERT(!pendingLazyEvaluation); 1.227 + 1.228 + JS_ASSERT(start <= end); 1.229 + JS_ASSERT(end <= matchesInput->length()); 1.230 + JSString *str = js_NewDependentString(cx, matchesInput, start, end - start); 1.231 + if (!str) 1.232 + return false; 1.233 + out.setString(str); 1.234 + return true; 1.235 +} 1.236 + 1.237 +inline bool 1.238 +RegExpStatics::createPendingInput(JSContext *cx, MutableHandleValue out) 1.239 +{ 1.240 + /* Lazy evaluation need not be resolved to return the input. */ 1.241 + out.setString(pendingInput ? pendingInput.get() : cx->runtime()->emptyString); 1.242 + return true; 1.243 +} 1.244 + 1.245 +inline bool 1.246 +RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, 1.247 + MutableHandleValue out) 1.248 +{ 1.249 + /* Private function: caller must perform lazy evaluation. */ 1.250 + JS_ASSERT(!pendingLazyEvaluation); 1.251 + 1.252 + bool checkWhich = checkValidIndex % 2; 1.253 + size_t checkPair = checkValidIndex / 2; 1.254 + 1.255 + if (matches.empty() || checkPair >= matches.pairCount() || 1.256 + (checkWhich ? matches[checkPair].limit : matches[checkPair].start) < 0) 1.257 + { 1.258 + out.setString(cx->runtime()->emptyString); 1.259 + return true; 1.260 + } 1.261 + const MatchPair &pair = matches[pairNum]; 1.262 + return createDependent(cx, pair.start, pair.limit, out); 1.263 +} 1.264 + 1.265 +inline bool 1.266 +RegExpStatics::createLastMatch(JSContext *cx, MutableHandleValue out) 1.267 +{ 1.268 + if (!executeLazy(cx)) 1.269 + return false; 1.270 + return makeMatch(cx, 0, 0, out); 1.271 +} 1.272 + 1.273 +inline bool 1.274 +RegExpStatics::createLastParen(JSContext *cx, MutableHandleValue out) 1.275 +{ 1.276 + if (!executeLazy(cx)) 1.277 + return false; 1.278 + 1.279 + if (matches.empty() || matches.pairCount() == 1) { 1.280 + out.setString(cx->runtime()->emptyString); 1.281 + return true; 1.282 + } 1.283 + const MatchPair &pair = matches[matches.pairCount() - 1]; 1.284 + if (pair.start == -1) { 1.285 + out.setString(cx->runtime()->emptyString); 1.286 + return true; 1.287 + } 1.288 + JS_ASSERT(pair.start >= 0 && pair.limit >= 0); 1.289 + JS_ASSERT(pair.limit >= pair.start); 1.290 + return createDependent(cx, pair.start, pair.limit, out); 1.291 +} 1.292 + 1.293 +inline bool 1.294 +RegExpStatics::createParen(JSContext *cx, size_t pairNum, MutableHandleValue out) 1.295 +{ 1.296 + JS_ASSERT(pairNum >= 1); 1.297 + if (!executeLazy(cx)) 1.298 + return false; 1.299 + 1.300 + if (matches.empty() || pairNum >= matches.pairCount()) { 1.301 + out.setString(cx->runtime()->emptyString); 1.302 + return true; 1.303 + } 1.304 + return makeMatch(cx, pairNum * 2, pairNum, out); 1.305 +} 1.306 + 1.307 +inline bool 1.308 +RegExpStatics::createLeftContext(JSContext *cx, MutableHandleValue out) 1.309 +{ 1.310 + if (!executeLazy(cx)) 1.311 + return false; 1.312 + 1.313 + if (matches.empty()) { 1.314 + out.setString(cx->runtime()->emptyString); 1.315 + return true; 1.316 + } 1.317 + if (matches[0].start < 0) { 1.318 + out.setUndefined(); 1.319 + return true; 1.320 + } 1.321 + return createDependent(cx, 0, matches[0].start, out); 1.322 +} 1.323 + 1.324 +inline bool 1.325 +RegExpStatics::createRightContext(JSContext *cx, MutableHandleValue out) 1.326 +{ 1.327 + if (!executeLazy(cx)) 1.328 + return false; 1.329 + 1.330 + if (matches.empty()) { 1.331 + out.setString(cx->runtime()->emptyString); 1.332 + return true; 1.333 + } 1.334 + if (matches[0].limit < 0) { 1.335 + out.setUndefined(); 1.336 + return true; 1.337 + } 1.338 + return createDependent(cx, matches[0].limit, matchesInput->length(), out); 1.339 +} 1.340 + 1.341 +inline void 1.342 +RegExpStatics::getParen(size_t pairNum, JSSubString *out) const 1.343 +{ 1.344 + JS_ASSERT(!pendingLazyEvaluation); 1.345 + 1.346 + JS_ASSERT(pairNum >= 1 && pairNum < matches.pairCount()); 1.347 + const MatchPair &pair = matches[pairNum]; 1.348 + if (pair.isUndefined()) { 1.349 + *out = js_EmptySubString; 1.350 + return; 1.351 + } 1.352 + out->chars = matchesInput->chars() + pair.start; 1.353 + out->length = pair.length(); 1.354 +} 1.355 + 1.356 +inline void 1.357 +RegExpStatics::getLastMatch(JSSubString *out) const 1.358 +{ 1.359 + JS_ASSERT(!pendingLazyEvaluation); 1.360 + 1.361 + if (matches.empty()) { 1.362 + *out = js_EmptySubString; 1.363 + return; 1.364 + } 1.365 + JS_ASSERT(matchesInput); 1.366 + out->chars = matchesInput->chars() + matches[0].start; 1.367 + JS_ASSERT(matches[0].limit >= matches[0].start); 1.368 + out->length = matches[0].length(); 1.369 +} 1.370 + 1.371 +inline void 1.372 +RegExpStatics::getLastParen(JSSubString *out) const 1.373 +{ 1.374 + JS_ASSERT(!pendingLazyEvaluation); 1.375 + 1.376 + /* Note: the first pair is the whole match. */ 1.377 + if (matches.empty() || matches.pairCount() == 1) { 1.378 + *out = js_EmptySubString; 1.379 + return; 1.380 + } 1.381 + getParen(matches.parenCount(), out); 1.382 +} 1.383 + 1.384 +inline void 1.385 +RegExpStatics::getLeftContext(JSSubString *out) const 1.386 +{ 1.387 + JS_ASSERT(!pendingLazyEvaluation); 1.388 + 1.389 + if (matches.empty()) { 1.390 + *out = js_EmptySubString; 1.391 + return; 1.392 + } 1.393 + out->chars = matchesInput->chars(); 1.394 + out->length = matches[0].start; 1.395 +} 1.396 + 1.397 +inline void 1.398 +RegExpStatics::getRightContext(JSSubString *out) const 1.399 +{ 1.400 + JS_ASSERT(!pendingLazyEvaluation); 1.401 + 1.402 + if (matches.empty()) { 1.403 + *out = js_EmptySubString; 1.404 + return; 1.405 + } 1.406 + out->chars = matchesInput->chars() + matches[0].limit; 1.407 + JS_ASSERT(matches[0].limit <= int(matchesInput->length())); 1.408 + out->length = matchesInput->length() - matches[0].limit; 1.409 +} 1.410 + 1.411 +inline void 1.412 +RegExpStatics::copyTo(RegExpStatics &dst) 1.413 +{ 1.414 + /* Destination buffer has already been reserved by save(). */ 1.415 + if (!pendingLazyEvaluation) 1.416 + dst.matches.initArrayFrom(matches); 1.417 + 1.418 + dst.matchesInput = matchesInput; 1.419 + dst.lazySource = lazySource; 1.420 + dst.lazyFlags = lazyFlags; 1.421 + dst.lazyIndex = lazyIndex; 1.422 + dst.pendingInput = pendingInput; 1.423 + dst.flags = flags; 1.424 + dst.pendingLazyEvaluation = pendingLazyEvaluation; 1.425 + 1.426 + JS_ASSERT_IF(pendingLazyEvaluation, lazySource); 1.427 + JS_ASSERT_IF(pendingLazyEvaluation, matchesInput); 1.428 +} 1.429 + 1.430 +inline void 1.431 +RegExpStatics::aboutToWrite() 1.432 +{ 1.433 + if (bufferLink && !bufferLink->copied) { 1.434 + copyTo(*bufferLink); 1.435 + bufferLink->copied = true; 1.436 + } 1.437 +} 1.438 + 1.439 +inline void 1.440 +RegExpStatics::restore() 1.441 +{ 1.442 + if (bufferLink->copied) 1.443 + bufferLink->copyTo(*this); 1.444 + bufferLink = bufferLink->bufferLink; 1.445 +} 1.446 + 1.447 +inline void 1.448 +RegExpStatics::updateLazily(JSContext *cx, JSLinearString *input, 1.449 + RegExpShared *shared, size_t lastIndex) 1.450 +{ 1.451 + JS_ASSERT(input && shared); 1.452 + aboutToWrite(); 1.453 + 1.454 + BarrieredSetPair<JSString, JSLinearString>(cx->zone(), 1.455 + pendingInput, input, 1.456 + matchesInput, input); 1.457 + 1.458 + lazySource = shared->source; 1.459 + lazyFlags = shared->flags; 1.460 + lazyIndex = lastIndex; 1.461 + pendingLazyEvaluation = true; 1.462 +} 1.463 + 1.464 +inline bool 1.465 +RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs) 1.466 +{ 1.467 + JS_ASSERT(input); 1.468 + aboutToWrite(); 1.469 + 1.470 + /* Unset all lazy state. */ 1.471 + pendingLazyEvaluation = false; 1.472 + this->lazySource = nullptr; 1.473 + this->lazyIndex = size_t(-1); 1.474 + 1.475 + BarrieredSetPair<JSString, JSLinearString>(cx->zone(), 1.476 + pendingInput, input, 1.477 + matchesInput, input); 1.478 + 1.479 + if (!matches.initArrayFrom(newPairs)) { 1.480 + js_ReportOutOfMemory(cx); 1.481 + return false; 1.482 + } 1.483 + 1.484 + return true; 1.485 +} 1.486 + 1.487 +inline void 1.488 +RegExpStatics::clear() 1.489 +{ 1.490 + aboutToWrite(); 1.491 + 1.492 + matches.forgetArray(); 1.493 + matchesInput = nullptr; 1.494 + lazySource = nullptr; 1.495 + lazyFlags = RegExpFlag(0); 1.496 + lazyIndex = size_t(-1); 1.497 + pendingInput = nullptr; 1.498 + flags = RegExpFlag(0); 1.499 + pendingLazyEvaluation = false; 1.500 +} 1.501 + 1.502 +inline void 1.503 +RegExpStatics::setPendingInput(JSString *newInput) 1.504 +{ 1.505 + aboutToWrite(); 1.506 + pendingInput = newInput; 1.507 +} 1.508 + 1.509 +inline void 1.510 +RegExpStatics::checkInvariants() 1.511 +{ 1.512 +#ifdef DEBUG 1.513 + if (pendingLazyEvaluation) { 1.514 + JS_ASSERT(lazySource); 1.515 + JS_ASSERT(matchesInput); 1.516 + JS_ASSERT(lazyIndex != size_t(-1)); 1.517 + return; 1.518 + } 1.519 + 1.520 + if (matches.empty()) { 1.521 + JS_ASSERT(!matchesInput); 1.522 + return; 1.523 + } 1.524 + 1.525 + /* Pair count is non-zero, so there must be match pairs input. */ 1.526 + JS_ASSERT(matchesInput); 1.527 + size_t mpiLen = matchesInput->length(); 1.528 + 1.529 + /* Both members of the first pair must be non-negative. */ 1.530 + JS_ASSERT(!matches[0].isUndefined()); 1.531 + JS_ASSERT(matches[0].limit >= 0); 1.532 + 1.533 + /* Present pairs must be valid. */ 1.534 + for (size_t i = 0; i < matches.pairCount(); i++) { 1.535 + if (matches[i].isUndefined()) 1.536 + continue; 1.537 + const MatchPair &pair = matches[i]; 1.538 + JS_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0); 1.539 + } 1.540 +#endif /* DEBUG */ 1.541 +} 1.542 + 1.543 +} /* namespace js */ 1.544 + 1.545 +#endif /* vm_RegExpStatics_h */