|
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 #include "builtin/RegExp.h" |
|
8 |
|
9 #include "jscntxt.h" |
|
10 |
|
11 #include "vm/RegExpStatics.h" |
|
12 #include "vm/StringBuffer.h" |
|
13 |
|
14 #include "jsobjinlines.h" |
|
15 |
|
16 using namespace js; |
|
17 using namespace js::types; |
|
18 |
|
19 using mozilla::ArrayLength; |
|
20 |
|
21 bool |
|
22 js::CreateRegExpMatchResult(JSContext *cx, HandleString input, const MatchPairs &matches, |
|
23 MutableHandleValue rval) |
|
24 { |
|
25 JS_ASSERT(input); |
|
26 |
|
27 /* |
|
28 * Create the (slow) result array for a match. |
|
29 * |
|
30 * Array contents: |
|
31 * 0: matched string |
|
32 * 1..pairCount-1: paren matches |
|
33 * input: input string |
|
34 * index: start index for the match |
|
35 */ |
|
36 |
|
37 /* Get the templateObject that defines the shape and type of the output object */ |
|
38 JSObject *templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx); |
|
39 if (!templateObject) |
|
40 return false; |
|
41 |
|
42 size_t numPairs = matches.length(); |
|
43 JS_ASSERT(numPairs > 0); |
|
44 |
|
45 RootedObject arr(cx, NewDenseAllocatedArrayWithTemplate(cx, numPairs, templateObject)); |
|
46 if (!arr) |
|
47 return false; |
|
48 |
|
49 /* Store a Value for each pair. */ |
|
50 for (size_t i = 0; i < numPairs; i++) { |
|
51 const MatchPair &pair = matches[i]; |
|
52 |
|
53 if (pair.isUndefined()) { |
|
54 JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ |
|
55 arr->setDenseInitializedLength(i + 1); |
|
56 arr->initDenseElement(i, UndefinedValue()); |
|
57 } else { |
|
58 JSLinearString *str = js_NewDependentString(cx, input, pair.start, pair.length()); |
|
59 if (!str) |
|
60 return false; |
|
61 arr->setDenseInitializedLength(i + 1); |
|
62 arr->initDenseElement(i, StringValue(str)); |
|
63 } |
|
64 } |
|
65 |
|
66 /* Set the |index| property. (TemplateObject positions it in slot 0) */ |
|
67 arr->nativeSetSlot(0, Int32Value(matches[0].start)); |
|
68 |
|
69 /* Set the |input| property. (TemplateObject positions it in slot 1) */ |
|
70 arr->nativeSetSlot(1, StringValue(input)); |
|
71 |
|
72 #ifdef DEBUG |
|
73 RootedValue test(cx); |
|
74 RootedId id(cx, NameToId(cx->names().index)); |
|
75 if (!baseops::GetProperty(cx, arr, id, &test)) |
|
76 return false; |
|
77 JS_ASSERT(test == arr->nativeGetSlot(0)); |
|
78 id = NameToId(cx->names().input); |
|
79 if (!baseops::GetProperty(cx, arr, id, &test)) |
|
80 return false; |
|
81 JS_ASSERT(test == arr->nativeGetSlot(1)); |
|
82 #endif |
|
83 |
|
84 rval.setObject(*arr); |
|
85 return true; |
|
86 } |
|
87 |
|
88 static RegExpRunStatus |
|
89 ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re, |
|
90 Handle<JSLinearString*> input, const jschar *chars, size_t length, |
|
91 size_t *lastIndex, MatchConduit &matches) |
|
92 { |
|
93 RegExpRunStatus status; |
|
94 |
|
95 /* Switch between MatchOnly and IncludeSubpatterns modes. */ |
|
96 if (matches.isPair) { |
|
97 size_t lastIndex_orig = *lastIndex; |
|
98 /* Only one MatchPair slot provided: execute short-circuiting regexp. */ |
|
99 status = re.executeMatchOnly(cx, chars, length, lastIndex, *matches.u.pair); |
|
100 if (status == RegExpRunStatus_Success && res) |
|
101 res->updateLazily(cx, input, &re, lastIndex_orig); |
|
102 } else { |
|
103 /* Vector of MatchPairs provided: execute full regexp. */ |
|
104 status = re.execute(cx, chars, length, lastIndex, *matches.u.pairs); |
|
105 if (status == RegExpRunStatus_Success && res) { |
|
106 if (!res->updateFromMatchPairs(cx, input, *matches.u.pairs)) |
|
107 return RegExpRunStatus_Error; |
|
108 } |
|
109 } |
|
110 |
|
111 return status; |
|
112 } |
|
113 |
|
114 /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */ |
|
115 bool |
|
116 js::ExecuteRegExpLegacy(JSContext *cx, RegExpStatics *res, RegExpObject &reobj, |
|
117 Handle<JSLinearString*> input_, const jschar *chars, size_t length, |
|
118 size_t *lastIndex, bool test, MutableHandleValue rval) |
|
119 { |
|
120 RegExpGuard shared(cx); |
|
121 if (!reobj.getShared(cx, &shared)) |
|
122 return false; |
|
123 |
|
124 ScopedMatchPairs matches(&cx->tempLifoAlloc()); |
|
125 MatchConduit conduit(&matches); |
|
126 |
|
127 RegExpRunStatus status = |
|
128 ExecuteRegExpImpl(cx, res, *shared, input_, chars, length, lastIndex, conduit); |
|
129 |
|
130 if (status == RegExpRunStatus_Error) |
|
131 return false; |
|
132 |
|
133 if (status == RegExpRunStatus_Success_NotFound) { |
|
134 /* ExecuteRegExp() previously returned an array or null. */ |
|
135 rval.setNull(); |
|
136 return true; |
|
137 } |
|
138 |
|
139 if (test) { |
|
140 /* Forbid an array, as an optimization. */ |
|
141 rval.setBoolean(true); |
|
142 return true; |
|
143 } |
|
144 |
|
145 RootedString input(cx, input_); |
|
146 if (!input) { |
|
147 input = js_NewStringCopyN<CanGC>(cx, chars, length); |
|
148 if (!input) |
|
149 return false; |
|
150 } |
|
151 |
|
152 return CreateRegExpMatchResult(cx, input, matches, rval); |
|
153 } |
|
154 |
|
155 /* Note: returns the original if no escaping need be performed. */ |
|
156 static JSAtom * |
|
157 EscapeNakedForwardSlashes(JSContext *cx, HandleAtom unescaped) |
|
158 { |
|
159 size_t oldLen = unescaped->length(); |
|
160 const jschar *oldChars = unescaped->chars(); |
|
161 |
|
162 JS::Anchor<JSString *> anchor(unescaped); |
|
163 |
|
164 /* We may never need to use |sb|. Start using it lazily. */ |
|
165 StringBuffer sb(cx); |
|
166 |
|
167 for (const jschar *it = oldChars; it < oldChars + oldLen; ++it) { |
|
168 if (*it == '/' && (it == oldChars || it[-1] != '\\')) { |
|
169 /* There's a forward slash that needs escaping. */ |
|
170 if (sb.empty()) { |
|
171 /* This is the first one we've seen, copy everything up to this point. */ |
|
172 if (!sb.reserve(oldLen + 1)) |
|
173 return nullptr; |
|
174 sb.infallibleAppend(oldChars, size_t(it - oldChars)); |
|
175 } |
|
176 if (!sb.append('\\')) |
|
177 return nullptr; |
|
178 } |
|
179 |
|
180 if (!sb.empty() && !sb.append(*it)) |
|
181 return nullptr; |
|
182 } |
|
183 |
|
184 return sb.empty() ? (JSAtom *)unescaped : sb.finishAtom(); |
|
185 } |
|
186 |
|
187 /* |
|
188 * Compile a new |RegExpShared| for the |RegExpObject|. |
|
189 * |
|
190 * Per ECMAv5 15.10.4.1, we act on combinations of (pattern, flags) as |
|
191 * arguments: |
|
192 * |
|
193 * RegExp, undefined => flags := pattern.flags |
|
194 * RegExp, _ => throw TypeError |
|
195 * _ => pattern := ToString(pattern) if defined(pattern) else '' |
|
196 * flags := ToString(flags) if defined(flags) else '' |
|
197 */ |
|
198 static bool |
|
199 CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args) |
|
200 { |
|
201 if (args.length() == 0) { |
|
202 RegExpStatics *res = cx->global()->getRegExpStatics(); |
|
203 Rooted<JSAtom*> empty(cx, cx->runtime()->emptyString); |
|
204 RegExpObject *reobj = builder.build(empty, res->getFlags()); |
|
205 if (!reobj) |
|
206 return false; |
|
207 args.rval().setObject(*reobj); |
|
208 return true; |
|
209 } |
|
210 |
|
211 RootedValue sourceValue(cx, args[0]); |
|
212 |
|
213 /* |
|
214 * If we get passed in an object whose internal [[Class]] property is |
|
215 * "RegExp", return a new object with the same source/flags. |
|
216 */ |
|
217 if (IsObjectWithClass(sourceValue, ESClass_RegExp, cx)) { |
|
218 /* |
|
219 * Beware, sourceObj may be a (transparent) proxy to a RegExp, so only |
|
220 * use generic (proxyable) operations on sourceObj that do not assume |
|
221 * sourceObj.is<RegExpObject>(). |
|
222 */ |
|
223 RootedObject sourceObj(cx, &sourceValue.toObject()); |
|
224 |
|
225 if (args.hasDefined(1)) { |
|
226 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED); |
|
227 return false; |
|
228 } |
|
229 |
|
230 /* |
|
231 * Only extract the 'flags' out of sourceObj; do not reuse the |
|
232 * RegExpShared since it may be from a different compartment. |
|
233 */ |
|
234 RegExpFlag flags; |
|
235 { |
|
236 RegExpGuard g(cx); |
|
237 if (!RegExpToShared(cx, sourceObj, &g)) |
|
238 return false; |
|
239 |
|
240 flags = g->getFlags(); |
|
241 } |
|
242 |
|
243 /* |
|
244 * 'toSource' is a permanent read-only property, so this is equivalent |
|
245 * to executing RegExpObject::getSource on the unwrapped object. |
|
246 */ |
|
247 RootedValue v(cx); |
|
248 if (!JSObject::getProperty(cx, sourceObj, sourceObj, cx->names().source, &v)) |
|
249 return false; |
|
250 |
|
251 Rooted<JSAtom*> sourceAtom(cx, &v.toString()->asAtom()); |
|
252 RegExpObject *reobj = builder.build(sourceAtom, flags); |
|
253 if (!reobj) |
|
254 return false; |
|
255 |
|
256 args.rval().setObject(*reobj); |
|
257 return true; |
|
258 } |
|
259 |
|
260 RootedAtom source(cx); |
|
261 if (sourceValue.isUndefined()) { |
|
262 source = cx->runtime()->emptyString; |
|
263 } else { |
|
264 /* Coerce to string and compile. */ |
|
265 source = ToAtom<CanGC>(cx, sourceValue); |
|
266 if (!source) |
|
267 return false; |
|
268 } |
|
269 |
|
270 RegExpFlag flags = RegExpFlag(0); |
|
271 if (args.hasDefined(1)) { |
|
272 RootedString flagStr(cx, ToString<CanGC>(cx, args[1])); |
|
273 if (!flagStr) |
|
274 return false; |
|
275 args[1].setString(flagStr); |
|
276 if (!ParseRegExpFlags(cx, flagStr, &flags)) |
|
277 return false; |
|
278 } |
|
279 |
|
280 RootedAtom escapedSourceStr(cx, EscapeNakedForwardSlashes(cx, source)); |
|
281 if (!escapedSourceStr) |
|
282 return false; |
|
283 |
|
284 if (!js::RegExpShared::checkSyntax(cx, nullptr, escapedSourceStr)) |
|
285 return false; |
|
286 |
|
287 RegExpStatics *res = cx->global()->getRegExpStatics(); |
|
288 RegExpObject *reobj = builder.build(escapedSourceStr, RegExpFlag(flags | res->getFlags())); |
|
289 if (!reobj) |
|
290 return false; |
|
291 |
|
292 args.rval().setObject(*reobj); |
|
293 return true; |
|
294 } |
|
295 |
|
296 MOZ_ALWAYS_INLINE bool |
|
297 IsRegExp(HandleValue v) |
|
298 { |
|
299 return v.isObject() && v.toObject().is<RegExpObject>(); |
|
300 } |
|
301 |
|
302 MOZ_ALWAYS_INLINE bool |
|
303 regexp_compile_impl(JSContext *cx, CallArgs args) |
|
304 { |
|
305 JS_ASSERT(IsRegExp(args.thisv())); |
|
306 RegExpObjectBuilder builder(cx, &args.thisv().toObject().as<RegExpObject>()); |
|
307 return CompileRegExpObject(cx, builder, args); |
|
308 } |
|
309 |
|
310 static bool |
|
311 regexp_compile(JSContext *cx, unsigned argc, Value *vp) |
|
312 { |
|
313 CallArgs args = CallArgsFromVp(argc, vp); |
|
314 return CallNonGenericMethod<IsRegExp, regexp_compile_impl>(cx, args); |
|
315 } |
|
316 |
|
317 static bool |
|
318 regexp_construct(JSContext *cx, unsigned argc, Value *vp) |
|
319 { |
|
320 CallArgs args = CallArgsFromVp(argc, vp); |
|
321 |
|
322 if (!args.isConstructing()) { |
|
323 /* |
|
324 * If first arg is regexp and no flags are given, just return the arg. |
|
325 * Otherwise, delegate to the standard constructor. |
|
326 * See ECMAv5 15.10.3.1. |
|
327 */ |
|
328 if (args.hasDefined(0) && |
|
329 IsObjectWithClass(args[0], ESClass_RegExp, cx) && |
|
330 !args.hasDefined(1)) |
|
331 { |
|
332 args.rval().set(args[0]); |
|
333 return true; |
|
334 } |
|
335 } |
|
336 |
|
337 RegExpObjectBuilder builder(cx); |
|
338 return CompileRegExpObject(cx, builder, args); |
|
339 } |
|
340 |
|
341 MOZ_ALWAYS_INLINE bool |
|
342 regexp_toString_impl(JSContext *cx, CallArgs args) |
|
343 { |
|
344 JS_ASSERT(IsRegExp(args.thisv())); |
|
345 |
|
346 JSString *str = args.thisv().toObject().as<RegExpObject>().toString(cx); |
|
347 if (!str) |
|
348 return false; |
|
349 |
|
350 args.rval().setString(str); |
|
351 return true; |
|
352 } |
|
353 |
|
354 static bool |
|
355 regexp_toString(JSContext *cx, unsigned argc, Value *vp) |
|
356 { |
|
357 CallArgs args = CallArgsFromVp(argc, vp); |
|
358 return CallNonGenericMethod<IsRegExp, regexp_toString_impl>(cx, args); |
|
359 } |
|
360 |
|
361 static const JSFunctionSpec regexp_methods[] = { |
|
362 #if JS_HAS_TOSOURCE |
|
363 JS_FN(js_toSource_str, regexp_toString, 0,0), |
|
364 #endif |
|
365 JS_FN(js_toString_str, regexp_toString, 0,0), |
|
366 JS_FN("compile", regexp_compile, 2,0), |
|
367 JS_FN("exec", regexp_exec, 1,0), |
|
368 JS_FN("test", regexp_test, 1,0), |
|
369 JS_FS_END |
|
370 }; |
|
371 |
|
372 /* |
|
373 * RegExp static properties. |
|
374 * |
|
375 * RegExp class static properties and their Perl counterparts: |
|
376 * |
|
377 * RegExp.input $_ |
|
378 * RegExp.multiline $* |
|
379 * RegExp.lastMatch $& |
|
380 * RegExp.lastParen $+ |
|
381 * RegExp.leftContext $` |
|
382 * RegExp.rightContext $' |
|
383 */ |
|
384 |
|
385 #define DEFINE_STATIC_GETTER(name, code) \ |
|
386 static bool \ |
|
387 name(JSContext *cx, unsigned argc, Value *vp) \ |
|
388 { \ |
|
389 CallArgs args = CallArgsFromVp(argc, vp); \ |
|
390 RegExpStatics *res = cx->global()->getRegExpStatics(); \ |
|
391 code; \ |
|
392 } |
|
393 |
|
394 DEFINE_STATIC_GETTER(static_input_getter, return res->createPendingInput(cx, args.rval())) |
|
395 DEFINE_STATIC_GETTER(static_multiline_getter, args.rval().setBoolean(res->multiline()); |
|
396 return true) |
|
397 DEFINE_STATIC_GETTER(static_lastMatch_getter, return res->createLastMatch(cx, args.rval())) |
|
398 DEFINE_STATIC_GETTER(static_lastParen_getter, return res->createLastParen(cx, args.rval())) |
|
399 DEFINE_STATIC_GETTER(static_leftContext_getter, return res->createLeftContext(cx, args.rval())) |
|
400 DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, args.rval())) |
|
401 |
|
402 DEFINE_STATIC_GETTER(static_paren1_getter, return res->createParen(cx, 1, args.rval())) |
|
403 DEFINE_STATIC_GETTER(static_paren2_getter, return res->createParen(cx, 2, args.rval())) |
|
404 DEFINE_STATIC_GETTER(static_paren3_getter, return res->createParen(cx, 3, args.rval())) |
|
405 DEFINE_STATIC_GETTER(static_paren4_getter, return res->createParen(cx, 4, args.rval())) |
|
406 DEFINE_STATIC_GETTER(static_paren5_getter, return res->createParen(cx, 5, args.rval())) |
|
407 DEFINE_STATIC_GETTER(static_paren6_getter, return res->createParen(cx, 6, args.rval())) |
|
408 DEFINE_STATIC_GETTER(static_paren7_getter, return res->createParen(cx, 7, args.rval())) |
|
409 DEFINE_STATIC_GETTER(static_paren8_getter, return res->createParen(cx, 8, args.rval())) |
|
410 DEFINE_STATIC_GETTER(static_paren9_getter, return res->createParen(cx, 9, args.rval())) |
|
411 |
|
412 #define DEFINE_STATIC_SETTER(name, code) \ |
|
413 static bool \ |
|
414 name(JSContext *cx, unsigned argc, Value *vp) \ |
|
415 { \ |
|
416 RegExpStatics *res = cx->global()->getRegExpStatics(); \ |
|
417 code; \ |
|
418 return true; \ |
|
419 } |
|
420 |
|
421 static bool |
|
422 static_input_setter(JSContext *cx, unsigned argc, Value *vp) |
|
423 { |
|
424 CallArgs args = CallArgsFromVp(argc, vp); |
|
425 RegExpStatics *res = cx->global()->getRegExpStatics(); |
|
426 |
|
427 RootedString str(cx, ToString<CanGC>(cx, args.get(0))); |
|
428 if (!str) |
|
429 return false; |
|
430 |
|
431 res->setPendingInput(str); |
|
432 args.rval().setString(str); |
|
433 return true; |
|
434 } |
|
435 |
|
436 static bool |
|
437 static_multiline_setter(JSContext *cx, unsigned argc, Value *vp) |
|
438 { |
|
439 CallArgs args = CallArgsFromVp(argc, vp); |
|
440 RegExpStatics *res = cx->global()->getRegExpStatics(); |
|
441 |
|
442 bool b = ToBoolean(args.get(0)); |
|
443 res->setMultiline(cx, b); |
|
444 args.rval().setBoolean(b); |
|
445 return true; |
|
446 } |
|
447 |
|
448 static const JSPropertySpec regexp_static_props[] = { |
|
449 JS_PSGS("input", static_input_getter, static_input_setter, |
|
450 JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
451 JS_PSGS("multiline", static_multiline_getter, static_multiline_setter, |
|
452 JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
453 JS_PSG("lastMatch", static_lastMatch_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
454 JS_PSG("lastParen", static_lastParen_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
455 JS_PSG("leftContext", static_leftContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
456 JS_PSG("rightContext", static_rightContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
457 JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
458 JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
459 JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
460 JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
461 JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
462 JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
463 JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
464 JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
465 JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), |
|
466 JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT), |
|
467 JS_PSGS("$*", static_multiline_getter, static_multiline_setter, JSPROP_PERMANENT), |
|
468 JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT), |
|
469 JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT), |
|
470 JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT), |
|
471 JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT), |
|
472 JS_PS_END |
|
473 }; |
|
474 |
|
475 JSObject * |
|
476 js_InitRegExpClass(JSContext *cx, HandleObject obj) |
|
477 { |
|
478 JS_ASSERT(obj->isNative()); |
|
479 |
|
480 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
|
481 |
|
482 RootedObject proto(cx, global->createBlankPrototype(cx, &RegExpObject::class_)); |
|
483 if (!proto) |
|
484 return nullptr; |
|
485 proto->setPrivate(nullptr); |
|
486 |
|
487 HandlePropertyName empty = cx->names().empty; |
|
488 RegExpObjectBuilder builder(cx, &proto->as<RegExpObject>()); |
|
489 if (!builder.build(empty, RegExpFlag(0))) |
|
490 return nullptr; |
|
491 |
|
492 if (!DefinePropertiesAndBrand(cx, proto, nullptr, regexp_methods)) |
|
493 return nullptr; |
|
494 |
|
495 RootedFunction ctor(cx); |
|
496 ctor = global->createConstructor(cx, regexp_construct, cx->names().RegExp, 2); |
|
497 if (!ctor) |
|
498 return nullptr; |
|
499 |
|
500 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
501 return nullptr; |
|
502 |
|
503 /* Add static properties to the RegExp constructor. */ |
|
504 if (!JS_DefineProperties(cx, ctor, regexp_static_props)) |
|
505 return nullptr; |
|
506 |
|
507 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_RegExp, ctor, proto)) |
|
508 return nullptr; |
|
509 |
|
510 return proto; |
|
511 } |
|
512 |
|
513 RegExpRunStatus |
|
514 js::ExecuteRegExp(JSContext *cx, HandleObject regexp, HandleString string, |
|
515 MatchConduit &matches, RegExpStaticsUpdate staticsUpdate) |
|
516 { |
|
517 /* Step 1 (b) was performed by CallNonGenericMethod. */ |
|
518 Rooted<RegExpObject*> reobj(cx, ®exp->as<RegExpObject>()); |
|
519 |
|
520 RegExpGuard re(cx); |
|
521 if (!reobj->getShared(cx, &re)) |
|
522 return RegExpRunStatus_Error; |
|
523 |
|
524 RegExpStatics *res = (staticsUpdate == UpdateRegExpStatics) |
|
525 ? cx->global()->getRegExpStatics() |
|
526 : nullptr; |
|
527 |
|
528 /* Step 3. */ |
|
529 Rooted<JSLinearString*> input(cx, string->ensureLinear(cx)); |
|
530 if (!input) |
|
531 return RegExpRunStatus_Error; |
|
532 |
|
533 /* Step 4. */ |
|
534 RootedValue lastIndex(cx, reobj->getLastIndex()); |
|
535 size_t length = input->length(); |
|
536 |
|
537 /* Step 5. */ |
|
538 int i; |
|
539 if (lastIndex.isInt32()) { |
|
540 /* Aggressively avoid doubles. */ |
|
541 i = lastIndex.toInt32(); |
|
542 } else { |
|
543 double d; |
|
544 if (!ToInteger(cx, lastIndex, &d)) |
|
545 return RegExpRunStatus_Error; |
|
546 |
|
547 /* Inlined steps 6, 7, 9a with doubles to detect failure case. */ |
|
548 if ((re->global() || re->sticky()) && (d < 0 || d > length)) { |
|
549 reobj->zeroLastIndex(); |
|
550 return RegExpRunStatus_Success_NotFound; |
|
551 } |
|
552 |
|
553 i = int(d); |
|
554 } |
|
555 |
|
556 /* Steps 6-7 (with sticky extension). */ |
|
557 if (!re->global() && !re->sticky()) |
|
558 i = 0; |
|
559 |
|
560 /* Step 9a. */ |
|
561 if (i < 0 || size_t(i) > length) { |
|
562 reobj->zeroLastIndex(); |
|
563 return RegExpRunStatus_Success_NotFound; |
|
564 } |
|
565 |
|
566 /* Steps 8-21. */ |
|
567 const jschar *chars = input->chars(); |
|
568 size_t lastIndexInt(i); |
|
569 RegExpRunStatus status = |
|
570 ExecuteRegExpImpl(cx, res, *re, input, chars, length, &lastIndexInt, matches); |
|
571 |
|
572 if (status == RegExpRunStatus_Error) |
|
573 return RegExpRunStatus_Error; |
|
574 |
|
575 /* Steps 9a and 11 (with sticky extension). */ |
|
576 if (status == RegExpRunStatus_Success_NotFound) |
|
577 reobj->zeroLastIndex(); |
|
578 else if (re->global() || re->sticky()) |
|
579 reobj->setLastIndex(lastIndexInt); |
|
580 |
|
581 return status; |
|
582 } |
|
583 |
|
584 /* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). */ |
|
585 static RegExpRunStatus |
|
586 ExecuteRegExp(JSContext *cx, CallArgs args, MatchConduit &matches) |
|
587 { |
|
588 /* Step 1 (a) was performed by CallNonGenericMethod. */ |
|
589 RootedObject regexp(cx, &args.thisv().toObject()); |
|
590 |
|
591 /* Step 2. */ |
|
592 RootedString string(cx, ToString<CanGC>(cx, args.get(0))); |
|
593 if (!string) |
|
594 return RegExpRunStatus_Error; |
|
595 |
|
596 return ExecuteRegExp(cx, regexp, string, matches, UpdateRegExpStatics); |
|
597 } |
|
598 |
|
599 /* ES5 15.10.6.2. */ |
|
600 static bool |
|
601 regexp_exec_impl(JSContext *cx, HandleObject regexp, HandleString string, |
|
602 RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval) |
|
603 { |
|
604 /* Execute regular expression and gather matches. */ |
|
605 ScopedMatchPairs matches(&cx->tempLifoAlloc()); |
|
606 MatchConduit conduit(&matches); |
|
607 |
|
608 RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, conduit, staticsUpdate); |
|
609 |
|
610 if (status == RegExpRunStatus_Error) |
|
611 return false; |
|
612 |
|
613 if (status == RegExpRunStatus_Success_NotFound) { |
|
614 rval.setNull(); |
|
615 return true; |
|
616 } |
|
617 |
|
618 return CreateRegExpMatchResult(cx, string, matches, rval); |
|
619 } |
|
620 |
|
621 static bool |
|
622 regexp_exec_impl(JSContext *cx, CallArgs args) |
|
623 { |
|
624 RootedObject regexp(cx, &args.thisv().toObject()); |
|
625 RootedString string(cx, ToString<CanGC>(cx, args.get(0))); |
|
626 if (!string) |
|
627 return false; |
|
628 |
|
629 return regexp_exec_impl(cx, regexp, string, UpdateRegExpStatics, args.rval()); |
|
630 } |
|
631 |
|
632 bool |
|
633 js::regexp_exec(JSContext *cx, unsigned argc, Value *vp) |
|
634 { |
|
635 CallArgs args = CallArgsFromVp(argc, vp); |
|
636 return CallNonGenericMethod(cx, IsRegExp, regexp_exec_impl, args); |
|
637 } |
|
638 |
|
639 /* Separate interface for use by IonMonkey. */ |
|
640 bool |
|
641 js::regexp_exec_raw(JSContext *cx, HandleObject regexp, HandleString input, MutableHandleValue output) |
|
642 { |
|
643 return regexp_exec_impl(cx, regexp, input, UpdateRegExpStatics, output); |
|
644 } |
|
645 |
|
646 bool |
|
647 js::regexp_exec_no_statics(JSContext *cx, unsigned argc, Value *vp) |
|
648 { |
|
649 CallArgs args = CallArgsFromVp(argc, vp); |
|
650 JS_ASSERT(args.length() == 2); |
|
651 JS_ASSERT(IsRegExp(args[0])); |
|
652 JS_ASSERT(args[1].isString()); |
|
653 |
|
654 RootedObject regexp(cx, &args[0].toObject()); |
|
655 RootedString string(cx, args[1].toString()); |
|
656 |
|
657 return regexp_exec_impl(cx, regexp, string, DontUpdateRegExpStatics, args.rval()); |
|
658 } |
|
659 |
|
660 /* ES5 15.10.6.3. */ |
|
661 static bool |
|
662 regexp_test_impl(JSContext *cx, CallArgs args) |
|
663 { |
|
664 MatchPair match; |
|
665 MatchConduit conduit(&match); |
|
666 RegExpRunStatus status = ExecuteRegExp(cx, args, conduit); |
|
667 args.rval().setBoolean(status == RegExpRunStatus_Success); |
|
668 return status != RegExpRunStatus_Error; |
|
669 } |
|
670 |
|
671 /* Separate interface for use by IonMonkey. */ |
|
672 bool |
|
673 js::regexp_test_raw(JSContext *cx, HandleObject regexp, HandleString input, bool *result) |
|
674 { |
|
675 MatchPair match; |
|
676 MatchConduit conduit(&match); |
|
677 RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, conduit, UpdateRegExpStatics); |
|
678 *result = (status == RegExpRunStatus_Success); |
|
679 return status != RegExpRunStatus_Error; |
|
680 } |
|
681 |
|
682 bool |
|
683 js::regexp_test(JSContext *cx, unsigned argc, Value *vp) |
|
684 { |
|
685 CallArgs args = CallArgsFromVp(argc, vp); |
|
686 return CallNonGenericMethod(cx, IsRegExp, regexp_test_impl, args); |
|
687 } |
|
688 |
|
689 bool |
|
690 js::regexp_test_no_statics(JSContext *cx, unsigned argc, Value *vp) |
|
691 { |
|
692 CallArgs args = CallArgsFromVp(argc, vp); |
|
693 JS_ASSERT(args.length() == 2); |
|
694 JS_ASSERT(IsRegExp(args[0])); |
|
695 JS_ASSERT(args[1].isString()); |
|
696 |
|
697 RootedObject regexp(cx, &args[0].toObject()); |
|
698 RootedString string(cx, args[1].toString()); |
|
699 |
|
700 MatchPair match; |
|
701 MatchConduit conduit(&match); |
|
702 RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, conduit, DontUpdateRegExpStatics); |
|
703 args.rval().setBoolean(status == RegExpRunStatus_Success); |
|
704 return status != RegExpRunStatus_Error; |
|
705 } |