1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/builtins/builtins-utils.h"
6 #include "src/builtins/builtins.h"
7
8 #include "src/code-factory.h"
9 #include "src/regexp/jsregexp.h"
10 #include "src/regexp/regexp-utils.h"
11 #include "src/string-builder.h"
12
13 namespace v8 {
14 namespace internal {
15
16 // -----------------------------------------------------------------------------
17 // ES6 section 21.2 RegExp Objects
18
19 namespace {
20
PatternFlags(Isolate * isolate,Handle<JSRegExp> regexp)21 Handle<String> PatternFlags(Isolate* isolate, Handle<JSRegExp> regexp) {
22 static const int kMaxFlagsLength = 5 + 1; // 5 flags and '\0';
23 char flags_string[kMaxFlagsLength];
24 int i = 0;
25
26 const JSRegExp::Flags flags = regexp->GetFlags();
27
28 if ((flags & JSRegExp::kGlobal) != 0) flags_string[i++] = 'g';
29 if ((flags & JSRegExp::kIgnoreCase) != 0) flags_string[i++] = 'i';
30 if ((flags & JSRegExp::kMultiline) != 0) flags_string[i++] = 'm';
31 if ((flags & JSRegExp::kUnicode) != 0) flags_string[i++] = 'u';
32 if ((flags & JSRegExp::kSticky) != 0) flags_string[i++] = 'y';
33
34 DCHECK_LT(i, kMaxFlagsLength);
35 memset(&flags_string[i], '\0', kMaxFlagsLength - i);
36
37 return isolate->factory()->NewStringFromAsciiChecked(flags_string);
38 }
39
40 // ES#sec-regexpinitialize
41 // Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
RegExpInitialize(Isolate * isolate,Handle<JSRegExp> regexp,Handle<Object> pattern,Handle<Object> flags)42 MUST_USE_RESULT MaybeHandle<JSRegExp> RegExpInitialize(Isolate* isolate,
43 Handle<JSRegExp> regexp,
44 Handle<Object> pattern,
45 Handle<Object> flags) {
46 Handle<String> pattern_string;
47 if (pattern->IsUndefined(isolate)) {
48 pattern_string = isolate->factory()->empty_string();
49 } else {
50 ASSIGN_RETURN_ON_EXCEPTION(isolate, pattern_string,
51 Object::ToString(isolate, pattern), JSRegExp);
52 }
53
54 Handle<String> flags_string;
55 if (flags->IsUndefined(isolate)) {
56 flags_string = isolate->factory()->empty_string();
57 } else {
58 ASSIGN_RETURN_ON_EXCEPTION(isolate, flags_string,
59 Object::ToString(isolate, flags), JSRegExp);
60 }
61
62 // TODO(jgruber): We could avoid the flags back and forth conversions.
63 return JSRegExp::Initialize(regexp, pattern_string, flags_string);
64 }
65
66 } // namespace
67
68 // ES#sec-regexp-pattern-flags
69 // RegExp ( pattern, flags )
BUILTIN(RegExpConstructor)70 BUILTIN(RegExpConstructor) {
71 HandleScope scope(isolate);
72
73 Handle<HeapObject> new_target = args.new_target();
74 Handle<Object> pattern = args.atOrUndefined(isolate, 1);
75 Handle<Object> flags = args.atOrUndefined(isolate, 2);
76
77 Handle<JSFunction> target = isolate->regexp_function();
78
79 bool pattern_is_regexp;
80 {
81 Maybe<bool> maybe_pattern_is_regexp =
82 RegExpUtils::IsRegExp(isolate, pattern);
83 if (maybe_pattern_is_regexp.IsNothing()) {
84 DCHECK(isolate->has_pending_exception());
85 return isolate->heap()->exception();
86 }
87 pattern_is_regexp = maybe_pattern_is_regexp.FromJust();
88 }
89
90 if (new_target->IsUndefined(isolate)) {
91 new_target = target;
92
93 // ES6 section 21.2.3.1 step 3.b
94 if (pattern_is_regexp && flags->IsUndefined(isolate)) {
95 Handle<Object> pattern_constructor;
96 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
97 isolate, pattern_constructor,
98 Object::GetProperty(pattern,
99 isolate->factory()->constructor_string()));
100
101 if (pattern_constructor.is_identical_to(new_target)) {
102 return *pattern;
103 }
104 }
105 }
106
107 if (pattern->IsJSRegExp()) {
108 Handle<JSRegExp> regexp_pattern = Handle<JSRegExp>::cast(pattern);
109
110 if (flags->IsUndefined(isolate)) {
111 flags = PatternFlags(isolate, regexp_pattern);
112 }
113 pattern = handle(regexp_pattern->source(), isolate);
114 } else if (pattern_is_regexp) {
115 Handle<Object> pattern_source;
116 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
117 isolate, pattern_source,
118 Object::GetProperty(pattern, isolate->factory()->source_string()));
119
120 if (flags->IsUndefined(isolate)) {
121 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
122 isolate, flags,
123 Object::GetProperty(pattern, isolate->factory()->flags_string()));
124 }
125 pattern = pattern_source;
126 }
127
128 Handle<JSReceiver> new_target_receiver = Handle<JSReceiver>::cast(new_target);
129
130 // TODO(jgruber): Fast-path for target == new_target == unmodified JSRegExp.
131
132 Handle<JSObject> object;
133 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
134 isolate, object, JSObject::New(target, new_target_receiver));
135 Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(object);
136
137 RETURN_RESULT_OR_FAILURE(isolate,
138 RegExpInitialize(isolate, regexp, pattern, flags));
139 }
140
BUILTIN(RegExpPrototypeCompile)141 BUILTIN(RegExpPrototypeCompile) {
142 HandleScope scope(isolate);
143 CHECK_RECEIVER(JSRegExp, regexp, "RegExp.prototype.compile");
144
145 Handle<Object> pattern = args.atOrUndefined(isolate, 1);
146 Handle<Object> flags = args.atOrUndefined(isolate, 2);
147
148 if (pattern->IsJSRegExp()) {
149 Handle<JSRegExp> pattern_regexp = Handle<JSRegExp>::cast(pattern);
150
151 if (!flags->IsUndefined(isolate)) {
152 THROW_NEW_ERROR_RETURN_FAILURE(
153 isolate, NewTypeError(MessageTemplate::kRegExpFlags));
154 }
155
156 flags = PatternFlags(isolate, pattern_regexp);
157 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
158 isolate, pattern,
159 Object::GetProperty(pattern, isolate->factory()->source_string()));
160 }
161
162 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
163 isolate, regexp, RegExpInitialize(isolate, regexp, pattern, flags));
164
165 // Return undefined for compatibility with JSC.
166 // See http://crbug.com/585775 for web compat details.
167
168 return isolate->heap()->undefined_value();
169 }
170
171 namespace {
172
FastLoadLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp)173 compiler::Node* FastLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
174 compiler::Node* regexp) {
175 // Load the in-object field.
176 static const int field_offset =
177 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
178 return a->LoadObjectField(regexp, field_offset);
179 }
180
SlowLoadLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp)181 compiler::Node* SlowLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
182 compiler::Node* regexp) {
183 // Load through the GetProperty stub.
184 typedef compiler::Node Node;
185
186 Node* const name =
187 a->HeapConstant(a->isolate()->factory()->lastIndex_string());
188 Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
189 return a->CallStub(getproperty_callable, context, regexp, name);
190 }
191
LoadLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * has_initialmap,compiler::Node * regexp)192 compiler::Node* LoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
193 compiler::Node* has_initialmap,
194 compiler::Node* regexp) {
195 typedef CodeStubAssembler::Variable Variable;
196 typedef CodeStubAssembler::Label Label;
197
198 Variable var_value(a, MachineRepresentation::kTagged);
199
200 Label out(a), if_unmodified(a), if_modified(a);
201 a->Branch(has_initialmap, &if_unmodified, &if_modified);
202
203 a->Bind(&if_unmodified);
204 {
205 var_value.Bind(FastLoadLastIndex(a, context, regexp));
206 a->Goto(&out);
207 }
208
209 a->Bind(&if_modified);
210 {
211 var_value.Bind(SlowLoadLastIndex(a, context, regexp));
212 a->Goto(&out);
213 }
214
215 a->Bind(&out);
216 return var_value.value();
217 }
218
219 // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified
220 // JSRegExp instance.
FastStoreLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp,compiler::Node * value)221 void FastStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
222 compiler::Node* regexp, compiler::Node* value) {
223 // Store the in-object field.
224 static const int field_offset =
225 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
226 a->StoreObjectField(regexp, field_offset, value);
227 }
228
SlowStoreLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp,compiler::Node * value)229 void SlowStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
230 compiler::Node* regexp, compiler::Node* value) {
231 // Store through runtime.
232 // TODO(ishell): Use SetPropertyStub here once available.
233 typedef compiler::Node Node;
234
235 Node* const name =
236 a->HeapConstant(a->isolate()->factory()->lastIndex_string());
237 Node* const language_mode = a->SmiConstant(Smi::FromInt(STRICT));
238 a->CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
239 language_mode);
240 }
241
StoreLastIndex(CodeStubAssembler * a,compiler::Node * context,compiler::Node * has_initialmap,compiler::Node * regexp,compiler::Node * value)242 void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
243 compiler::Node* has_initialmap, compiler::Node* regexp,
244 compiler::Node* value) {
245 typedef CodeStubAssembler::Label Label;
246
247 Label out(a), if_unmodified(a), if_modified(a);
248 a->Branch(has_initialmap, &if_unmodified, &if_modified);
249
250 a->Bind(&if_unmodified);
251 {
252 FastStoreLastIndex(a, context, regexp, value);
253 a->Goto(&out);
254 }
255
256 a->Bind(&if_modified);
257 {
258 SlowStoreLastIndex(a, context, regexp, value);
259 a->Goto(&out);
260 }
261
262 a->Bind(&out);
263 }
264
ConstructNewResultFromMatchInfo(Isolate * isolate,CodeStubAssembler * a,compiler::Node * context,compiler::Node * match_info,compiler::Node * string)265 compiler::Node* ConstructNewResultFromMatchInfo(Isolate* isolate,
266 CodeStubAssembler* a,
267 compiler::Node* context,
268 compiler::Node* match_info,
269 compiler::Node* string) {
270 typedef CodeStubAssembler::Variable Variable;
271 typedef CodeStubAssembler::Label Label;
272 typedef compiler::Node Node;
273
274 Label out(a);
275
276 CodeStubAssembler::ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
277 Node* const num_indices = a->SmiUntag(a->LoadFixedArrayElement(
278 match_info, a->IntPtrConstant(RegExpMatchInfo::kNumberOfCapturesIndex), 0,
279 mode));
280 Node* const num_results = a->SmiTag(a->WordShr(num_indices, 1));
281 Node* const start = a->LoadFixedArrayElement(
282 match_info, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), 0,
283 mode);
284 Node* const end = a->LoadFixedArrayElement(
285 match_info, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1), 0,
286 mode);
287
288 // Calculate the substring of the first match before creating the result array
289 // to avoid an unnecessary write barrier storing the first result.
290 Node* const first = a->SubString(context, string, start, end);
291
292 Node* const result =
293 a->AllocateRegExpResult(context, num_results, start, string);
294 Node* const result_elements = a->LoadElements(result);
295
296 a->StoreFixedArrayElement(result_elements, a->IntPtrConstant(0), first,
297 SKIP_WRITE_BARRIER);
298
299 a->GotoIf(a->SmiEqual(num_results, a->SmiConstant(Smi::FromInt(1))), &out);
300
301 // Store all remaining captures.
302 Node* const limit = a->IntPtrAdd(
303 a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices);
304
305 Variable var_from_cursor(a, MachineType::PointerRepresentation());
306 Variable var_to_cursor(a, MachineType::PointerRepresentation());
307
308 var_from_cursor.Bind(
309 a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2));
310 var_to_cursor.Bind(a->IntPtrConstant(1));
311
312 Variable* vars[] = {&var_from_cursor, &var_to_cursor};
313 Label loop(a, 2, vars);
314
315 a->Goto(&loop);
316 a->Bind(&loop);
317 {
318 Node* const from_cursor = var_from_cursor.value();
319 Node* const to_cursor = var_to_cursor.value();
320 Node* const start = a->LoadFixedArrayElement(match_info, from_cursor);
321
322 Label next_iter(a);
323 a->GotoIf(a->SmiEqual(start, a->SmiConstant(Smi::FromInt(-1))), &next_iter);
324
325 Node* const from_cursor_plus1 =
326 a->IntPtrAdd(from_cursor, a->IntPtrConstant(1));
327 Node* const end = a->LoadFixedArrayElement(match_info, from_cursor_plus1);
328
329 Node* const capture = a->SubString(context, string, start, end);
330 a->StoreFixedArrayElement(result_elements, to_cursor, capture);
331 a->Goto(&next_iter);
332
333 a->Bind(&next_iter);
334 var_from_cursor.Bind(a->IntPtrAdd(from_cursor, a->IntPtrConstant(2)));
335 var_to_cursor.Bind(a->IntPtrAdd(to_cursor, a->IntPtrConstant(1)));
336 a->Branch(a->UintPtrLessThan(var_from_cursor.value(), limit), &loop, &out);
337 }
338
339 a->Bind(&out);
340 return result;
341 }
342
343 // ES#sec-regexp.prototype.exec
344 // RegExp.prototype.exec ( string )
RegExpPrototypeExecInternal(CodeStubAssembler * a,compiler::Node * context,compiler::Node * maybe_receiver,compiler::Node * maybe_string)345 compiler::Node* RegExpPrototypeExecInternal(CodeStubAssembler* a,
346 compiler::Node* context,
347 compiler::Node* maybe_receiver,
348 compiler::Node* maybe_string) {
349 typedef CodeStubAssembler::Variable Variable;
350 typedef CodeStubAssembler::Label Label;
351 typedef compiler::Node Node;
352
353 Isolate* const isolate = a->isolate();
354
355 Node* const null = a->NullConstant();
356 Node* const int_zero = a->IntPtrConstant(0);
357 Node* const smi_zero = a->SmiConstant(Smi::kZero);
358
359 Variable var_result(a, MachineRepresentation::kTagged);
360 Label out(a);
361
362 // Ensure {maybe_receiver} is a JSRegExp.
363 Node* const regexp_map = a->ThrowIfNotInstanceType(
364 context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec");
365 Node* const regexp = maybe_receiver;
366
367 // Check whether the regexp instance is unmodified.
368 Node* const native_context = a->LoadNativeContext(context);
369 Node* const regexp_fun =
370 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
371 Node* const initial_map =
372 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
373 Node* const has_initialmap = a->WordEqual(regexp_map, initial_map);
374
375 // Convert {maybe_string} to a string.
376 Callable tostring_callable = CodeFactory::ToString(isolate);
377 Node* const string = a->CallStub(tostring_callable, context, maybe_string);
378 Node* const string_length = a->LoadStringLength(string);
379
380 // Check whether the regexp is global or sticky, which determines whether we
381 // update last index later on.
382 Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
383 Node* const is_global_or_sticky =
384 a->WordAnd(a->SmiUntag(flags),
385 a->IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky));
386 Node* const should_update_last_index =
387 a->WordNotEqual(is_global_or_sticky, int_zero);
388
389 // Grab and possibly update last index.
390 Label run_exec(a);
391 Variable var_lastindex(a, MachineRepresentation::kTagged);
392 {
393 Label if_doupdate(a), if_dontupdate(a);
394 a->Branch(should_update_last_index, &if_doupdate, &if_dontupdate);
395
396 a->Bind(&if_doupdate);
397 {
398 Node* const regexp_lastindex =
399 LoadLastIndex(a, context, has_initialmap, regexp);
400
401 Callable tolength_callable = CodeFactory::ToLength(isolate);
402 Node* const lastindex =
403 a->CallStub(tolength_callable, context, regexp_lastindex);
404 var_lastindex.Bind(lastindex);
405
406 Label if_isoob(a, Label::kDeferred);
407 a->GotoUnless(a->TaggedIsSmi(lastindex), &if_isoob);
408 a->GotoUnless(a->SmiLessThanOrEqual(lastindex, string_length), &if_isoob);
409 a->Goto(&run_exec);
410
411 a->Bind(&if_isoob);
412 {
413 StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
414 var_result.Bind(null);
415 a->Goto(&out);
416 }
417 }
418
419 a->Bind(&if_dontupdate);
420 {
421 var_lastindex.Bind(smi_zero);
422 a->Goto(&run_exec);
423 }
424 }
425
426 Node* match_indices;
427 Label successful_match(a);
428 a->Bind(&run_exec);
429 {
430 // Get last match info from the context.
431 Node* const last_match_info = a->LoadContextElement(
432 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
433
434 // Call the exec stub.
435 Callable exec_callable = CodeFactory::RegExpExec(isolate);
436 match_indices = a->CallStub(exec_callable, context, regexp, string,
437 var_lastindex.value(), last_match_info);
438
439 // {match_indices} is either null or the RegExpMatchInfo array.
440 // Return early if exec failed, possibly updating last index.
441 a->GotoUnless(a->WordEqual(match_indices, null), &successful_match);
442
443 Label return_null(a);
444 a->GotoUnless(should_update_last_index, &return_null);
445
446 StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
447 a->Goto(&return_null);
448
449 a->Bind(&return_null);
450 var_result.Bind(null);
451 a->Goto(&out);
452 }
453
454 Label construct_result(a);
455 a->Bind(&successful_match);
456 {
457 a->GotoUnless(should_update_last_index, &construct_result);
458
459 // Update the new last index from {match_indices}.
460 Node* const new_lastindex = a->LoadFixedArrayElement(
461 match_indices,
462 a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1));
463
464 StoreLastIndex(a, context, has_initialmap, regexp, new_lastindex);
465 a->Goto(&construct_result);
466
467 a->Bind(&construct_result);
468 {
469 Node* result = ConstructNewResultFromMatchInfo(isolate, a, context,
470 match_indices, string);
471 var_result.Bind(result);
472 a->Goto(&out);
473 }
474 }
475
476 a->Bind(&out);
477 return var_result.value();
478 }
479
480 } // namespace
481
482 // ES#sec-regexp.prototype.exec
483 // RegExp.prototype.exec ( string )
Generate_RegExpPrototypeExec(CodeStubAssembler * a)484 void Builtins::Generate_RegExpPrototypeExec(CodeStubAssembler* a) {
485 typedef compiler::Node Node;
486
487 Node* const maybe_receiver = a->Parameter(0);
488 Node* const maybe_string = a->Parameter(1);
489 Node* const context = a->Parameter(4);
490
491 Node* const result =
492 RegExpPrototypeExecInternal(a, context, maybe_receiver, maybe_string);
493 a->Return(result);
494 }
495
496 namespace {
497
ThrowIfNotJSReceiver(CodeStubAssembler * a,Isolate * isolate,compiler::Node * context,compiler::Node * value,MessageTemplate::Template msg_template,char const * method_name)498 compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
499 compiler::Node* context,
500 compiler::Node* value,
501 MessageTemplate::Template msg_template,
502 char const* method_name) {
503 typedef compiler::Node Node;
504 typedef CodeStubAssembler::Label Label;
505 typedef CodeStubAssembler::Variable Variable;
506
507 Label out(a), throw_exception(a, Label::kDeferred);
508 Variable var_value_map(a, MachineRepresentation::kTagged);
509
510 a->GotoIf(a->TaggedIsSmi(value), &throw_exception);
511
512 // Load the instance type of the {value}.
513 var_value_map.Bind(a->LoadMap(value));
514 Node* const value_instance_type =
515 a->LoadMapInstanceType(var_value_map.value());
516
517 a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out,
518 &throw_exception);
519
520 // The {value} is not a compatible receiver for this method.
521 a->Bind(&throw_exception);
522 {
523 Node* const message_id = a->SmiConstant(Smi::FromInt(msg_template));
524 Node* const method_name_str = a->HeapConstant(
525 isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED));
526
527 Callable callable = CodeFactory::ToString(isolate);
528 Node* const value_str = a->CallStub(callable, context, value);
529
530 a->CallRuntime(Runtime::kThrowTypeError, context, message_id,
531 method_name_str, value_str);
532 var_value_map.Bind(a->UndefinedConstant());
533 a->Goto(&out); // Never reached.
534 }
535
536 a->Bind(&out);
537 return var_value_map.value();
538 }
539
IsInitialRegExpMap(CodeStubAssembler * a,compiler::Node * context,compiler::Node * map)540 compiler::Node* IsInitialRegExpMap(CodeStubAssembler* a,
541 compiler::Node* context,
542 compiler::Node* map) {
543 typedef compiler::Node Node;
544
545 Node* const native_context = a->LoadNativeContext(context);
546 Node* const regexp_fun =
547 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
548 Node* const initial_map =
549 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
550 Node* const has_initialmap = a->WordEqual(map, initial_map);
551
552 return has_initialmap;
553 }
554
555 // RegExp fast path implementations rely on unmodified JSRegExp instances.
556 // We use a fairly coarse granularity for this and simply check whether both
557 // the regexp itself is unmodified (i.e. its map has not changed) and its
558 // prototype is unmodified.
BranchIfFastPath(CodeStubAssembler * a,compiler::Node * context,compiler::Node * map,CodeStubAssembler::Label * if_isunmodified,CodeStubAssembler::Label * if_ismodified)559 void BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context,
560 compiler::Node* map,
561 CodeStubAssembler::Label* if_isunmodified,
562 CodeStubAssembler::Label* if_ismodified) {
563 typedef compiler::Node Node;
564
565 Node* const native_context = a->LoadNativeContext(context);
566 Node* const regexp_fun =
567 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
568 Node* const initial_map =
569 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
570 Node* const has_initialmap = a->WordEqual(map, initial_map);
571
572 a->GotoUnless(has_initialmap, if_ismodified);
573
574 Node* const initial_proto_initial_map = a->LoadContextElement(
575 native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX);
576 Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map));
577 Node* const proto_has_initialmap =
578 a->WordEqual(proto_map, initial_proto_initial_map);
579
580 // TODO(ishell): Update this check once map changes for constant field
581 // tracking are landing.
582
583 a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
584 }
585
586 } // namespace
587
Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler * a)588 void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) {
589 typedef CodeStubAssembler::Variable Variable;
590 typedef CodeStubAssembler::Label Label;
591 typedef compiler::Node Node;
592
593 Node* const receiver = a->Parameter(0);
594 Node* const context = a->Parameter(3);
595
596 Isolate* isolate = a->isolate();
597 Node* const int_zero = a->IntPtrConstant(0);
598 Node* const int_one = a->IntPtrConstant(1);
599
600 Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver,
601 MessageTemplate::kRegExpNonObject,
602 "RegExp.prototype.flags");
603
604 Variable var_length(a, MachineType::PointerRepresentation());
605 Variable var_flags(a, MachineType::PointerRepresentation());
606
607 // First, count the number of characters we will need and check which flags
608 // are set.
609
610 var_length.Bind(int_zero);
611
612 Label if_isunmodifiedjsregexp(a),
613 if_isnotunmodifiedjsregexp(a, Label::kDeferred);
614 a->Branch(IsInitialRegExpMap(a, context, map), &if_isunmodifiedjsregexp,
615 &if_isnotunmodifiedjsregexp);
616
617 Label construct_string(a);
618 a->Bind(&if_isunmodifiedjsregexp);
619 {
620 // Refer to JSRegExp's flag property on the fast-path.
621 Node* const flags_smi =
622 a->LoadObjectField(receiver, JSRegExp::kFlagsOffset);
623 Node* const flags_intptr = a->SmiUntag(flags_smi);
624 var_flags.Bind(flags_intptr);
625
626 Label label_global(a), label_ignorecase(a), label_multiline(a),
627 label_unicode(a), label_sticky(a);
628
629 #define CASE_FOR_FLAG(FLAG, LABEL, NEXT_LABEL) \
630 do { \
631 a->Bind(&LABEL); \
632 Node* const mask = a->IntPtrConstant(FLAG); \
633 a->GotoIf(a->WordEqual(a->WordAnd(flags_intptr, mask), int_zero), \
634 &NEXT_LABEL); \
635 var_length.Bind(a->IntPtrAdd(var_length.value(), int_one)); \
636 a->Goto(&NEXT_LABEL); \
637 } while (false)
638
639 a->Goto(&label_global);
640 CASE_FOR_FLAG(JSRegExp::kGlobal, label_global, label_ignorecase);
641 CASE_FOR_FLAG(JSRegExp::kIgnoreCase, label_ignorecase, label_multiline);
642 CASE_FOR_FLAG(JSRegExp::kMultiline, label_multiline, label_unicode);
643 CASE_FOR_FLAG(JSRegExp::kUnicode, label_unicode, label_sticky);
644 CASE_FOR_FLAG(JSRegExp::kSticky, label_sticky, construct_string);
645 #undef CASE_FOR_FLAG
646 }
647
648 a->Bind(&if_isnotunmodifiedjsregexp);
649 {
650 // Fall back to GetProperty stub on the slow-path.
651 var_flags.Bind(int_zero);
652
653 Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
654 Label label_global(a), label_ignorecase(a), label_multiline(a),
655 label_unicode(a), label_sticky(a);
656
657 #define CASE_FOR_FLAG(NAME, FLAG, LABEL, NEXT_LABEL) \
658 do { \
659 a->Bind(&LABEL); \
660 Node* const name = \
661 a->HeapConstant(isolate->factory()->NewStringFromAsciiChecked(NAME)); \
662 Node* const flag = \
663 a->CallStub(getproperty_callable, context, receiver, name); \
664 Label if_isflagset(a); \
665 a->BranchIfToBooleanIsTrue(flag, &if_isflagset, &NEXT_LABEL); \
666 a->Bind(&if_isflagset); \
667 var_length.Bind(a->IntPtrAdd(var_length.value(), int_one)); \
668 var_flags.Bind(a->WordOr(var_flags.value(), a->IntPtrConstant(FLAG))); \
669 a->Goto(&NEXT_LABEL); \
670 } while (false)
671
672 a->Goto(&label_global);
673 CASE_FOR_FLAG("global", JSRegExp::kGlobal, label_global, label_ignorecase);
674 CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase, label_ignorecase,
675 label_multiline);
676 CASE_FOR_FLAG("multiline", JSRegExp::kMultiline, label_multiline,
677 label_unicode);
678 CASE_FOR_FLAG("unicode", JSRegExp::kUnicode, label_unicode, label_sticky);
679 CASE_FOR_FLAG("sticky", JSRegExp::kSticky, label_sticky, construct_string);
680 #undef CASE_FOR_FLAG
681 }
682
683 // Allocate a string of the required length and fill it with the corresponding
684 // char for each set flag.
685
686 a->Bind(&construct_string);
687 {
688 Node* const result =
689 a->AllocateSeqOneByteString(context, var_length.value());
690 Node* const flags_intptr = var_flags.value();
691
692 Variable var_offset(a, MachineType::PointerRepresentation());
693 var_offset.Bind(
694 a->IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag));
695
696 Label label_global(a), label_ignorecase(a), label_multiline(a),
697 label_unicode(a), label_sticky(a), out(a);
698
699 #define CASE_FOR_FLAG(FLAG, CHAR, LABEL, NEXT_LABEL) \
700 do { \
701 a->Bind(&LABEL); \
702 Node* const mask = a->IntPtrConstant(FLAG); \
703 a->GotoIf(a->WordEqual(a->WordAnd(flags_intptr, mask), int_zero), \
704 &NEXT_LABEL); \
705 Node* const value = a->IntPtrConstant(CHAR); \
706 a->StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \
707 var_offset.value(), value); \
708 var_offset.Bind(a->IntPtrAdd(var_offset.value(), int_one)); \
709 a->Goto(&NEXT_LABEL); \
710 } while (false)
711
712 a->Goto(&label_global);
713 CASE_FOR_FLAG(JSRegExp::kGlobal, 'g', label_global, label_ignorecase);
714 CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i', label_ignorecase,
715 label_multiline);
716 CASE_FOR_FLAG(JSRegExp::kMultiline, 'm', label_multiline, label_unicode);
717 CASE_FOR_FLAG(JSRegExp::kUnicode, 'u', label_unicode, label_sticky);
718 CASE_FOR_FLAG(JSRegExp::kSticky, 'y', label_sticky, out);
719 #undef CASE_FOR_FLAG
720
721 a->Bind(&out);
722 a->Return(result);
723 }
724 }
725
726 // ES6 21.2.5.10.
BUILTIN(RegExpPrototypeSourceGetter)727 BUILTIN(RegExpPrototypeSourceGetter) {
728 HandleScope scope(isolate);
729
730 Handle<Object> recv = args.receiver();
731 if (!recv->IsJSRegExp()) {
732 Handle<JSFunction> regexp_fun = isolate->regexp_function();
733 if (*recv == regexp_fun->prototype()) {
734 isolate->CountUsage(v8::Isolate::kRegExpPrototypeSourceGetter);
735 return *isolate->factory()->NewStringFromAsciiChecked("(?:)");
736 }
737 THROW_NEW_ERROR_RETURN_FAILURE(
738 isolate, NewTypeError(MessageTemplate::kRegExpNonRegExp,
739 isolate->factory()->NewStringFromAsciiChecked(
740 "RegExp.prototype.source")));
741 }
742
743 Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(recv);
744 return regexp->source();
745 }
746
BUILTIN(RegExpPrototypeToString)747 BUILTIN(RegExpPrototypeToString) {
748 HandleScope scope(isolate);
749 CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.toString");
750
751 if (*recv == isolate->regexp_function()->prototype()) {
752 isolate->CountUsage(v8::Isolate::kRegExpPrototypeToString);
753 }
754
755 IncrementalStringBuilder builder(isolate);
756
757 builder.AppendCharacter('/');
758 {
759 Handle<Object> source;
760 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
761 isolate, source,
762 JSReceiver::GetProperty(recv, isolate->factory()->source_string()));
763 Handle<String> source_str;
764 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, source_str,
765 Object::ToString(isolate, source));
766 builder.AppendString(source_str);
767 }
768
769 builder.AppendCharacter('/');
770 {
771 Handle<Object> flags;
772 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
773 isolate, flags,
774 JSReceiver::GetProperty(recv, isolate->factory()->flags_string()));
775 Handle<String> flags_str;
776 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags_str,
777 Object::ToString(isolate, flags));
778 builder.AppendString(flags_str);
779 }
780
781 RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
782 }
783
784 // ES6 21.2.4.2.
BUILTIN(RegExpPrototypeSpeciesGetter)785 BUILTIN(RegExpPrototypeSpeciesGetter) {
786 HandleScope scope(isolate);
787 return *args.receiver();
788 }
789
790 namespace {
791
792 // Fast-path implementation for flag checks on an unmodified JSRegExp instance.
FastFlagGetter(CodeStubAssembler * a,compiler::Node * const regexp,JSRegExp::Flag flag)793 compiler::Node* FastFlagGetter(CodeStubAssembler* a,
794 compiler::Node* const regexp,
795 JSRegExp::Flag flag) {
796 typedef compiler::Node Node;
797
798 Node* const smi_zero = a->SmiConstant(Smi::kZero);
799 Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
800 Node* const mask = a->SmiConstant(Smi::FromInt(flag));
801 Node* const is_flag_set = a->WordNotEqual(a->WordAnd(flags, mask), smi_zero);
802
803 return is_flag_set;
804 }
805
Generate_FlagGetter(CodeStubAssembler * a,JSRegExp::Flag flag,v8::Isolate::UseCounterFeature counter,const char * method_name)806 void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
807 v8::Isolate::UseCounterFeature counter,
808 const char* method_name) {
809 typedef CodeStubAssembler::Label Label;
810 typedef compiler::Node Node;
811
812 Node* const receiver = a->Parameter(0);
813 Node* const context = a->Parameter(3);
814
815 Isolate* isolate = a->isolate();
816
817 // Check whether we have an unmodified regexp instance.
818 Label if_isunmodifiedjsregexp(a),
819 if_isnotunmodifiedjsregexp(a, Label::kDeferred);
820
821 a->GotoIf(a->TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp);
822
823 Node* const receiver_map = a->LoadMap(receiver);
824 Node* const instance_type = a->LoadMapInstanceType(receiver_map);
825
826 a->Branch(a->Word32Equal(instance_type, a->Int32Constant(JS_REGEXP_TYPE)),
827 &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp);
828
829 a->Bind(&if_isunmodifiedjsregexp);
830 {
831 // Refer to JSRegExp's flag property on the fast-path.
832 Node* const is_flag_set = FastFlagGetter(a, receiver, flag);
833 a->Return(a->Select(is_flag_set, a->TrueConstant(), a->FalseConstant()));
834 }
835
836 a->Bind(&if_isnotunmodifiedjsregexp);
837 {
838 Node* const native_context = a->LoadNativeContext(context);
839 Node* const regexp_fun =
840 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
841 Node* const initial_map = a->LoadObjectField(
842 regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
843 Node* const initial_prototype = a->LoadMapPrototype(initial_map);
844
845 Label if_isprototype(a), if_isnotprototype(a);
846 a->Branch(a->WordEqual(receiver, initial_prototype), &if_isprototype,
847 &if_isnotprototype);
848
849 a->Bind(&if_isprototype);
850 {
851 Node* const counter_smi = a->SmiConstant(Smi::FromInt(counter));
852 a->CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
853 a->Return(a->UndefinedConstant());
854 }
855
856 a->Bind(&if_isnotprototype);
857 {
858 Node* const message_id =
859 a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
860 Node* const method_name_str = a->HeapConstant(
861 isolate->factory()->NewStringFromAsciiChecked(method_name));
862 a->CallRuntime(Runtime::kThrowTypeError, context, message_id,
863 method_name_str);
864 a->Return(a->UndefinedConstant()); // Never reached.
865 }
866 }
867 }
868
869 } // namespace
870
871 // ES6 21.2.5.4.
Generate_RegExpPrototypeGlobalGetter(CodeStubAssembler * a)872 void Builtins::Generate_RegExpPrototypeGlobalGetter(CodeStubAssembler* a) {
873 Generate_FlagGetter(a, JSRegExp::kGlobal,
874 v8::Isolate::kRegExpPrototypeOldFlagGetter,
875 "RegExp.prototype.global");
876 }
877
878 // ES6 21.2.5.5.
Generate_RegExpPrototypeIgnoreCaseGetter(CodeStubAssembler * a)879 void Builtins::Generate_RegExpPrototypeIgnoreCaseGetter(CodeStubAssembler* a) {
880 Generate_FlagGetter(a, JSRegExp::kIgnoreCase,
881 v8::Isolate::kRegExpPrototypeOldFlagGetter,
882 "RegExp.prototype.ignoreCase");
883 }
884
885 // ES6 21.2.5.7.
Generate_RegExpPrototypeMultilineGetter(CodeStubAssembler * a)886 void Builtins::Generate_RegExpPrototypeMultilineGetter(CodeStubAssembler* a) {
887 Generate_FlagGetter(a, JSRegExp::kMultiline,
888 v8::Isolate::kRegExpPrototypeOldFlagGetter,
889 "RegExp.prototype.multiline");
890 }
891
892 // ES6 21.2.5.12.
Generate_RegExpPrototypeStickyGetter(CodeStubAssembler * a)893 void Builtins::Generate_RegExpPrototypeStickyGetter(CodeStubAssembler* a) {
894 Generate_FlagGetter(a, JSRegExp::kSticky,
895 v8::Isolate::kRegExpPrototypeStickyGetter,
896 "RegExp.prototype.sticky");
897 }
898
899 // ES6 21.2.5.15.
Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler * a)900 void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) {
901 Generate_FlagGetter(a, JSRegExp::kUnicode,
902 v8::Isolate::kRegExpPrototypeUnicodeGetter,
903 "RegExp.prototype.unicode");
904 }
905
906 // The properties $1..$9 are the first nine capturing substrings of the last
907 // successful match, or ''. The function RegExpMakeCaptureGetter will be
908 // called with indices from 1 to 9.
909 #define DEFINE_CAPTURE_GETTER(i) \
910 BUILTIN(RegExpCapture##i##Getter) { \
911 HandleScope scope(isolate); \
912 return *RegExpUtils::GenericCaptureGetter( \
913 isolate, isolate->regexp_last_match_info(), i); \
914 }
915 DEFINE_CAPTURE_GETTER(1)
916 DEFINE_CAPTURE_GETTER(2)
917 DEFINE_CAPTURE_GETTER(3)
918 DEFINE_CAPTURE_GETTER(4)
919 DEFINE_CAPTURE_GETTER(5)
920 DEFINE_CAPTURE_GETTER(6)
921 DEFINE_CAPTURE_GETTER(7)
922 DEFINE_CAPTURE_GETTER(8)
923 DEFINE_CAPTURE_GETTER(9)
924 #undef DEFINE_CAPTURE_GETTER
925
926 // The properties `input` and `$_` are aliases for each other. When this
927 // value is set, the value it is set to is coerced to a string.
928 // Getter and setter for the input.
929
BUILTIN(RegExpInputGetter)930 BUILTIN(RegExpInputGetter) {
931 HandleScope scope(isolate);
932 Handle<Object> obj(isolate->regexp_last_match_info()->LastInput(), isolate);
933 return obj->IsUndefined(isolate) ? isolate->heap()->empty_string()
934 : String::cast(*obj);
935 }
936
BUILTIN(RegExpInputSetter)937 BUILTIN(RegExpInputSetter) {
938 HandleScope scope(isolate);
939 Handle<Object> value = args.atOrUndefined(isolate, 1);
940 Handle<String> str;
941 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str,
942 Object::ToString(isolate, value));
943 isolate->regexp_last_match_info()->SetLastInput(*str);
944 return isolate->heap()->undefined_value();
945 }
946
947 // Getters for the static properties lastMatch, lastParen, leftContext, and
948 // rightContext of the RegExp constructor. The properties are computed based
949 // on the captures array of the last successful match and the subject string
950 // of the last successful match.
BUILTIN(RegExpLastMatchGetter)951 BUILTIN(RegExpLastMatchGetter) {
952 HandleScope scope(isolate);
953 return *RegExpUtils::GenericCaptureGetter(
954 isolate, isolate->regexp_last_match_info(), 0);
955 }
956
BUILTIN(RegExpLastParenGetter)957 BUILTIN(RegExpLastParenGetter) {
958 HandleScope scope(isolate);
959 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info();
960 const int length = match_info->NumberOfCaptureRegisters();
961 if (length <= 2) return isolate->heap()->empty_string(); // No captures.
962
963 DCHECK_EQ(0, length % 2);
964 const int last_capture = (length / 2) - 1;
965
966 // We match the SpiderMonkey behavior: return the substring defined by the
967 // last pair (after the first pair) of elements of the capture array even if
968 // it is empty.
969 return *RegExpUtils::GenericCaptureGetter(isolate, match_info, last_capture);
970 }
971
BUILTIN(RegExpLeftContextGetter)972 BUILTIN(RegExpLeftContextGetter) {
973 HandleScope scope(isolate);
974 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info();
975 const int start_index = match_info->Capture(0);
976 Handle<String> last_subject(match_info->LastSubject());
977 return *isolate->factory()->NewSubString(last_subject, 0, start_index);
978 }
979
BUILTIN(RegExpRightContextGetter)980 BUILTIN(RegExpRightContextGetter) {
981 HandleScope scope(isolate);
982 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info();
983 const int start_index = match_info->Capture(1);
984 Handle<String> last_subject(match_info->LastSubject());
985 const int len = last_subject->length();
986 return *isolate->factory()->NewSubString(last_subject, start_index, len);
987 }
988
989 namespace {
990
991 // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
RegExpExec(CodeStubAssembler * a,compiler::Node * context,compiler::Node * recv,compiler::Node * string)992 compiler::Node* RegExpExec(CodeStubAssembler* a, compiler::Node* context,
993 compiler::Node* recv, compiler::Node* string) {
994 typedef CodeStubAssembler::Variable Variable;
995 typedef CodeStubAssembler::Label Label;
996 typedef compiler::Node Node;
997
998 Isolate* isolate = a->isolate();
999
1000 Node* const null = a->NullConstant();
1001
1002 Variable var_result(a, MachineRepresentation::kTagged);
1003 Label out(a), call_builtin_exec(a), slow_path(a, Label::kDeferred);
1004
1005 Node* const map = a->LoadMap(recv);
1006 BranchIfFastPath(a, context, map, &call_builtin_exec, &slow_path);
1007
1008 a->Bind(&call_builtin_exec);
1009 {
1010 Node* const result = RegExpPrototypeExecInternal(a, context, recv, string);
1011 var_result.Bind(result);
1012 a->Goto(&out);
1013 }
1014
1015 a->Bind(&slow_path);
1016 {
1017 // Take the slow path of fetching the exec property, calling it, and
1018 // verifying its return value.
1019
1020 // Get the exec property.
1021 Node* const name = a->HeapConstant(isolate->factory()->exec_string());
1022 Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
1023 Node* const exec = a->CallStub(getproperty_callable, context, recv, name);
1024
1025 // Is {exec} callable?
1026 Label if_iscallable(a), if_isnotcallable(a);
1027
1028 a->GotoIf(a->TaggedIsSmi(exec), &if_isnotcallable);
1029
1030 Node* const exec_map = a->LoadMap(exec);
1031 a->Branch(a->IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable);
1032
1033 a->Bind(&if_iscallable);
1034 {
1035 Callable call_callable = CodeFactory::Call(isolate);
1036 Node* const result =
1037 a->CallJS(call_callable, context, exec, recv, string);
1038
1039 var_result.Bind(result);
1040 a->GotoIf(a->WordEqual(result, null), &out);
1041
1042 ThrowIfNotJSReceiver(a, isolate, context, result,
1043 MessageTemplate::kInvalidRegExpExecResult, "unused");
1044
1045 a->Goto(&out);
1046 }
1047
1048 a->Bind(&if_isnotcallable);
1049 {
1050 a->ThrowIfNotInstanceType(context, recv, JS_REGEXP_TYPE,
1051 "RegExp.prototype.exec");
1052 a->Goto(&call_builtin_exec);
1053 }
1054 }
1055
1056 a->Bind(&out);
1057 return var_result.value();
1058 }
1059
1060 } // namespace
1061
1062 // ES#sec-regexp.prototype.test
1063 // RegExp.prototype.test ( S )
Generate_RegExpPrototypeTest(CodeStubAssembler * a)1064 void Builtins::Generate_RegExpPrototypeTest(CodeStubAssembler* a) {
1065 typedef compiler::Node Node;
1066
1067 Isolate* const isolate = a->isolate();
1068
1069 Node* const maybe_receiver = a->Parameter(0);
1070 Node* const maybe_string = a->Parameter(1);
1071 Node* const context = a->Parameter(4);
1072
1073 // Ensure {maybe_receiver} is a JSReceiver.
1074 ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
1075 MessageTemplate::kIncompatibleMethodReceiver,
1076 "RegExp.prototype.test");
1077 Node* const receiver = maybe_receiver;
1078
1079 // Convert {maybe_string} to a String.
1080 Node* const string = a->ToString(context, maybe_string);
1081
1082 // Call exec.
1083 Node* const match_indices = RegExpExec(a, context, receiver, string);
1084
1085 // Return true iff exec matched successfully.
1086 Node* const result = a->Select(a->WordEqual(match_indices, a->NullConstant()),
1087 a->FalseConstant(), a->TrueConstant());
1088 a->Return(result);
1089 }
1090
1091 // ES#sec-regexp.prototype-@@match
1092 // RegExp.prototype [ @@match ] ( string )
BUILTIN(RegExpPrototypeMatch)1093 BUILTIN(RegExpPrototypeMatch) {
1094 HandleScope scope(isolate);
1095 CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@match");
1096
1097 Handle<Object> string_obj = args.atOrUndefined(isolate, 1);
1098
1099 Handle<String> string;
1100 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string,
1101 Object::ToString(isolate, string_obj));
1102
1103 Handle<Object> global_obj;
1104 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1105 isolate, global_obj,
1106 JSReceiver::GetProperty(recv, isolate->factory()->global_string()));
1107 const bool global = global_obj->BooleanValue();
1108
1109 if (!global) {
1110 RETURN_RESULT_OR_FAILURE(
1111 isolate,
1112 RegExpUtils::RegExpExec(isolate, recv, string,
1113 isolate->factory()->undefined_value()));
1114 }
1115
1116 Handle<Object> unicode_obj;
1117 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1118 isolate, unicode_obj,
1119 JSReceiver::GetProperty(recv, isolate->factory()->unicode_string()));
1120 const bool unicode = unicode_obj->BooleanValue();
1121
1122 RETURN_FAILURE_ON_EXCEPTION(isolate,
1123 RegExpUtils::SetLastIndex(isolate, recv, 0));
1124
1125 static const int kInitialArraySize = 8;
1126 Handle<FixedArray> elems =
1127 isolate->factory()->NewFixedArrayWithHoles(kInitialArraySize);
1128
1129 int n = 0;
1130 for (;; n++) {
1131 Handle<Object> result;
1132 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1133 isolate, result,
1134 RegExpUtils::RegExpExec(isolate, recv, string,
1135 isolate->factory()->undefined_value()));
1136
1137 if (result->IsNull(isolate)) {
1138 if (n == 0) return isolate->heap()->null_value();
1139 break;
1140 }
1141
1142 Handle<Object> match_obj;
1143 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,
1144 Object::GetElement(isolate, result, 0));
1145
1146 Handle<String> match;
1147 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,
1148 Object::ToString(isolate, match_obj));
1149
1150 elems = FixedArray::SetAndGrow(elems, n, match);
1151
1152 if (match->length() == 0) {
1153 RETURN_FAILURE_ON_EXCEPTION(isolate, RegExpUtils::SetAdvancedStringIndex(
1154 isolate, recv, string, unicode));
1155 }
1156 }
1157
1158 elems->Shrink(n);
1159 return *isolate->factory()->NewJSArrayWithElements(elems);
1160 }
1161
1162 namespace {
1163
Generate_RegExpPrototypeSearchBody(CodeStubAssembler * a,compiler::Node * const receiver,compiler::Node * const string,compiler::Node * const context,bool is_fastpath)1164 void Generate_RegExpPrototypeSearchBody(CodeStubAssembler* a,
1165 compiler::Node* const receiver,
1166 compiler::Node* const string,
1167 compiler::Node* const context,
1168 bool is_fastpath) {
1169 typedef CodeStubAssembler::Label Label;
1170 typedef compiler::Node Node;
1171
1172 Isolate* const isolate = a->isolate();
1173
1174 Node* const smi_zero = a->SmiConstant(Smi::kZero);
1175
1176 // Grab the initial value of last index.
1177 Node* const previous_last_index =
1178 is_fastpath ? FastLoadLastIndex(a, context, receiver)
1179 : SlowLoadLastIndex(a, context, receiver);
1180
1181 // Ensure last index is 0.
1182 if (is_fastpath) {
1183 FastStoreLastIndex(a, context, receiver, smi_zero);
1184 } else {
1185 Label next(a);
1186 a->GotoIf(a->SameValue(previous_last_index, smi_zero, context), &next);
1187
1188 SlowStoreLastIndex(a, context, receiver, smi_zero);
1189 a->Goto(&next);
1190 a->Bind(&next);
1191 }
1192
1193 // Call exec.
1194 Node* const match_indices =
1195 is_fastpath ? RegExpPrototypeExecInternal(a, context, receiver, string)
1196 : RegExpExec(a, context, receiver, string);
1197
1198 // Reset last index if necessary.
1199 if (is_fastpath) {
1200 FastStoreLastIndex(a, context, receiver, previous_last_index);
1201 } else {
1202 Label next(a);
1203 Node* const current_last_index = SlowLoadLastIndex(a, context, receiver);
1204
1205 a->GotoIf(a->SameValue(current_last_index, previous_last_index, context),
1206 &next);
1207
1208 SlowStoreLastIndex(a, context, receiver, previous_last_index);
1209 a->Goto(&next);
1210 a->Bind(&next);
1211 }
1212
1213 // Return -1 if no match was found.
1214 {
1215 Label next(a);
1216 a->GotoUnless(a->WordEqual(match_indices, a->NullConstant()), &next);
1217 a->Return(a->SmiConstant(-1));
1218 a->Bind(&next);
1219 }
1220
1221 // Return the index of the match.
1222 {
1223 Label fast_result(a), slow_result(a, Label::kDeferred);
1224
1225 Node* const native_context = a->LoadNativeContext(context);
1226 Node* const initial_regexp_result_map =
1227 a->LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
1228 Node* const match_indices_map = a->LoadMap(match_indices);
1229
1230 a->Branch(a->WordEqual(match_indices_map, initial_regexp_result_map),
1231 &fast_result, &slow_result);
1232
1233 a->Bind(&fast_result);
1234 {
1235 Node* const index =
1236 a->LoadObjectField(match_indices, JSRegExpResult::kIndexOffset,
1237 MachineType::AnyTagged());
1238 a->Return(index);
1239 }
1240
1241 a->Bind(&slow_result);
1242 {
1243 Node* const name = a->HeapConstant(isolate->factory()->index_string());
1244 Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
1245 Node* const index =
1246 a->CallStub(getproperty_callable, context, match_indices, name);
1247 a->Return(index);
1248 }
1249 }
1250 }
1251
1252 } // namespace
1253
1254 // ES#sec-regexp.prototype-@@search
1255 // RegExp.prototype [ @@search ] ( string )
Generate_RegExpPrototypeSearch(CodeStubAssembler * a)1256 void Builtins::Generate_RegExpPrototypeSearch(CodeStubAssembler* a) {
1257 typedef CodeStubAssembler::Label Label;
1258 typedef compiler::Node Node;
1259
1260 Isolate* const isolate = a->isolate();
1261
1262 Node* const maybe_receiver = a->Parameter(0);
1263 Node* const maybe_string = a->Parameter(1);
1264 Node* const context = a->Parameter(4);
1265
1266 // Ensure {maybe_receiver} is a JSReceiver.
1267 Node* const map =
1268 ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
1269 MessageTemplate::kIncompatibleMethodReceiver,
1270 "RegExp.prototype.@@search");
1271 Node* const receiver = maybe_receiver;
1272
1273 // Convert {maybe_string} to a String.
1274 Node* const string = a->ToString(context, maybe_string);
1275
1276 Label fast_path(a), slow_path(a);
1277 BranchIfFastPath(a, context, map, &fast_path, &slow_path);
1278
1279 a->Bind(&fast_path);
1280 Generate_RegExpPrototypeSearchBody(a, receiver, string, context, true);
1281
1282 a->Bind(&slow_path);
1283 Generate_RegExpPrototypeSearchBody(a, receiver, string, context, false);
1284 }
1285
1286 namespace {
1287
ToUint32(Isolate * isolate,Handle<Object> object,uint32_t * out)1288 MUST_USE_RESULT MaybeHandle<Object> ToUint32(Isolate* isolate,
1289 Handle<Object> object,
1290 uint32_t* out) {
1291 if (object->IsUndefined(isolate)) {
1292 *out = kMaxUInt32;
1293 return object;
1294 }
1295
1296 Handle<Object> number;
1297 ASSIGN_RETURN_ON_EXCEPTION(isolate, number, Object::ToNumber(object), Object);
1298 *out = NumberToUint32(*number);
1299 return object;
1300 }
1301
AtSurrogatePair(Isolate * isolate,Handle<String> string,int index)1302 bool AtSurrogatePair(Isolate* isolate, Handle<String> string, int index) {
1303 if (index + 1 >= string->length()) return false;
1304 const uint16_t first = string->Get(index);
1305 if (first < 0xD800 || first > 0xDBFF) return false;
1306 const uint16_t second = string->Get(index + 1);
1307 return (second >= 0xDC00 && second <= 0xDFFF);
1308 }
1309
NewJSArrayWithElements(Isolate * isolate,Handle<FixedArray> elems,int num_elems)1310 Handle<JSArray> NewJSArrayWithElements(Isolate* isolate,
1311 Handle<FixedArray> elems,
1312 int num_elems) {
1313 elems->Shrink(num_elems);
1314 return isolate->factory()->NewJSArrayWithElements(elems);
1315 }
1316
RegExpSplit(Isolate * isolate,Handle<JSRegExp> regexp,Handle<String> string,Handle<Object> limit_obj)1317 MaybeHandle<JSArray> RegExpSplit(Isolate* isolate, Handle<JSRegExp> regexp,
1318 Handle<String> string,
1319 Handle<Object> limit_obj) {
1320 Factory* factory = isolate->factory();
1321
1322 uint32_t limit;
1323 RETURN_ON_EXCEPTION(isolate, ToUint32(isolate, limit_obj, &limit), JSArray);
1324
1325 const int length = string->length();
1326
1327 if (limit == 0) return factory->NewJSArray(0);
1328
1329 Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
1330
1331 if (length == 0) {
1332 Handle<Object> match_indices;
1333 ASSIGN_RETURN_ON_EXCEPTION(
1334 isolate, match_indices,
1335 RegExpImpl::Exec(regexp, string, 0, last_match_info), JSArray);
1336
1337 if (!match_indices->IsNull(isolate)) return factory->NewJSArray(0);
1338
1339 Handle<FixedArray> elems = factory->NewUninitializedFixedArray(1);
1340 elems->set(0, *string);
1341 return factory->NewJSArrayWithElements(elems);
1342 }
1343
1344 int current_index = 0;
1345 int start_index = 0;
1346 int start_match = 0;
1347
1348 static const int kInitialArraySize = 8;
1349 Handle<FixedArray> elems = factory->NewFixedArrayWithHoles(kInitialArraySize);
1350 int num_elems = 0;
1351
1352 while (true) {
1353 if (start_index == length) {
1354 Handle<String> substr =
1355 factory->NewSubString(string, current_index, length);
1356 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1357 break;
1358 }
1359
1360 Handle<Object> match_indices_obj;
1361 ASSIGN_RETURN_ON_EXCEPTION(
1362 isolate, match_indices_obj,
1363 RegExpImpl::Exec(regexp, string, start_index,
1364 isolate->regexp_last_match_info()),
1365 JSArray);
1366
1367 if (match_indices_obj->IsNull(isolate)) {
1368 Handle<String> substr =
1369 factory->NewSubString(string, current_index, length);
1370 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1371 break;
1372 }
1373
1374 auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
1375
1376 start_match = match_indices->Capture(0);
1377
1378 if (start_match == length) {
1379 Handle<String> substr =
1380 factory->NewSubString(string, current_index, length);
1381 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1382 break;
1383 }
1384
1385 const int end_index = match_indices->Capture(1);
1386
1387 if (start_index == end_index && end_index == current_index) {
1388 const bool unicode = (regexp->GetFlags() & JSRegExp::kUnicode) != 0;
1389 if (unicode && AtSurrogatePair(isolate, string, start_index)) {
1390 start_index += 2;
1391 } else {
1392 start_index += 1;
1393 }
1394 continue;
1395 }
1396
1397 {
1398 Handle<String> substr =
1399 factory->NewSubString(string, current_index, start_match);
1400 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1401 }
1402
1403 if (static_cast<uint32_t>(num_elems) == limit) break;
1404
1405 for (int i = 2; i < match_indices->NumberOfCaptureRegisters(); i += 2) {
1406 const int start = match_indices->Capture(i);
1407 const int end = match_indices->Capture(i + 1);
1408
1409 if (end != -1) {
1410 Handle<String> substr = factory->NewSubString(string, start, end);
1411 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1412 } else {
1413 elems = FixedArray::SetAndGrow(elems, num_elems++,
1414 factory->undefined_value());
1415 }
1416
1417 if (static_cast<uint32_t>(num_elems) == limit) {
1418 return NewJSArrayWithElements(isolate, elems, num_elems);
1419 }
1420 }
1421
1422 start_index = current_index = end_index;
1423 }
1424
1425 return NewJSArrayWithElements(isolate, elems, num_elems);
1426 }
1427
1428 // ES##sec-speciesconstructor
1429 // SpeciesConstructor ( O, defaultConstructor )
SpeciesConstructor(Isolate * isolate,Handle<JSReceiver> recv,Handle<JSFunction> default_ctor)1430 MUST_USE_RESULT MaybeHandle<Object> SpeciesConstructor(
1431 Isolate* isolate, Handle<JSReceiver> recv,
1432 Handle<JSFunction> default_ctor) {
1433 Handle<Object> ctor_obj;
1434 ASSIGN_RETURN_ON_EXCEPTION(
1435 isolate, ctor_obj,
1436 JSObject::GetProperty(recv, isolate->factory()->constructor_string()),
1437 Object);
1438
1439 if (ctor_obj->IsUndefined(isolate)) return default_ctor;
1440
1441 if (!ctor_obj->IsJSReceiver()) {
1442 THROW_NEW_ERROR(isolate,
1443 NewTypeError(MessageTemplate::kConstructorNotReceiver),
1444 Object);
1445 }
1446
1447 Handle<JSReceiver> ctor = Handle<JSReceiver>::cast(ctor_obj);
1448
1449 Handle<Object> species;
1450 ASSIGN_RETURN_ON_EXCEPTION(
1451 isolate, species,
1452 JSObject::GetProperty(ctor, isolate->factory()->species_symbol()),
1453 Object);
1454
1455 if (species->IsNull(isolate) || species->IsUndefined(isolate)) {
1456 return default_ctor;
1457 }
1458
1459 if (species->IsConstructor()) return species;
1460
1461 THROW_NEW_ERROR(
1462 isolate, NewTypeError(MessageTemplate::kSpeciesNotConstructor), Object);
1463 }
1464
1465 } // namespace
1466
1467 // ES#sec-regexp.prototype-@@split
1468 // RegExp.prototype [ @@split ] ( string, limit )
BUILTIN(RegExpPrototypeSplit)1469 BUILTIN(RegExpPrototypeSplit) {
1470 HandleScope scope(isolate);
1471 CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@split");
1472
1473 Factory* factory = isolate->factory();
1474
1475 Handle<Object> string_obj = args.atOrUndefined(isolate, 1);
1476 Handle<Object> limit_obj = args.atOrUndefined(isolate, 2);
1477
1478 Handle<String> string;
1479 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string,
1480 Object::ToString(isolate, string_obj));
1481
1482 if (RegExpUtils::IsUnmodifiedRegExp(isolate, recv)) {
1483 RETURN_RESULT_OR_FAILURE(
1484 isolate,
1485 RegExpSplit(isolate, Handle<JSRegExp>::cast(recv), string, limit_obj));
1486 }
1487
1488 Handle<JSFunction> regexp_fun = isolate->regexp_function();
1489 Handle<Object> ctor;
1490 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1491 isolate, ctor, SpeciesConstructor(isolate, recv, regexp_fun));
1492
1493 Handle<Object> flags_obj;
1494 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1495 isolate, flags_obj, JSObject::GetProperty(recv, factory->flags_string()));
1496
1497 Handle<String> flags;
1498 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags,
1499 Object::ToString(isolate, flags_obj));
1500
1501 Handle<String> u_str = factory->LookupSingleCharacterStringFromCode('u');
1502 const bool unicode = (String::IndexOf(isolate, flags, u_str, 0) >= 0);
1503
1504 Handle<String> y_str = factory->LookupSingleCharacterStringFromCode('y');
1505 const bool sticky = (String::IndexOf(isolate, flags, y_str, 0) >= 0);
1506
1507 Handle<String> new_flags = flags;
1508 if (!sticky) {
1509 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, new_flags,
1510 factory->NewConsString(flags, y_str));
1511 }
1512
1513 Handle<JSReceiver> splitter;
1514 {
1515 const int argc = 2;
1516
1517 ScopedVector<Handle<Object>> argv(argc);
1518 argv[0] = recv;
1519 argv[1] = new_flags;
1520
1521 Handle<JSFunction> ctor_fun = Handle<JSFunction>::cast(ctor);
1522 Handle<Object> splitter_obj;
1523 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1524 isolate, splitter_obj, Execution::New(ctor_fun, argc, argv.start()));
1525
1526 splitter = Handle<JSReceiver>::cast(splitter_obj);
1527 }
1528
1529 uint32_t limit;
1530 RETURN_FAILURE_ON_EXCEPTION(isolate, ToUint32(isolate, limit_obj, &limit));
1531
1532 const int length = string->length();
1533
1534 if (limit == 0) return *factory->NewJSArray(0);
1535
1536 if (length == 0) {
1537 Handle<Object> result;
1538 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1539 isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
1540 factory->undefined_value()));
1541
1542 if (!result->IsNull(isolate)) return *factory->NewJSArray(0);
1543
1544 Handle<FixedArray> elems = factory->NewUninitializedFixedArray(1);
1545 elems->set(0, *string);
1546 return *factory->NewJSArrayWithElements(elems);
1547 }
1548
1549 // TODO(jgruber): Wrap this in a helper class.
1550 static const int kInitialArraySize = 8;
1551 Handle<FixedArray> elems = factory->NewFixedArrayWithHoles(kInitialArraySize);
1552 int num_elems = 0;
1553
1554 int string_index = 0;
1555 int prev_string_index = 0;
1556 while (string_index < length) {
1557 RETURN_FAILURE_ON_EXCEPTION(
1558 isolate, RegExpUtils::SetLastIndex(isolate, splitter, string_index));
1559
1560 Handle<Object> result;
1561 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1562 isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
1563 factory->undefined_value()));
1564
1565 if (result->IsNull(isolate)) {
1566 string_index = RegExpUtils::AdvanceStringIndex(isolate, string,
1567 string_index, unicode);
1568 continue;
1569 }
1570
1571 // TODO(jgruber): Extract toLength of some property into function.
1572 Handle<Object> last_index_obj;
1573 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1574 isolate, last_index_obj, RegExpUtils::GetLastIndex(isolate, splitter));
1575
1576 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1577 isolate, last_index_obj, Object::ToLength(isolate, last_index_obj));
1578 const int last_index = Handle<Smi>::cast(last_index_obj)->value();
1579
1580 const int end = std::min(last_index, length);
1581 if (end == prev_string_index) {
1582 string_index = RegExpUtils::AdvanceStringIndex(isolate, string,
1583 string_index, unicode);
1584 continue;
1585 }
1586
1587 {
1588 Handle<String> substr =
1589 factory->NewSubString(string, prev_string_index, string_index);
1590 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1591 if (static_cast<uint32_t>(num_elems) == limit) {
1592 return *NewJSArrayWithElements(isolate, elems, num_elems);
1593 }
1594 }
1595
1596 prev_string_index = end;
1597
1598 Handle<Object> num_captures_obj;
1599 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1600 isolate, num_captures_obj,
1601 Object::GetProperty(result, isolate->factory()->length_string()));
1602
1603 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1604 isolate, num_captures_obj, Object::ToLength(isolate, num_captures_obj));
1605 const int num_captures =
1606 std::max(Handle<Smi>::cast(num_captures_obj)->value(), 0);
1607
1608 for (int i = 1; i < num_captures; i++) {
1609 Handle<Object> capture;
1610 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1611 isolate, capture, Object::GetElement(isolate, result, i));
1612 elems = FixedArray::SetAndGrow(elems, num_elems++, capture);
1613 if (static_cast<uint32_t>(num_elems) == limit) {
1614 return *NewJSArrayWithElements(isolate, elems, num_elems);
1615 }
1616 }
1617
1618 string_index = prev_string_index;
1619 }
1620
1621 {
1622 Handle<String> substr =
1623 factory->NewSubString(string, prev_string_index, length);
1624 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1625 }
1626
1627 return *NewJSArrayWithElements(isolate, elems, num_elems);
1628 }
1629
1630 namespace {
1631
ReplaceGlobalCallableFastPath(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp,compiler::Node * subject_string,compiler::Node * replace_callable)1632 compiler::Node* ReplaceGlobalCallableFastPath(
1633 CodeStubAssembler* a, compiler::Node* context, compiler::Node* regexp,
1634 compiler::Node* subject_string, compiler::Node* replace_callable) {
1635 // The fast path is reached only if {receiver} is a global unmodified
1636 // JSRegExp instance and {replace_callable} is callable.
1637
1638 typedef CodeStubAssembler::Variable Variable;
1639 typedef CodeStubAssembler::Label Label;
1640 typedef compiler::Node Node;
1641
1642 Isolate* const isolate = a->isolate();
1643
1644 Node* const null = a->NullConstant();
1645 Node* const undefined = a->UndefinedConstant();
1646 Node* const int_zero = a->IntPtrConstant(0);
1647 Node* const int_one = a->IntPtrConstant(1);
1648 Node* const smi_zero = a->SmiConstant(Smi::kZero);
1649
1650 Node* const native_context = a->LoadNativeContext(context);
1651
1652 Label out(a);
1653 Variable var_result(a, MachineRepresentation::kTagged);
1654
1655 // Set last index to 0.
1656 FastStoreLastIndex(a, context, regexp, smi_zero);
1657
1658 // Allocate {result_array}.
1659 Node* result_array;
1660 {
1661 ElementsKind kind = FAST_ELEMENTS;
1662 Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context);
1663 Node* const capacity = a->IntPtrConstant(16);
1664 Node* const length = smi_zero;
1665 Node* const allocation_site = nullptr;
1666 CodeStubAssembler::ParameterMode capacity_mode =
1667 CodeStubAssembler::INTPTR_PARAMETERS;
1668
1669 result_array = a->AllocateJSArray(kind, array_map, capacity, length,
1670 allocation_site, capacity_mode);
1671 }
1672
1673 // Call into runtime for RegExpExecMultiple.
1674 Node* last_match_info = a->LoadContextElement(
1675 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1676 Node* const res =
1677 a->CallRuntime(Runtime::kRegExpExecMultiple, context, regexp,
1678 subject_string, last_match_info, result_array);
1679
1680 // Reset last index to 0.
1681 FastStoreLastIndex(a, context, regexp, smi_zero);
1682
1683 // If no matches, return the subject string.
1684 var_result.Bind(subject_string);
1685 a->GotoIf(a->WordEqual(res, null), &out);
1686
1687 // Reload last match info since it might have changed.
1688 last_match_info = a->LoadContextElement(
1689 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1690
1691 Node* const res_length = a->LoadJSArrayLength(res);
1692 Node* const res_elems = a->LoadElements(res);
1693 CSA_ASSERT(a, a->HasInstanceType(res_elems, FIXED_ARRAY_TYPE));
1694
1695 CodeStubAssembler::ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
1696 Node* const num_capture_registers = a->LoadFixedArrayElement(
1697 last_match_info,
1698 a->IntPtrConstant(RegExpMatchInfo::kNumberOfCapturesIndex), 0, mode);
1699
1700 Label if_hasexplicitcaptures(a), if_noexplicitcaptures(a), create_result(a);
1701 a->Branch(a->SmiEqual(num_capture_registers, a->SmiConstant(Smi::FromInt(2))),
1702 &if_noexplicitcaptures, &if_hasexplicitcaptures);
1703
1704 a->Bind(&if_noexplicitcaptures);
1705 {
1706 // If the number of captures is two then there are no explicit captures in
1707 // the regexp, just the implicit capture that captures the whole match. In
1708 // this case we can simplify quite a bit and end up with something faster.
1709 // The builder will consist of some integers that indicate slices of the
1710 // input string and some replacements that were returned from the replace
1711 // function.
1712
1713 Variable var_match_start(a, MachineRepresentation::kTagged);
1714 var_match_start.Bind(smi_zero);
1715
1716 Node* const end = a->SmiUntag(res_length);
1717 Variable var_i(a, MachineType::PointerRepresentation());
1718 var_i.Bind(int_zero);
1719
1720 Variable* vars[] = {&var_i, &var_match_start};
1721 Label loop(a, 2, vars);
1722 a->Goto(&loop);
1723 a->Bind(&loop);
1724 {
1725 Node* const i = var_i.value();
1726 a->GotoUnless(a->IntPtrLessThan(i, end), &create_result);
1727
1728 CodeStubAssembler::ParameterMode mode =
1729 CodeStubAssembler::INTPTR_PARAMETERS;
1730 Node* const elem = a->LoadFixedArrayElement(res_elems, i, 0, mode);
1731
1732 Label if_issmi(a), if_isstring(a), loop_epilogue(a);
1733 a->Branch(a->TaggedIsSmi(elem), &if_issmi, &if_isstring);
1734
1735 a->Bind(&if_issmi);
1736 {
1737 // Integers represent slices of the original string.
1738 Label if_isnegativeorzero(a), if_ispositive(a);
1739 a->BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero,
1740 &if_ispositive);
1741
1742 a->Bind(&if_ispositive);
1743 {
1744 Node* const int_elem = a->SmiUntag(elem);
1745 Node* const new_match_start =
1746 a->IntPtrAdd(a->WordShr(int_elem, a->IntPtrConstant(11)),
1747 a->WordAnd(int_elem, a->IntPtrConstant(0x7ff)));
1748 var_match_start.Bind(a->SmiTag(new_match_start));
1749 a->Goto(&loop_epilogue);
1750 }
1751
1752 a->Bind(&if_isnegativeorzero);
1753 {
1754 Node* const next_i = a->IntPtrAdd(i, int_one);
1755 var_i.Bind(next_i);
1756
1757 Node* const next_elem =
1758 a->LoadFixedArrayElement(res_elems, next_i, 0, mode);
1759
1760 Node* const new_match_start = a->SmiSub(next_elem, elem);
1761 var_match_start.Bind(new_match_start);
1762 a->Goto(&loop_epilogue);
1763 }
1764 }
1765
1766 a->Bind(&if_isstring);
1767 {
1768 CSA_ASSERT(a, a->IsStringInstanceType(a->LoadInstanceType(elem)));
1769
1770 Callable call_callable = CodeFactory::Call(isolate);
1771 Node* const replacement_obj =
1772 a->CallJS(call_callable, context, replace_callable, undefined, elem,
1773 var_match_start.value(), subject_string);
1774
1775 Node* const replacement_str = a->ToString(context, replacement_obj);
1776 a->StoreFixedArrayElement(res_elems, i, replacement_str);
1777
1778 Node* const elem_length = a->LoadStringLength(elem);
1779 Node* const new_match_start =
1780 a->SmiAdd(var_match_start.value(), elem_length);
1781 var_match_start.Bind(new_match_start);
1782
1783 a->Goto(&loop_epilogue);
1784 }
1785
1786 a->Bind(&loop_epilogue);
1787 {
1788 var_i.Bind(a->IntPtrAdd(var_i.value(), int_one));
1789 a->Goto(&loop);
1790 }
1791 }
1792 }
1793
1794 a->Bind(&if_hasexplicitcaptures);
1795 {
1796 CodeStubAssembler::ParameterMode mode =
1797 CodeStubAssembler::INTPTR_PARAMETERS;
1798
1799 Node* const from = int_zero;
1800 Node* const to = a->SmiUntag(res_length);
1801 const int increment = 1;
1802
1803 a->BuildFastLoop(
1804 MachineType::PointerRepresentation(), from, to,
1805 [res_elems, isolate, native_context, context, undefined,
1806 replace_callable, mode](CodeStubAssembler* a, Node* index) {
1807 Node* const elem =
1808 a->LoadFixedArrayElement(res_elems, index, 0, mode);
1809
1810 Label do_continue(a);
1811 a->GotoIf(a->TaggedIsSmi(elem), &do_continue);
1812
1813 // elem must be an Array.
1814 // Use the apply argument as backing for global RegExp properties.
1815
1816 CSA_ASSERT(a, a->HasInstanceType(elem, JS_ARRAY_TYPE));
1817
1818 // TODO(jgruber): Remove indirection through Call->ReflectApply.
1819 Callable call_callable = CodeFactory::Call(isolate);
1820 Node* const reflect_apply = a->LoadContextElement(
1821 native_context, Context::REFLECT_APPLY_INDEX);
1822
1823 Node* const replacement_obj =
1824 a->CallJS(call_callable, context, reflect_apply, undefined,
1825 replace_callable, undefined, elem);
1826
1827 // Overwrite the i'th element in the results with the string we got
1828 // back from the callback function.
1829
1830 Node* const replacement_str = a->ToString(context, replacement_obj);
1831 a->StoreFixedArrayElement(res_elems, index, replacement_str,
1832 UPDATE_WRITE_BARRIER, mode);
1833
1834 a->Goto(&do_continue);
1835 a->Bind(&do_continue);
1836 },
1837 increment, CodeStubAssembler::IndexAdvanceMode::kPost);
1838
1839 a->Goto(&create_result);
1840 }
1841
1842 a->Bind(&create_result);
1843 {
1844 Node* const result = a->CallRuntime(Runtime::kStringBuilderConcat, context,
1845 res, res_length, subject_string);
1846 var_result.Bind(result);
1847 a->Goto(&out);
1848 }
1849
1850 a->Bind(&out);
1851 return var_result.value();
1852 }
1853
ReplaceSimpleStringFastPath(CodeStubAssembler * a,compiler::Node * context,compiler::Node * regexp,compiler::Node * subject_string,compiler::Node * replace_string)1854 compiler::Node* ReplaceSimpleStringFastPath(CodeStubAssembler* a,
1855 compiler::Node* context,
1856 compiler::Node* regexp,
1857 compiler::Node* subject_string,
1858 compiler::Node* replace_string) {
1859 // The fast path is reached only if {receiver} is an unmodified
1860 // JSRegExp instance, {replace_value} is non-callable, and
1861 // ToString({replace_value}) does not contain '$', i.e. we're doing a simple
1862 // string replacement.
1863
1864 typedef CodeStubAssembler::Variable Variable;
1865 typedef CodeStubAssembler::Label Label;
1866 typedef compiler::Node Node;
1867
1868 Isolate* const isolate = a->isolate();
1869
1870 Node* const null = a->NullConstant();
1871 Node* const int_zero = a->IntPtrConstant(0);
1872 Node* const smi_zero = a->SmiConstant(Smi::kZero);
1873
1874 Label out(a);
1875 Variable var_result(a, MachineRepresentation::kTagged);
1876
1877 // Load the last match info.
1878 Node* const native_context = a->LoadNativeContext(context);
1879 Node* const last_match_info = a->LoadContextElement(
1880 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1881
1882 // Is {regexp} global?
1883 Label if_isglobal(a), if_isnonglobal(a);
1884 Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
1885 Node* const is_global =
1886 a->WordAnd(a->SmiUntag(flags), a->IntPtrConstant(JSRegExp::kGlobal));
1887 a->Branch(a->WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
1888
1889 a->Bind(&if_isglobal);
1890 {
1891 // Hand off global regexps to runtime.
1892 FastStoreLastIndex(a, context, regexp, smi_zero);
1893 Node* const result =
1894 a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
1895 subject_string, regexp, replace_string, last_match_info);
1896 var_result.Bind(result);
1897 a->Goto(&out);
1898 }
1899
1900 a->Bind(&if_isnonglobal);
1901 {
1902 // Run exec, then manually construct the resulting string.
1903 Callable exec_callable = CodeFactory::RegExpExec(isolate);
1904 Node* const match_indices =
1905 a->CallStub(exec_callable, context, regexp, subject_string, smi_zero,
1906 last_match_info);
1907
1908 Label if_matched(a), if_didnotmatch(a);
1909 a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
1910
1911 a->Bind(&if_didnotmatch);
1912 {
1913 FastStoreLastIndex(a, context, regexp, smi_zero);
1914 var_result.Bind(subject_string);
1915 a->Goto(&out);
1916 }
1917
1918 a->Bind(&if_matched);
1919 {
1920 CodeStubAssembler::ParameterMode mode =
1921 CodeStubAssembler::INTPTR_PARAMETERS;
1922
1923 Node* const subject_start = smi_zero;
1924 Node* const match_start = a->LoadFixedArrayElement(
1925 match_indices, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex),
1926 0, mode);
1927 Node* const match_end = a->LoadFixedArrayElement(
1928 match_indices,
1929 a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1), 0, mode);
1930 Node* const subject_end = a->LoadStringLength(subject_string);
1931
1932 Label if_replaceisempty(a), if_replaceisnotempty(a);
1933 Node* const replace_length = a->LoadStringLength(replace_string);
1934 a->Branch(a->SmiEqual(replace_length, smi_zero), &if_replaceisempty,
1935 &if_replaceisnotempty);
1936
1937 a->Bind(&if_replaceisempty);
1938 {
1939 // TODO(jgruber): We could skip many of the checks that using SubString
1940 // here entails.
1941
1942 Node* const first_part =
1943 a->SubString(context, subject_string, subject_start, match_start);
1944 Node* const second_part =
1945 a->SubString(context, subject_string, match_end, subject_end);
1946
1947 Node* const result = a->StringAdd(context, first_part, second_part);
1948 var_result.Bind(result);
1949 a->Goto(&out);
1950 }
1951
1952 a->Bind(&if_replaceisnotempty);
1953 {
1954 Node* const first_part =
1955 a->SubString(context, subject_string, subject_start, match_start);
1956 Node* const second_part = replace_string;
1957 Node* const third_part =
1958 a->SubString(context, subject_string, match_end, subject_end);
1959
1960 Node* result = a->StringAdd(context, first_part, second_part);
1961 result = a->StringAdd(context, result, third_part);
1962
1963 var_result.Bind(result);
1964 a->Goto(&out);
1965 }
1966 }
1967 }
1968
1969 a->Bind(&out);
1970 return var_result.value();
1971 }
1972
1973 } // namespace
1974
1975 // ES#sec-regexp.prototype-@@replace
1976 // RegExp.prototype [ @@replace ] ( string, replaceValue )
Generate_RegExpPrototypeReplace(CodeStubAssembler * a)1977 void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
1978 typedef CodeStubAssembler::Label Label;
1979 typedef compiler::Node Node;
1980
1981 Isolate* const isolate = a->isolate();
1982
1983 Node* const maybe_receiver = a->Parameter(0);
1984 Node* const maybe_string = a->Parameter(1);
1985 Node* const replace_value = a->Parameter(2);
1986 Node* const context = a->Parameter(5);
1987
1988 Node* const int_zero = a->IntPtrConstant(0);
1989
1990 // Ensure {maybe_receiver} is a JSReceiver.
1991 Node* const map =
1992 ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
1993 MessageTemplate::kIncompatibleMethodReceiver,
1994 "RegExp.prototype.@@replace");
1995 Node* const receiver = maybe_receiver;
1996
1997 // Convert {maybe_string} to a String.
1998 Callable tostring_callable = CodeFactory::ToString(isolate);
1999 Node* const string = a->CallStub(tostring_callable, context, maybe_string);
2000
2001 // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
2002 Label checkreplacecallable(a), runtime(a, Label::kDeferred), fastpath(a);
2003 BranchIfFastPath(a, context, map, &checkreplacecallable, &runtime);
2004
2005 a->Bind(&checkreplacecallable);
2006 Node* const regexp = receiver;
2007
2008 // 2. Is {replace_value} callable?
2009 Label checkreplacestring(a), if_iscallable(a);
2010 a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring);
2011
2012 Node* const replace_value_map = a->LoadMap(replace_value);
2013 a->Branch(a->IsCallableMap(replace_value_map), &if_iscallable,
2014 &checkreplacestring);
2015
2016 // 3. Does ToString({replace_value}) contain '$'?
2017 a->Bind(&checkreplacestring);
2018 {
2019 Node* const replace_string =
2020 a->CallStub(tostring_callable, context, replace_value);
2021
2022 Node* const dollar_char = a->IntPtrConstant('$');
2023 Node* const smi_minusone = a->SmiConstant(Smi::FromInt(-1));
2024 a->GotoUnless(a->SmiEqual(a->StringIndexOfChar(context, replace_string,
2025 dollar_char, int_zero),
2026 smi_minusone),
2027 &runtime);
2028
2029 a->Return(ReplaceSimpleStringFastPath(a, context, regexp, string,
2030 replace_string));
2031 }
2032
2033 // {regexp} is unmodified and {replace_value} is callable.
2034 a->Bind(&if_iscallable);
2035 {
2036 Node* const replace_callable = replace_value;
2037
2038 // Check if the {regexp} is global.
2039 Label if_isglobal(a), if_isnotglobal(a);
2040 Node* const is_global = FastFlagGetter(a, regexp, JSRegExp::kGlobal);
2041 a->Branch(is_global, &if_isglobal, &if_isnotglobal);
2042
2043 a->Bind(&if_isglobal);
2044 {
2045 Node* const result = ReplaceGlobalCallableFastPath(
2046 a, context, regexp, string, replace_callable);
2047 a->Return(result);
2048 }
2049
2050 a->Bind(&if_isnotglobal);
2051 {
2052 Node* const result =
2053 a->CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
2054 context, string, regexp, replace_callable);
2055 a->Return(result);
2056 }
2057 }
2058
2059 a->Bind(&runtime);
2060 {
2061 Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context,
2062 receiver, string, replace_value);
2063 a->Return(result);
2064 }
2065 }
2066
2067 // Simple string matching functionality for internal use which does not modify
2068 // the last match info.
Generate_RegExpInternalMatch(CodeStubAssembler * a)2069 void Builtins::Generate_RegExpInternalMatch(CodeStubAssembler* a) {
2070 typedef CodeStubAssembler::Label Label;
2071 typedef compiler::Node Node;
2072
2073 Isolate* const isolate = a->isolate();
2074
2075 Node* const regexp = a->Parameter(1);
2076 Node* const string = a->Parameter(2);
2077 Node* const context = a->Parameter(5);
2078
2079 Node* const null = a->NullConstant();
2080 Node* const smi_zero = a->SmiConstant(Smi::FromInt(0));
2081
2082 Node* const native_context = a->LoadNativeContext(context);
2083 Node* const internal_match_info = a->LoadContextElement(
2084 native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX);
2085
2086 Callable exec_callable = CodeFactory::RegExpExec(isolate);
2087 Node* const match_indices = a->CallStub(
2088 exec_callable, context, regexp, string, smi_zero, internal_match_info);
2089
2090 Label if_matched(a), if_didnotmatch(a);
2091 a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
2092
2093 a->Bind(&if_didnotmatch);
2094 a->Return(null);
2095
2096 a->Bind(&if_matched);
2097 {
2098 Node* result = ConstructNewResultFromMatchInfo(isolate, a, context,
2099 match_indices, string);
2100 a->Return(result);
2101 }
2102 }
2103
2104 } // namespace internal
2105 } // namespace v8
2106