RegExpStatics.h (9794B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 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 #ifndef vm_RegExpStatics_h 8 #define vm_RegExpStatics_h 9 10 #include "js/RegExpFlags.h" 11 #include "vm/JSContext.h" 12 #include "vm/MatchPairs.h" 13 #include "vm/Runtime.h" 14 15 namespace js { 16 17 class RegExpStatics { 18 /* The latest RegExp output, set after execution. */ 19 VectorMatchPairs matches; 20 HeapPtr<JSLinearString*> matchesInput; 21 22 /* 23 * The previous RegExp input, used to resolve lazy state. 24 * A raw RegExpShared cannot be stored because it may be in 25 * a different compartment via evalcx(). 26 */ 27 HeapPtr<JSAtom*> lazySource; 28 JS::RegExpFlags lazyFlags; 29 size_t lazyIndex; 30 31 /* The latest RegExp input, set before execution. */ 32 HeapPtr<JSString*> pendingInput; 33 34 /* 35 * If non-zero, |matchesInput| and the |lazy*| fields may be used 36 * to replay the last executed RegExp, and |matches| is invalid. 37 */ 38 int32_t pendingLazyEvaluation; 39 40 public: 41 RegExpStatics() { clear(); } 42 static UniquePtr<RegExpStatics> create(JSContext* cx); 43 44 private: 45 bool executeLazy(JSContext* cx); 46 47 inline void checkInvariants(); 48 49 // Legacy RegExp static properties support. 50 private: 51 bool invalidated_ = false; 52 53 public: 54 bool isInvalidated() const { 55 if (!JS::Prefs::experimental_legacy_regexp()) { 56 return false; 57 } 58 return invalidated_; 59 } 60 61 inline void invalidate() { 62 if (JS::Prefs::experimental_legacy_regexp()) { 63 invalidated_ = true; 64 } 65 } 66 67 inline void clearInvalidation() { 68 if (JS::Prefs::experimental_legacy_regexp()) { 69 invalidated_ = false; 70 } 71 } 72 73 /* 74 * Check whether a match for pair |pairNum| occurred. If so, allocate and 75 * store the match string in |*out|; otherwise place |undefined| in |*out|. 76 */ 77 bool makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out); 78 bool createDependent(JSContext* cx, size_t start, size_t end, 79 MutableHandleValue out); 80 81 public: 82 /* Mutators. */ 83 inline bool updateFromMatchPairs(JSContext* cx, JSLinearString* input, 84 VectorMatchPairs& newPairs); 85 86 inline void clear(); 87 88 /* Corresponds to JSAPI functionality to set the pending RegExp input. */ 89 void reset(JSString* newInput) { 90 clear(); 91 pendingInput = newInput; 92 checkInvariants(); 93 } 94 95 inline void setPendingInput(JSString* newInput); 96 97 public: 98 void trace(JSTracer* trc) { 99 /* 100 * Changes to this function must also be reflected in 101 * RegExpStatics::AutoRooter::trace(). 102 */ 103 TraceNullableEdge(trc, &matchesInput, "res->matchesInput"); 104 TraceNullableEdge(trc, &lazySource, "res->lazySource"); 105 TraceNullableEdge(trc, &pendingInput, "res->pendingInput"); 106 } 107 108 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { 109 return mallocSizeOf(this) + matches.sizeOfExcludingThis(mallocSizeOf); 110 } 111 112 /* Value creators. */ 113 114 bool createPendingInput(JSContext* cx, MutableHandleValue out); 115 bool createLastMatch(JSContext* cx, MutableHandleValue out); 116 bool createLastParen(JSContext* cx, MutableHandleValue out); 117 bool createParen(JSContext* cx, size_t pairNum, MutableHandleValue out); 118 bool createLeftContext(JSContext* cx, MutableHandleValue out); 119 bool createRightContext(JSContext* cx, MutableHandleValue out); 120 121 static size_t offsetOfPendingInput() { 122 return offsetof(RegExpStatics, pendingInput); 123 } 124 125 static size_t offsetOfMatchesInput() { 126 return offsetof(RegExpStatics, matchesInput); 127 } 128 129 static size_t offsetOfLazySource() { 130 return offsetof(RegExpStatics, lazySource); 131 } 132 133 static size_t offsetOfLazyFlags() { 134 return offsetof(RegExpStatics, lazyFlags); 135 } 136 137 static size_t offsetOfLazyIndex() { 138 return offsetof(RegExpStatics, lazyIndex); 139 } 140 141 static size_t offsetOfPendingLazyEvaluation() { 142 return offsetof(RegExpStatics, pendingLazyEvaluation); 143 } 144 145 static size_t offsetOfInvalidated() { 146 return offsetof(RegExpStatics, invalidated_); 147 } 148 }; 149 150 inline bool RegExpStatics::createDependent(JSContext* cx, size_t start, 151 size_t end, MutableHandleValue out) { 152 /* Private function: caller must perform lazy evaluation. */ 153 MOZ_ASSERT(!pendingLazyEvaluation); 154 155 MOZ_ASSERT(start <= end); 156 MOZ_ASSERT(end <= matchesInput->length()); 157 JSString* str = NewDependentString(cx, matchesInput, start, end - start); 158 if (!str) { 159 return false; 160 } 161 out.setString(str); 162 return true; 163 } 164 165 inline bool RegExpStatics::createPendingInput(JSContext* cx, 166 MutableHandleValue out) { 167 /* Lazy evaluation need not be resolved to return the input. */ 168 out.setString(pendingInput ? pendingInput.get() 169 : cx->runtime()->emptyString.ref()); 170 return true; 171 } 172 173 inline bool RegExpStatics::makeMatch(JSContext* cx, size_t pairNum, 174 MutableHandleValue out) { 175 /* Private function: caller must perform lazy evaluation. */ 176 MOZ_ASSERT(!pendingLazyEvaluation); 177 178 if (matches.empty() || pairNum >= matches.pairCount() || 179 matches[pairNum].isUndefined()) { 180 out.setUndefined(); 181 return true; 182 } 183 184 const MatchPair& pair = matches[pairNum]; 185 return createDependent(cx, pair.start, pair.limit, out); 186 } 187 188 inline bool RegExpStatics::createLastMatch(JSContext* cx, 189 MutableHandleValue out) { 190 if (!executeLazy(cx)) { 191 return false; 192 } 193 194 if (isInvalidated()) { 195 out.setUndefined(); 196 return true; 197 } 198 199 return makeMatch(cx, 0, out); 200 } 201 202 inline bool RegExpStatics::createLastParen(JSContext* cx, 203 MutableHandleValue out) { 204 if (!executeLazy(cx)) { 205 return false; 206 } 207 208 if (isInvalidated()) { 209 out.setUndefined(); 210 return true; 211 } 212 213 if (matches.empty() || matches.pairCount() == 1) { 214 out.setString(cx->runtime()->emptyString); 215 return true; 216 } 217 const MatchPair& pair = matches[matches.pairCount() - 1]; 218 if (pair.start == -1) { 219 out.setString(cx->runtime()->emptyString); 220 return true; 221 } 222 MOZ_ASSERT(pair.start >= 0 && pair.limit >= 0); 223 MOZ_ASSERT(pair.limit >= pair.start); 224 return createDependent(cx, pair.start, pair.limit, out); 225 } 226 227 inline bool RegExpStatics::createParen(JSContext* cx, size_t pairNum, 228 MutableHandleValue out) { 229 MOZ_ASSERT(pairNum >= 1); 230 if (!executeLazy(cx)) { 231 return false; 232 } 233 234 if (isInvalidated()) { 235 out.setUndefined(); 236 return true; 237 } 238 239 if (matches.empty() || pairNum >= matches.pairCount()) { 240 out.setString(cx->runtime()->emptyString); 241 return true; 242 } 243 return makeMatch(cx, pairNum, out); 244 } 245 246 inline bool RegExpStatics::createLeftContext(JSContext* cx, 247 MutableHandleValue out) { 248 if (!executeLazy(cx)) { 249 return false; 250 } 251 252 if (isInvalidated()) { 253 out.setUndefined(); 254 return true; 255 } 256 257 if (matches.empty()) { 258 out.setString(cx->runtime()->emptyString); 259 return true; 260 } 261 if (matches[0].start < 0) { 262 out.setUndefined(); 263 return true; 264 } 265 return createDependent(cx, 0, matches[0].start, out); 266 } 267 268 inline bool RegExpStatics::createRightContext(JSContext* cx, 269 MutableHandleValue out) { 270 if (!executeLazy(cx)) { 271 return false; 272 } 273 274 if (isInvalidated()) { 275 out.setUndefined(); 276 return true; 277 } 278 279 if (matches.empty()) { 280 out.setString(cx->runtime()->emptyString); 281 return true; 282 } 283 if (matches[0].limit < 0) { 284 out.setUndefined(); 285 return true; 286 } 287 return createDependent(cx, matches[0].limit, matchesInput->length(), out); 288 } 289 290 inline bool RegExpStatics::updateFromMatchPairs(JSContext* cx, 291 JSLinearString* input, 292 VectorMatchPairs& newPairs) { 293 MOZ_ASSERT(input); 294 295 /* Unset all lazy state. */ 296 pendingLazyEvaluation = false; 297 this->lazySource = nullptr; 298 this->lazyIndex = size_t(-1); 299 300 BarrieredSetPair<JSString, JSLinearString>(cx->zone(), pendingInput, input, 301 matchesInput, input); 302 303 if (!matches.initArrayFrom(newPairs)) { 304 ReportOutOfMemory(cx); 305 return false; 306 } 307 clearInvalidation(); 308 return true; 309 } 310 311 inline void RegExpStatics::clear() { 312 matches.forgetArray(); 313 matchesInput = nullptr; 314 lazySource = nullptr; 315 lazyFlags = JS::RegExpFlag::NoFlags; 316 lazyIndex = size_t(-1); 317 pendingInput = nullptr; 318 pendingLazyEvaluation = false; 319 } 320 321 inline void RegExpStatics::setPendingInput(JSString* newInput) { 322 pendingInput = newInput; 323 } 324 325 inline void RegExpStatics::checkInvariants() { 326 #ifdef DEBUG 327 if (pendingLazyEvaluation) { 328 MOZ_ASSERT(lazySource); 329 MOZ_ASSERT(matchesInput); 330 MOZ_ASSERT(lazyIndex != size_t(-1)); 331 return; 332 } 333 334 if (matches.empty()) { 335 MOZ_ASSERT(!matchesInput); 336 return; 337 } 338 339 /* Pair count is non-zero, so there must be match pairs input. */ 340 MOZ_ASSERT(matchesInput); 341 size_t mpiLen = matchesInput->length(); 342 343 /* Both members of the first pair must be non-negative. */ 344 MOZ_ASSERT(!matches[0].isUndefined()); 345 MOZ_ASSERT(matches[0].limit >= 0); 346 347 /* Present pairs must be valid. */ 348 for (size_t i = 0; i < matches.pairCount(); i++) { 349 if (matches[i].isUndefined()) { 350 continue; 351 } 352 const MatchPair& pair = matches[i]; 353 MOZ_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && 354 pair.start >= 0); 355 } 356 #endif /* DEBUG */ 357 } 358 359 } /* namespace js */ 360 361 #endif /* vm_RegExpStatics_h */