Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 "mozilla/Attributes.h"
13 : #include "mozilla/MemoryReporting.h"
14 :
15 : #include "builtin/SelfHostingDefines.h"
16 : #include "gc/Marking.h"
17 : #include "js/GCHashTable.h"
18 : #include "proxy/Proxy.h"
19 : #include "vm/ArrayObject.h"
20 : #include "vm/JSContext.h"
21 : #include "vm/RegExpShared.h"
22 : #include "vm/Shape.h"
23 :
24 : /*
25 : * JavaScript Regular Expressions
26 : *
27 : * There are several engine concepts associated with a single logical regexp:
28 : *
29 : * RegExpObject:
30 : * The JS-visible object whose .[[Class]] equals "RegExp".
31 : * RegExpShared:
32 : * The compiled representation of the regexp (lazily created, cleared
33 : * during some forms of GC).
34 : * RegExpZone:
35 : * Owns all RegExpShared instances in a zone.
36 : */
37 : namespace js {
38 :
39 : struct MatchPair;
40 : class MatchPairs;
41 : class RegExpStatics;
42 :
43 : namespace frontend { class TokenStreamAnyChars; }
44 :
45 : extern RegExpObject*
46 : RegExpAlloc(JSContext* cx, NewObjectKind newKind, HandleObject proto = nullptr);
47 :
48 : extern JSObject*
49 : CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex);
50 :
51 : class RegExpObject : public NativeObject
52 : {
53 : static const unsigned LAST_INDEX_SLOT = 0;
54 : static const unsigned SOURCE_SLOT = 1;
55 : static const unsigned FLAGS_SLOT = 2;
56 :
57 : static_assert(RegExpObject::FLAGS_SLOT == REGEXP_FLAGS_SLOT,
58 : "FLAGS_SLOT values should be in sync with self-hosted JS");
59 :
60 : public:
61 : static const unsigned RESERVED_SLOTS = 3;
62 : static const unsigned PRIVATE_SLOT = 3;
63 :
64 : static const Class class_;
65 : static const Class protoClass_;
66 :
67 : // The maximum number of pairs a MatchResult can have, without having to
68 : // allocate a bigger MatchResult.
69 : static const size_t MaxPairCount = 14;
70 :
71 : template<typename CharT>
72 : static RegExpObject*
73 : create(JSContext* cx, const CharT* chars, size_t length, RegExpFlag flags, LifoAlloc& alloc,
74 : NewObjectKind newKind);
75 :
76 : template<typename CharT>
77 : static RegExpObject*
78 : create(JSContext* cx, const CharT* chars, size_t length, RegExpFlag flags,
79 : frontend::TokenStreamAnyChars& ts, LifoAlloc& alloc, NewObjectKind kind);
80 :
81 : static RegExpObject*
82 : create(JSContext* cx, HandleAtom atom, RegExpFlag flags, LifoAlloc& alloc,
83 : NewObjectKind newKind);
84 :
85 : static RegExpObject*
86 : create(JSContext* cx, HandleAtom atom, RegExpFlag flags, frontend::TokenStreamAnyChars& ts,
87 : LifoAlloc& alloc, NewObjectKind newKind);
88 :
89 : /*
90 : * Compute the initial shape to associate with fresh RegExp objects,
91 : * encoding their initial properties. Return the shape after
92 : * changing |obj|'s last property to it.
93 : */
94 : static Shape*
95 : assignInitialShape(JSContext* cx, Handle<RegExpObject*> obj);
96 :
97 : /* Accessors. */
98 :
99 : static unsigned lastIndexSlot() { return LAST_INDEX_SLOT; }
100 :
101 5 : static bool isInitialShape(RegExpObject* rx) {
102 5 : Shape* shape = rx->lastProperty();
103 0 : if (shape->isEmptyShape() || !shape->isDataProperty())
104 : return false;
105 0 : if (shape->maybeSlot() != LAST_INDEX_SLOT)
106 : return false;
107 0 : return true;
108 : }
109 :
110 : const Value& getLastIndex() const { return getSlot(LAST_INDEX_SLOT); }
111 :
112 : void setLastIndex(double d) {
113 : setSlot(LAST_INDEX_SLOT, NumberValue(d));
114 : }
115 :
116 2282 : void zeroLastIndex(JSContext* cx) {
117 4564 : MOZ_ASSERT(lookupPure(cx->names().lastIndex)->writable(),
118 : "can't infallibly zero a non-writable lastIndex on a "
119 : "RegExp that's been exposed to script");
120 2282 : setSlot(LAST_INDEX_SLOT, Int32Value(0));
121 2282 : }
122 :
123 : JSFlatString* toString(JSContext* cx) const;
124 :
125 625 : JSAtom* getSource() const { return &getSlot(SOURCE_SLOT).toString()->asAtom(); }
126 :
127 0 : void setSource(JSAtom* source) {
128 2282 : setSlot(SOURCE_SLOT, StringValue(source));
129 0 : }
130 :
131 : /* Flags. */
132 :
133 : static unsigned flagsSlot() { return FLAGS_SLOT; }
134 :
135 : RegExpFlag getFlags() const {
136 2897 : return RegExpFlag(getFixedSlot(FLAGS_SLOT).toInt32());
137 : }
138 : void setFlags(RegExpFlag flags) {
139 4564 : setSlot(FLAGS_SLOT, Int32Value(flags));
140 : }
141 :
142 26 : bool ignoreCase() const { return getFlags() & IgnoreCaseFlag; }
143 28 : bool global() const { return getFlags() & GlobalFlag; }
144 0 : bool multiline() const { return getFlags() & MultilineFlag; }
145 0 : bool sticky() const { return getFlags() & StickyFlag; }
146 0 : bool unicode() const { return getFlags() & UnicodeFlag; }
147 :
148 : static bool isOriginalFlagGetter(JSNative native, RegExpFlag* mask);
149 :
150 : static RegExpShared* getShared(JSContext* cx, Handle<RegExpObject*> regexp);
151 :
152 : bool hasShared() {
153 12286 : return !!sharedRef();
154 : }
155 :
156 2025 : void setShared(RegExpShared& shared) {
157 2025 : MOZ_ASSERT(!hasShared());
158 0 : sharedRef().init(&shared);
159 0 : }
160 :
161 : PreBarriered<RegExpShared*>& sharedRef() {
162 14577 : auto& ref = NativeObject::privateRef(PRIVATE_SLOT);
163 : return reinterpret_cast<PreBarriered<RegExpShared*>&>(ref);
164 : }
165 :
166 : static void trace(JSTracer* trc, JSObject* obj);
167 : void trace(JSTracer* trc);
168 :
169 : void initIgnoringLastIndex(JSAtom* source, RegExpFlag flags);
170 :
171 : // NOTE: This method is *only* safe to call on RegExps that haven't been
172 : // exposed to script, because it requires that the "lastIndex"
173 : // property be writable.
174 : void initAndZeroLastIndex(JSAtom* source, RegExpFlag flags, JSContext* cx);
175 :
176 : #ifdef DEBUG
177 : static MOZ_MUST_USE bool dumpBytecode(JSContext* cx, Handle<RegExpObject*> regexp,
178 : bool match_only, HandleLinearString input);
179 : #endif
180 :
181 : private:
182 : /*
183 : * Precondition: the syntax for |source| has already been validated.
184 : * Side effect: sets the private field.
185 : */
186 : static RegExpShared* createShared(JSContext* cx, Handle<RegExpObject*> regexp);
187 :
188 : /* Call setShared in preference to setPrivate. */
189 : void setPrivate(void* priv) = delete;
190 : };
191 :
192 : /*
193 : * Parse regexp flags. Report an error and return false if an invalid
194 : * sequence of flags is encountered (repeat/invalid flag).
195 : *
196 : * N.B. flagStr must be rooted.
197 : */
198 : bool
199 : ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut);
200 :
201 : /* Assuming GetBuiltinClass(obj) is ESClass::RegExp, return a RegExpShared for obj. */
202 : inline RegExpShared*
203 0 : RegExpToShared(JSContext* cx, HandleObject obj)
204 : {
205 0 : if (obj->is<RegExpObject>())
206 0 : return RegExpObject::getShared(cx, obj.as<RegExpObject>());
207 :
208 0 : return Proxy::regexp_toShared(cx, obj);
209 : }
210 :
211 : template<XDRMode mode>
212 : XDRResult
213 : XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp);
214 :
215 : extern JSObject*
216 : CloneScriptRegExpObject(JSContext* cx, RegExpObject& re);
217 :
218 : /* Escape all slashes and newlines in the given string. */
219 : extern JSAtom*
220 : EscapeRegExpPattern(JSContext* cx, HandleAtom src);
221 :
222 : template <typename CharT>
223 : extern bool
224 : HasRegExpMetaChars(const CharT* chars, size_t length);
225 :
226 : extern bool
227 : StringHasRegExpMetaChars(JSLinearString* str);
228 :
229 : } /* namespace js */
230 :
231 : #endif /* vm_RegExpObject_h */
|