RegExpObject.h (9409B)
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 /* JavaScript RegExp objects. */ 8 9 #ifndef vm_RegExpObject_h 10 #define vm_RegExpObject_h 11 12 #include "builtin/SelfHostingDefines.h" 13 #include "js/RegExpFlags.h" 14 #include "proxy/Proxy.h" 15 #include "vm/JSAtomState.h" 16 #include "vm/JSContext.h" 17 #include "vm/RegExpShared.h" 18 #include "vm/Shape.h" 19 20 /* 21 * JavaScript Regular Expressions 22 * 23 * There are several engine concepts associated with a single logical regexp: 24 * 25 * RegExpObject: 26 * The JS-visible object whose .[[Class]] equals "RegExp". 27 * RegExpShared: 28 * The compiled representation of the regexp (lazily created, cleared 29 * during some forms of GC). 30 * RegExpZone: 31 * Owns all RegExpShared instances in a zone. 32 */ 33 namespace js { 34 35 class GenericPrinter; 36 class JSONPrinter; 37 38 extern RegExpObject* RegExpAlloc(JSContext* cx, NewObjectKind newKind, 39 HandleObject proto = nullptr, 40 HandleObject newTarget = nullptr); 41 42 extern JSObject* CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex); 43 44 class RegExpObject : public NativeObject { 45 static const unsigned LAST_INDEX_SLOT = 0; 46 static const unsigned SOURCE_SLOT = 1; 47 static const unsigned FLAGS_SLOT = 2; 48 49 static_assert(RegExpObject::FLAGS_SLOT == REGEXP_FLAGS_SLOT, 50 "FLAGS_SLOT values should be in sync with self-hosted JS"); 51 52 static RegExpObject* create(JSContext* cx, Handle<JSAtom*> source, 53 NewObjectKind newKind); 54 55 public: 56 static const unsigned SHARED_SLOT = 3; 57 static const unsigned RESERVED_SLOTS = 4; 58 59 // This must match RESERVED_SLOTS. See assertions in CloneRegExpObject. 60 static constexpr gc::AllocKind AllocKind = gc::AllocKind::OBJECT4; 61 62 static const JSClass class_; 63 static const JSClass protoClass_; 64 65 static const size_t RegExpFlagsMask = JS::RegExpFlag::AllFlags; 66 static const size_t LegacyFeaturesEnabledBit = Bit(8); 67 68 static_assert((RegExpFlagsMask & LegacyFeaturesEnabledBit) == 0, 69 "LegacyFeaturesEnabledBit must not overlap"); 70 71 // The maximum number of pairs a MatchResult can have, without having to 72 // allocate a bigger MatchResult. 73 static const size_t MaxPairCount = 14; 74 75 template <typename CharT> 76 static RegExpObject* create(JSContext* cx, const CharT* chars, size_t length, 77 JS::RegExpFlags flags, NewObjectKind newKind, 78 HandleObject newTarget = nullptr); 79 80 // This variant assumes that the characters have already previously been 81 // syntax checked. 82 static RegExpObject* createSyntaxChecked(JSContext* cx, 83 Handle<JSAtom*> source, 84 JS::RegExpFlags flags, 85 NewObjectKind newKind, 86 HandleObject newTarget = nullptr); 87 88 static RegExpObject* create(JSContext* cx, Handle<JSAtom*> source, 89 JS::RegExpFlags flags, NewObjectKind newKind, 90 HandleObject newTarget = nullptr); 91 92 /* 93 * Compute the initial shape to associate with fresh RegExp objects, 94 * encoding their initial properties. Return the shape after 95 * changing |obj|'s last property to it. 96 */ 97 static SharedShape* assignInitialShape(JSContext* cx, 98 Handle<RegExpObject*> obj); 99 100 /* Accessors. */ 101 102 static constexpr size_t lastIndexSlot() { return LAST_INDEX_SLOT; } 103 104 static constexpr size_t offsetOfLastIndex() { 105 return getFixedSlotOffset(lastIndexSlot()); 106 } 107 108 static bool isInitialShape(RegExpObject* rx) { 109 // RegExpObject has a non-configurable lastIndex property, so there must be 110 // at least one property. Even though lastIndex is non-configurable, it can 111 // be made non-writable, so we have to check if it's still writable. 112 MOZ_ASSERT(!rx->empty()); 113 PropertyInfoWithKey prop = rx->getLastProperty(); 114 return prop.isDataProperty() && prop.slot() == LAST_INDEX_SLOT && 115 prop.writable(); 116 } 117 118 const Value& getLastIndex() const { return getReservedSlot(LAST_INDEX_SLOT); } 119 120 void setLastIndex(JSContext* cx, int32_t lastIndex) { 121 MOZ_ASSERT(lastIndex >= 0); 122 MOZ_ASSERT(lookupPure(cx->names().lastIndex)->writable(), 123 "can't infallibly set a non-writable lastIndex on a " 124 "RegExp that's been exposed to script"); 125 setReservedSlot(LAST_INDEX_SLOT, Int32Value(lastIndex)); 126 } 127 void zeroLastIndex(JSContext* cx) { setLastIndex(cx, 0); } 128 129 static JSLinearString* toString(JSContext* cx, Handle<RegExpObject*> obj); 130 131 JSAtom* getSource() const { 132 return &getReservedSlot(SOURCE_SLOT).toString()->asAtom(); 133 } 134 135 void setSource(JSAtom* source) { 136 setReservedSlot(SOURCE_SLOT, StringValue(source)); 137 } 138 139 /* Flags. */ 140 141 static constexpr size_t flagsSlot() { return FLAGS_SLOT; } 142 143 static constexpr size_t offsetOfFlags() { 144 return getFixedSlotOffset(flagsSlot()); 145 } 146 147 static constexpr size_t offsetOfShared() { 148 return getFixedSlotOffset(SHARED_SLOT); 149 } 150 151 JS::RegExpFlags getFlags() const { 152 Value flagsVal = getFixedSlot(FLAGS_SLOT); 153 uint32_t raw = flagsVal.toInt32(); 154 return JS::RegExpFlags(raw & RegExpFlagsMask); 155 } 156 157 void setFlags(JS::RegExpFlags flags) { 158 Value flagsVal = getFixedSlot(FLAGS_SLOT); 159 uint32_t raw = 0; 160 if (flagsVal.isInt32()) { 161 raw = static_cast<uint32_t>(flagsVal.toInt32()); 162 } 163 uint32_t newValue = flags.value() | (raw & ~RegExpFlagsMask); 164 setFixedSlot(FLAGS_SLOT, Int32Value(newValue)); 165 } 166 167 bool legacyFeaturesEnabled() const { 168 if (!JS::Prefs::experimental_legacy_regexp()) { 169 return false; 170 } 171 return (getFixedSlot(FLAGS_SLOT).toInt32() & LegacyFeaturesEnabledBit); 172 } 173 174 void setLegacyFeaturesEnabled(bool enabled) { 175 MOZ_ASSERT_IF(enabled, JS::Prefs::experimental_legacy_regexp()); 176 Value flagsVal = getFixedSlot(FLAGS_SLOT); 177 uint32_t raw = 0; 178 if (flagsVal.isInt32()) { 179 raw = static_cast<uint32_t>(flagsVal.toInt32()); 180 } 181 if (enabled) { 182 raw |= LegacyFeaturesEnabledBit; 183 } else { 184 raw &= ~LegacyFeaturesEnabledBit; 185 } 186 setFixedSlot(FLAGS_SLOT, Int32Value(raw)); 187 } 188 189 bool hasIndices() const { return getFlags().hasIndices(); } 190 bool global() const { return getFlags().global(); } 191 bool ignoreCase() const { return getFlags().ignoreCase(); } 192 bool multiline() const { return getFlags().multiline(); } 193 bool dotAll() const { return getFlags().dotAll(); } 194 bool unicode() const { return getFlags().unicode(); } 195 bool unicodeSets() const { return getFlags().unicodeSets(); } 196 bool sticky() const { return getFlags().sticky(); } 197 198 bool isGlobalOrSticky() const { 199 JS::RegExpFlags flags = getFlags(); 200 return flags.global() || flags.sticky(); 201 } 202 203 static RegExpShared* getShared(JSContext* cx, Handle<RegExpObject*> regexp); 204 205 bool hasShared() const { return !getFixedSlot(SHARED_SLOT).isUndefined(); } 206 207 RegExpShared* getShared() const { 208 return static_cast<RegExpShared*>(getFixedSlot(SHARED_SLOT).toGCThing()); 209 } 210 211 void setShared(RegExpShared* shared) { 212 MOZ_ASSERT(shared); 213 setFixedSlot(SHARED_SLOT, PrivateGCThingValue(shared)); 214 } 215 216 void clearShared() { setFixedSlot(SHARED_SLOT, UndefinedValue()); } 217 218 void initIgnoringLastIndex(JSAtom* source, JS::RegExpFlags flags); 219 220 // NOTE: This method is *only* safe to call on RegExps that haven't been 221 // exposed to script, because it requires that the "lastIndex" 222 // property be writable. 223 void initAndZeroLastIndex(JSAtom* source, JS::RegExpFlags flags, 224 JSContext* cx); 225 226 #if defined(DEBUG) || defined(JS_JITSPEW) 227 void dumpOwnFields(js::JSONPrinter& json) const; 228 void dumpOwnStringContent(js::GenericPrinter& out) const; 229 #endif 230 231 private: 232 /* 233 * Precondition: the syntax for |source| has already been validated. 234 * Side effect: sets the private field. 235 */ 236 static RegExpShared* createShared(JSContext* cx, 237 Handle<RegExpObject*> regexp); 238 239 /* Call setShared in preference to setPrivate. */ 240 void setPrivate(void* priv) = delete; 241 }; 242 243 /* 244 * Parse regexp flags. Report an error and return false if an invalid 245 * sequence of flags is encountered (repeat/invalid flag). 246 * 247 * N.B. flagStr must be rooted. 248 */ 249 bool ParseRegExpFlags(JSContext* cx, JSString* flagStr, 250 JS::RegExpFlags* flagsOut); 251 252 // Assuming GetBuiltinClass(obj) is ESClass::RegExp, return a RegExpShared for 253 // obj. 254 inline RegExpShared* RegExpToShared(JSContext* cx, HandleObject obj) { 255 if (obj->is<RegExpObject>()) { 256 return RegExpObject::getShared(cx, obj.as<RegExpObject>()); 257 } 258 259 return Proxy::regexp_toShared(cx, obj); 260 } 261 262 /* Escape all slashes and newlines in the given string. */ 263 extern JSLinearString* EscapeRegExpPattern(JSContext* cx, Handle<JSAtom*> src); 264 265 template <typename CharT> 266 extern bool HasRegExpMetaChars(const CharT* chars, size_t length); 267 268 extern bool StringHasRegExpMetaChars(const JSLinearString* str); 269 270 } /* namespace js */ 271 272 #endif /* vm_RegExpObject_h */