tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */