Commit 81e7e2f4 authored by Shu-yu Guo's avatar Shu-yu Guo Committed by Commit Bot

[regexp] Implement the /d flag for RegExp indices

This CL implements the upcoming spec change:
https://github.com/tc39/proposal-regexp-match-indices/pull/49

A new JSRegExpResultWithIndices subclass is introduced with a separate map and
an extra slot for storing the indices. If /d is passed, exec() constructs a
JSRegExpResultWithIndices and eagerly builds indices.

The existing re-execution logic is removed.

Bug: v8:9548
Change-Id: Ic11853e7521017af5e8bd583c7b82bb672821132
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2616873
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72306}
parent 9dccd91c
......@@ -6136,9 +6136,10 @@ class V8_EXPORT RegExp : public Object {
kUnicode = 1 << 4,
kDotAll = 1 << 5,
kLinear = 1 << 6,
kHasIndices = 1 << 7,
};
static constexpr int kFlagCount = 7;
static constexpr int kFlagCount = 8;
/**
* Creates a regular expression from the given pattern string and
......
......@@ -7032,6 +7032,7 @@ REGEXP_FLAG_ASSERT_EQ(kIgnoreCase);
REGEXP_FLAG_ASSERT_EQ(kMultiline);
REGEXP_FLAG_ASSERT_EQ(kSticky);
REGEXP_FLAG_ASSERT_EQ(kUnicode);
REGEXP_FLAG_ASSERT_EQ(kHasIndices);
REGEXP_FLAG_ASSERT_EQ(kLinear);
#undef REGEXP_FLAG_ASSERT_EQ
......
......@@ -256,6 +256,7 @@ void CallPrinter::VisitRegExpLiteral(RegExpLiteral* node) {
Print("/");
PrintLiteral(node->pattern(), false);
Print("/");
if (node->flags() & RegExp::kHasIndices) Print("d");
if (node->flags() & RegExp::kGlobal) Print("g");
if (node->flags() & RegExp::kIgnoreCase) Print("i");
if (node->flags() & RegExp::kLinear) Print("l");
......@@ -1156,6 +1157,7 @@ void AstPrinter::VisitRegExpLiteral(RegExpLiteral* node) {
PrintLiteralIndented("PATTERN", node->raw_pattern(), false);
int i = 0;
EmbeddedVector<char, 128> buf;
if (node->flags() & RegExp::kHasIndices) buf[i++] = 'd';
if (node->flags() & RegExp::kGlobal) buf[i++] = 'g';
if (node->flags() & RegExp::kIgnoreCase) buf[i++] = 'i';
if (node->flags() & RegExp::kLinear) buf[i++] = 'l';
......
......@@ -842,32 +842,5 @@ Handle<AccessorInfo> Accessors::MakeErrorStackInfo(Isolate* isolate) {
&ErrorStackGetter, &ErrorStackSetter);
}
//
// Accessors::RegExpResultIndices
//
void Accessors::RegExpResultIndicesGetter(
v8::Local<v8::Name> key, const v8::PropertyCallbackInfo<v8::Value>& info) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
HandleScope scope(isolate);
Handle<JSRegExpResult> regexp_result(
Handle<JSRegExpResult>::cast(Utils::OpenHandle(*info.Holder())));
MaybeHandle<JSArray> maybe_indices(
JSRegExpResult::GetAndCacheIndices(isolate, regexp_result));
Handle<JSArray> indices;
if (!maybe_indices.ToHandle(&indices)) {
isolate->OptionalRescheduleException(false);
Handle<Object> result = isolate->factory()->undefined_value();
info.GetReturnValue().Set(Utils::ToLocal(result));
} else {
info.GetReturnValue().Set(Utils::ToLocal(indices));
}
}
Handle<AccessorInfo> Accessors::MakeRegExpResultIndicesInfo(Isolate* isolate) {
return MakeAccessor(isolate, isolate->factory()->indices_string(),
&RegExpResultIndicesGetter, nullptr);
}
} // namespace internal
} // namespace v8
......@@ -44,8 +44,6 @@ class JavaScriptFrame;
kHasSideEffectToReceiver) \
V(_, function_prototype, FunctionPrototype, kHasNoSideEffect, \
kHasSideEffectToReceiver) \
V(_, regexp_result_indices, RegExpResultIndices, kHasSideEffectToReceiver, \
kHasSideEffectToReceiver) \
V(_, string_length, StringLength, kHasNoSideEffect, kHasSideEffectToReceiver)
#define ACCESSOR_SETTER_LIST(V) \
......
This diff is collapsed.
......@@ -22,13 +22,14 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
TNode<RawPtrT> LoadCodeObjectEntry(TNode<Code> code);
// Allocate a RegExpResult with the given length (the number of captures,
// including the match itself), index (the index where the match starts),
// and input string.
// Allocate either a JSRegExpResult or a JSRegExpResultWithIndices (depending
// on has_indices) with the given length (the number of captures, including
// the match itself), index (the index where the match starts), and input
// string.
TNode<JSRegExpResult> AllocateRegExpResult(
TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
TNode<String> input, TNode<JSRegExp> regexp, TNode<Number> last_index,
TNode<FixedArray>* elements_out = nullptr);
TNode<BoolT> has_indices, TNode<FixedArray>* elements_out = nullptr);
TNode<Object> FastLoadLastIndexBeforeSmiCheck(TNode<JSRegExp> regexp);
TNode<Smi> FastLoadLastIndex(TNode<JSRegExp> regexp) {
......
......@@ -51,8 +51,8 @@ transitioning macro RegExpExec(implicit context: Context)(
}
extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
implicit context: Context)(
JSRegExp, RegExpMatchInfo, String, Number): JSRegExpResult;
implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number):
JSRegExpResult|JSRegExpResultWithIndices;
const kGlobalOrSticky: constexpr int31
generates 'JSRegExp::kGlobal | JSRegExp::kSticky';
......@@ -185,6 +185,7 @@ extern enum Flag constexpr 'JSRegExp::Flag' {
kSticky,
kUnicode,
kDotAll,
kHasIndices,
kLinear
}
......@@ -243,6 +244,13 @@ transitioning javascript builtin RegExpPrototypeMultilineGetter(
'RegExp.prototype.multiline');
}
transitioning javascript builtin RegExpPrototypeHasIndicesGetter(
js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
return FlagGetter(
receiver, Flag::kHasIndices, kNoCounterFlagGetter,
'RegExp.prototype.hasIndices');
}
transitioning javascript builtin RegExpPrototypeLinearGetter(
js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
return FlagGetter(
......
......@@ -462,6 +462,11 @@ ExternalReference ExternalReference::address_of_builtin_subclassing_flag() {
return ExternalReference(&FLAG_builtin_subclassing);
}
ExternalReference
ExternalReference::address_of_harmony_regexp_match_indices_flag() {
return ExternalReference(&FLAG_harmony_regexp_match_indices);
}
ExternalReference ExternalReference::address_of_runtime_stats_flag() {
return ExternalReference(&TracingFlags::runtime_stats);
}
......
......@@ -99,16 +99,18 @@ class StatsCounter;
#define EXTERNAL_REFERENCE_LIST(V) \
V(abort_with_reason, "abort_with_reason") \
V(address_of_builtin_subclassing_flag, "FLAG_builtin_subclassing") \
V(address_of_double_abs_constant, "double_absolute_constant") \
V(address_of_double_neg_constant, "double_negate_constant") \
V(address_of_enable_experimental_regexp_engine, \
"address_of_enable_experimental_regexp_engine") \
V(address_of_float_abs_constant, "float_absolute_constant") \
V(address_of_float_neg_constant, "float_negate_constant") \
V(address_of_harmony_regexp_match_indices_flag, \
"FLAG_harmony_regexp_match_indices") \
V(address_of_min_int, "LDoubleConstant::min_int") \
V(address_of_mock_arraybuffer_allocator_flag, \
"FLAG_mock_arraybuffer_allocator") \
V(address_of_builtin_subclassing_flag, "FLAG_builtin_subclassing") \
V(address_of_one_half, "LDoubleConstant::one_half") \
V(address_of_runtime_stats_flag, "TracingFlags::runtime_stats") \
V(address_of_the_hole_nan, "the_hole_nan") \
......
......@@ -843,6 +843,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kRegExpPrototypeSplit:
case Builtins::kRegExpPrototypeFlagsGetter:
case Builtins::kRegExpPrototypeGlobalGetter:
case Builtins::kRegExpPrototypeHasIndicesGetter:
case Builtins::kRegExpPrototypeIgnoreCaseGetter:
case Builtins::kRegExpPrototypeMatchAll:
case Builtins::kRegExpPrototypeMultilineGetter:
......
......@@ -4436,13 +4436,31 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() {
void Genesis::InitializeGlobal_harmony_regexp_match_indices() {
if (!FLAG_harmony_regexp_match_indices) return;
// Add indices accessor to JSRegExpResult's initial map.
Handle<Map> initial_map(native_context()->regexp_result_map(), isolate());
Descriptor d = Descriptor::AccessorConstant(
factory()->indices_string(), factory()->regexp_result_indices_accessor(),
NONE);
Map::EnsureDescriptorSlack(isolate(), initial_map, 1);
initial_map->AppendDescriptor(isolate(), &d);
Handle<Map> source_map(native_context()->regexp_result_map(), isolate());
Handle<Map> initial_map =
Map::Copy(isolate(), source_map, "JSRegExpResult with indices");
initial_map->set_instance_size(JSRegExpResultWithIndices::kSize);
DCHECK_EQ(initial_map->GetInObjectProperties(),
JSRegExpResultWithIndices::kInObjectPropertyCount);
// indices descriptor
{
Descriptor d =
Descriptor::DataField(isolate(), factory()->indices_string(),
JSRegExpResultWithIndices::kIndicesIndex, NONE,
Representation::Tagged());
Map::EnsureDescriptorSlack(isolate(), initial_map, 1);
initial_map->AppendDescriptor(isolate(), &d);
}
native_context()->set_regexp_result_with_indices_map(*initial_map);
Handle<JSObject> prototype(native_context()->regexp_prototype(), isolate());
SimpleInstallGetter(isolate(), prototype, factory()->has_indices_string(),
Builtins::kRegExpPrototypeHasIndicesGetter, true);
// Store regexp prototype map again after change.
native_context()->set_regexp_prototype_map(prototype->map());
}
void Genesis::InitializeGlobal_harmony_string_replaceall() {
......@@ -4788,16 +4806,6 @@ bool Genesis::InstallABunchOfRandomThings() {
{
PropertyAttributes attribs = DONT_ENUM;
// cached_indices_or_regexp descriptor.
{
Descriptor d = Descriptor::DataField(
isolate(),
factory()->regexp_result_cached_indices_or_regexp_symbol(),
JSRegExpResult::kCachedIndicesOrRegExpIndex, attribs,
Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
}
// names descriptor.
{
Descriptor d = Descriptor::DataField(
......
......@@ -209,6 +209,7 @@
V(_, globalThis_string, "globalThis") \
V(_, groups_string, "groups") \
V(_, has_string, "has") \
V(_, has_indices_string, "hasIndices") \
V(_, ignoreCase_string, "ignoreCase") \
V(_, illegal_access_string, "illegal access") \
V(_, illegal_argument_string, "illegal argument") \
......@@ -357,7 +358,6 @@
V(_, promise_debug_message_symbol) \
V(_, promise_forwarding_handler_symbol) \
V(_, promise_handled_by_symbol) \
V(_, regexp_result_cached_indices_or_regexp_symbol) \
V(_, regexp_result_names_symbol) \
V(_, regexp_result_regexp_input_symbol) \
V(_, regexp_result_regexp_last_index_symbol) \
......
......@@ -214,6 +214,7 @@ enum ContextLookupFlags {
V(REGEXP_PROTOTYPE_MAP_INDEX, Map, regexp_prototype_map) \
V(REGEXP_REPLACE_FUNCTION_INDEX, JSFunction, regexp_replace_function) \
V(REGEXP_RESULT_MAP_INDEX, Map, regexp_result_map) \
V(REGEXP_RESULT_WITH_INDICES_MAP_INDEX, Map, regexp_result_with_indices_map) \
V(REGEXP_RESULT_INDICES_MAP_INDEX, Map, regexp_result_indices_map) \
V(REGEXP_SEARCH_FUNCTION_INDEX, JSFunction, regexp_search_function) \
V(REGEXP_SPLIT_FUNCTION_INDEX, JSFunction, regexp_split_function) \
......
......@@ -24,7 +24,13 @@ TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExp)
OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResult, JSArray)
OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResultIndices, JSArray)
inline JSRegExpResultWithIndices::JSRegExpResultWithIndices(Address ptr)
: JSRegExpResult(ptr) {
SLOW_DCHECK(IsJSArray());
}
CAST_ACCESSOR(JSRegExpResult)
CAST_ACCESSOR(JSRegExpResultWithIndices)
CAST_ACCESSOR(JSRegExpResultIndices)
ACCESSORS(JSRegExp, last_index, Object, kLastIndexOffset)
......
......@@ -12,78 +12,6 @@
namespace v8 {
namespace internal {
MaybeHandle<JSArray> JSRegExpResult::GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result) {
// Check for cached indices. We do a slow lookup and set of
// the cached_indices_or_match_info and names fields just in
// case they have been migrated to dictionaries.
Handle<Object> indices_or_regexp(
GetProperty(
isolate, regexp_result,
isolate->factory()->regexp_result_cached_indices_or_regexp_symbol())
.ToHandleChecked());
if (indices_or_regexp->IsJSRegExp()) {
// Build and cache indices for next lookup.
// TODO(joshualitt): Instead of caching the indices, we could call
// ReconfigureToDataProperty on 'indices' setting its value to this
// newly created array. However, care would have to be taken to ensure
// a new map is not created each time.
// Grab regexp, its last_index, and the original subject string from the
// result and the re-execute the regexp to generate a new MatchInfo.
Handle<JSRegExp> regexp(JSRegExp::cast(*indices_or_regexp), isolate);
Handle<Object> input_object(
GetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_input_symbol())
.ToHandleChecked());
Handle<String> subject(String::cast(*input_object), isolate);
Handle<Object> last_index_object(
GetProperty(
isolate, regexp_result,
isolate->factory()->regexp_result_regexp_last_index_symbol())
.ToHandleChecked());
int capture_count = regexp->CaptureCount();
Handle<RegExpMatchInfo> match_info =
RegExpMatchInfo::New(isolate, capture_count);
int last_index = Smi::ToInt(*last_index_object);
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
RegExp::Exec(isolate, regexp, subject, last_index, match_info),
JSArray);
DCHECK_EQ(*result, *match_info);
Handle<Object> maybe_names(
GetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_names_symbol())
.ToHandleChecked());
indices_or_regexp =
JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);
// Cache the result and clear the names array, last_index and subject.
SetProperty(
isolate, regexp_result,
isolate->factory()->regexp_result_cached_indices_or_regexp_symbol(),
indices_or_regexp)
.ToHandleChecked();
SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_names_symbol(),
isolate->factory()->undefined_value())
.ToHandleChecked();
SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_last_index_symbol(),
isolate->factory()->undefined_value())
.ToHandleChecked();
SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_input_symbol(),
isolate->factory()->undefined_value())
.ToHandleChecked();
}
return Handle<JSArray>::cast(indices_or_regexp);
}
Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
Isolate* isolate, Handle<RegExpMatchInfo> match_info,
Handle<Object> maybe_names) {
......
......@@ -5,6 +5,7 @@
#ifndef V8_OBJECTS_JS_REGEXP_H_
#define V8_OBJECTS_JS_REGEXP_H_
#include "src/objects/contexts.h"
#include "src/objects/js-array.h"
#include "torque-generated/bit-fields.h"
......@@ -43,7 +44,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
DEFINE_TORQUE_GENERATED_JS_REG_EXP_FLAGS()
static base::Optional<Flag> FlagFromChar(char c) {
STATIC_ASSERT(kFlagCount == 7);
STATIC_ASSERT(kFlagCount == 8);
// clang-format off
return c == 'g' ? base::Optional<Flag>(kGlobal)
: c == 'i' ? base::Optional<Flag>(kIgnoreCase)
......@@ -53,6 +54,8 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
: c == 's' ? base::Optional<Flag>(kDotAll)
: (FLAG_enable_experimental_regexp_engine && c == 'l')
? base::Optional<Flag>(kLinear)
: (FLAG_harmony_regexp_match_indices && c == 'd')
? base::Optional<Flag>(kHasIndices)
: base::Optional<Flag>();
// clang-format on
}
......@@ -65,6 +68,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
STATIC_ASSERT(static_cast<int>(kUnicode) == v8::RegExp::kUnicode);
STATIC_ASSERT(static_cast<int>(kDotAll) == v8::RegExp::kDotAll);
STATIC_ASSERT(static_cast<int>(kLinear) == v8::RegExp::kLinear);
STATIC_ASSERT(static_cast<int>(kHasIndices) == v8::RegExp::kHasIndices);
STATIC_ASSERT(kFlagCount == v8::RegExp::kFlagCount);
DECL_ACCESSORS(last_index, Object)
......@@ -249,24 +253,40 @@ class JSRegExpResult : public JSArray {
DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kHeaderSize,
TORQUE_GENERATED_JS_REG_EXP_RESULT_FIELDS)
static MaybeHandle<JSArray> GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result);
// Indices of in-object properties.
static const int kIndexIndex = 0;
static const int kInputIndex = 1;
static const int kGroupsIndex = 2;
// Private internal only fields.
static const int kCachedIndicesOrRegExpIndex = 3;
static const int kNamesIndex = 4;
static const int kRegExpInputIndex = 5;
static const int kRegExpLastIndex = 6;
static const int kInObjectPropertyCount = 7;
static const int kNamesIndex = 3;
static const int kRegExpInputIndex = 4;
static const int kRegExpLastIndex = 5;
static const int kInObjectPropertyCount = 6;
static const int kMapIndexInContext = Context::REGEXP_RESULT_MAP_INDEX;
OBJECT_CONSTRUCTORS(JSRegExpResult, JSArray);
};
class JSRegExpResultWithIndices : public JSRegExpResult {
public:
DECL_CAST(JSRegExpResultWithIndices)
// Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(
JSRegExpResult::kSize,
TORQUE_GENERATED_JS_REG_EXP_RESULT_WITH_INDICES_FIELDS)
static_assert(
JSRegExpResult::kInObjectPropertyCount == 6,
"JSRegExpResultWithIndices must be a subclass of JSRegExpResult");
static const int kIndicesIndex = 6;
static const int kInObjectPropertyCount = 7;
OBJECT_CONSTRUCTORS(JSRegExpResultWithIndices, JSRegExpResult);
};
// JSRegExpResultIndices is just a JSArray with a specific initial map.
// This initial map adds in-object properties for "group"
// properties, as assigned by RegExp.prototype.exec, which allows
......
......@@ -10,6 +10,7 @@ bitfield struct JSRegExpFlags extends uint31 {
unicode: bool: 1 bit;
dot_all: bool: 1 bit;
linear: bool: 1 bit;
has_indices: bool: 1 bit;
}
@generateCppClass
......@@ -34,6 +35,10 @@ RegExpBuiltinsAssembler::FastLoadLastIndex(FastJSRegExp): Smi;
extern operator '.lastIndex=' macro
RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void;
@doNotGenerateCast
extern class JSRegExpConstructor extends JSFunction
generates 'TNode<JSFunction>';
extern shape JSRegExpResult extends JSArray {
// In-object properties:
// The below fields are externally exposed.
......@@ -42,15 +47,14 @@ extern shape JSRegExpResult extends JSArray {
groups: JSAny;
// The below fields are for internal use only.
cached_indices_or_regexp: JSRegExpResultIndices|JSRegExp;
names: FixedArray|Undefined;
regexp_input: String;
regexp_last_index: Smi;
}
@doNotGenerateCast
extern class JSRegExpConstructor extends JSFunction
generates 'TNode<JSFunction>';
extern shape JSRegExpResultWithIndices extends JSRegExpResult {
indices: JSAny;
}
extern shape JSRegExpResultIndices extends JSArray {
// In-object properties:
......
......@@ -1662,6 +1662,9 @@ MaybeHandle<JSRegExp> ValueDeserializer::ReadJSRegExp() {
if (!FLAG_enable_experimental_regexp_engine) {
bad_flags_mask |= JSRegExp::kLinear;
}
if (!FLAG_harmony_regexp_match_indices) {
bad_flags_mask |= JSRegExp::kHasIndices;
}
if ((raw_flags & bad_flags_mask) ||
!JSRegExp::New(isolate_, pattern, static_cast<JSRegExp::Flags>(raw_flags))
.ToHandle(&regexp)) {
......
......@@ -894,6 +894,21 @@ RUNTIME_FUNCTION(Runtime_RegExpExperimentalOneshotExec) {
last_match_info));
}
RUNTIME_FUNCTION(Runtime_RegExpBuildIndices) {
DCHECK(FLAG_harmony_regexp_match_indices);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, match_info, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, maybe_names, 2);
#ifdef DEBUG
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0);
DCHECK(regexp->GetFlags() & JSRegExp::kHasIndices);
#endif
return *JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);
}
namespace {
class MatchInfoBackedMatch : public String::Match {
......
......@@ -385,6 +385,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_REGEXP(F, I) \
I(IsRegExp, 1, 1) \
F(RegExpBuildIndices, 3, 1) \
F(RegExpExec, 4, 1) \
F(RegExpExperimentalOneshotExec, 4, 1) \
F(RegExpExecMultiple, 4, 1) \
......
......@@ -243,8 +243,9 @@ std::string GenerateRandomFlags(FuzzerArgs* args) {
// TODO(mbid,v8:10765): Find a way to generate the kLinear flag sometimes,
// but only for patterns that are supported by the experimental engine.
constexpr size_t kFlagCount = JSRegExp::kFlagCount;
CHECK_EQ(JSRegExp::kLinear, 1 << (kFlagCount - 1));
CHECK_EQ(JSRegExp::kDotAll, 1 << (kFlagCount - 2));
CHECK_EQ(JSRegExp::kHasIndices, 1 << (kFlagCount - 1));
CHECK_EQ(JSRegExp::kLinear, 1 << (kFlagCount - 2));
CHECK_EQ(JSRegExp::kDotAll, 1 << (kFlagCount - 3));
STATIC_ASSERT((1 << kFlagCount) - 1 <= 0xFF);
const size_t flags = RandomByte(args) & ((1 << kFlagCount) - 1);
......@@ -258,6 +259,7 @@ std::string GenerateRandomFlags(FuzzerArgs* args) {
if (flags & JSRegExp::kSticky) buffer[cursor++] = 'y';
if (flags & JSRegExp::kUnicode) buffer[cursor++] = 'u';
if (flags & JSRegExp::kDotAll) buffer[cursor++] = 's';
if (flags & JSRegExp::kHasIndices) buffer[cursor++] = 'd';
return std::string(buffer, cursor);
}
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-harmony-regexp-match-indices
// Redefined hasIndices should not reflect in flags without
// --harmony-regexp-match-indices
{
let re = /./;
Object.defineProperty(re, "hasIndices", { get: function() { return true; } });
assertEquals("", re.flags);
}
......@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --harmony-regexp-match-indices --expose-gc --stack-size=100
// Flags: --harmony-regexp-match-indices --allow-natives-syntax
// Flags: --expose-gc --stack-size=100
// Flags: --no-force-slow-path
// Sanity test.
{
const re = /a+(?<Z>z)?/;
const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz");
assertEquals(m.indices, [[1, 5], [4, 5]]);
......@@ -15,7 +17,7 @@
// Capture groups that are not matched return `undefined`.
{
const re = /a+(?<Z>z)?/;
const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaay");
assertEquals(m.indices, [[1, 4], undefined]);
......@@ -24,7 +26,7 @@
// Two capture groups.
{
const re = /a+(?<A>zz)?(?<B>ii)?/;
const re = /a+(?<A>zz)?(?<B>ii)?/d;
const m = re.exec("xaaazzii");
assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]);
......@@ -33,16 +35,16 @@
// No capture groups.
{
const re = /a+/;
const re = /a+/d;
const m = re.exec("xaaazzii");
assertEquals(m.indices [[1, 4]]);
assertEquals(m.indices, [[1, 4]]);
assertEquals(m.indices.groups, undefined);
}
// No match.
{
const re = /a+/;
const re = /a+/d;
const m = re.exec("xzzii");
assertEquals(null, m);
......@@ -50,8 +52,8 @@
// Unnamed capture groups.
{
const re = /a+(z)?/;
const m = re.exec("xaaaz")
const re = /a+(z)?/d;
const m = re.exec("xaaaz");
assertEquals(m.indices, [[1, 5], [4, 5]]);
assertEquals(m.indices.groups, undefined)
......@@ -59,7 +61,7 @@
// Named and unnamed capture groups.
{
const re = /a+(z)?(?<Y>y)?/;
const re = /a+(z)?(?<Y>y)?/d;
const m = re.exec("xaaazyy")
assertEquals(m.indices, [[1, 6], [4, 5], [5, 6]]);
......@@ -69,7 +71,7 @@
// Verify property overwrite.
{
const re = /a+(?<Z>z)?/;
const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz");
m.indices = null;
......@@ -98,7 +100,7 @@
}
});
const re = /a+(?<Z>z)?/;
const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz");
assertEquals(m.indices.groups, {'Z': [4, 5]})
......@@ -106,14 +108,14 @@
// Test atomic regexp.
{
const m = /undefined/.exec();
const m = (/undefined/d).exec();
assertEquals(m.indices, [[0, 9]]);
}
// Test deleting unrelated fields does not break.
{
const m = /undefined/.exec();
const m = (/undefined/d).exec();
delete m['index'];
gc();
assertEquals(m.indices, [[0, 9]]);
......@@ -121,7 +123,7 @@
// Stack overflow.
{
const re = /a+(?<Z>z)?/;
const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz");
function rec() {
......@@ -138,9 +140,30 @@
// Match between matches.
{
const re = /a+(?<A>zz)?(?<B>ii)?/;
const re = /a+(?<A>zz)?(?<B>ii)?/d;
const m = re.exec("xaaazzii");
assertTrue(/b+(?<C>cccc)?/.test("llllllbbbbbbcccc"));
assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]);
assertEquals(m.indices.groups, {'A': [4, 6], 'B': [6, 8]});
}
// Redefined hasIndices should reflect in flags.
{
let re = /./;
Object.defineProperty(re, "hasIndices", { get: function() { return true; } });
assertEquals("d", re.flags);
}
{
// The flags field of a regexp should be sorted.
assertEquals("dgmsy", (/asdf/dymsg).flags);
// The 'hasIndices' member should be set according to the hasIndices flag.
assertTrue((/asdf/dymsg).hasIndices);
assertFalse((/asdf/ymsg).hasIndices);
// The new fields installed on the regexp prototype map shouldn't make
// unmodified regexps slow.
assertTrue(%RegexpIsUnmodified(/asdf/));
assertTrue(%RegexpIsUnmodified(/asdf/d));
}
......@@ -33,3 +33,10 @@ assertFalse((/asdf/ymsg).linear);
// unmodified regexps slow.
assertTrue(%RegexpIsUnmodified(/asdf/));
assertTrue(%RegexpIsUnmodified(/asdf/l));
// Redefined .linear should reflect in flags.
{
let re = /./;
Object.defineProperty(re, "linear", { get: function() { return true; } });
assertEquals("l", re.flags);
}
......@@ -15,8 +15,10 @@ assertThrows(() => new RegExp("((a*)*)*\1", "l"), SyntaxError)
assertFalse(RegExp.prototype.hasOwnProperty('linear'));
assertFalse(/123/.hasOwnProperty('linear'));
// Redefined .linear shouldn't reflect in flags without
// --enable-experimental-regexp-engine.
{
let re = /./;
re.linear = true;
Object.defineProperty(re, "linear", { get: function() { return true; } });
assertEquals("", re.flags);
}
......@@ -581,6 +581,9 @@
'built-ins/String/prototype/at/*': [FAIL],
'built-ins/TypedArray/prototype/at/*': [FAIL],
# Temporarily disabled until upstream tests are changed to use /d
'built-ins/RegExp/match-indices/*': [FAIL],
######################## NEEDS INVESTIGATION ###########################
# https://bugs.chromium.org/p/v8/issues/detail?id=7833
......
......@@ -1511,6 +1511,25 @@ TEST_F(ValueSerializerTest, DecodeLinearRegExp) {
i::FLAG_enable_experimental_regexp_engine = flag_was_enabled;
}
TEST_F(ValueSerializerTest, DecodeHasIndicesRegExp) {
bool flag_was_enabled = i::FLAG_harmony_regexp_match_indices;
// The last byte encodes the regexp flags.
std::vector<uint8_t> regexp_encoding = {0xFF, 0x09, 0x3F, 0x00, 0x52, 0x03,
0x66, 0x6F, 0x6F, 0xAD, 0x01};
i::FLAG_harmony_regexp_match_indices = true;
Local<Value> value = DecodeTest(regexp_encoding);
ASSERT_TRUE(value->IsRegExp());
ExpectScriptTrue("Object.getPrototypeOf(result) === RegExp.prototype");
ExpectScriptTrue("result.toString() === '/foo/dgmsy'");
i::FLAG_harmony_regexp_match_indices = false;
InvalidDecodeTest(regexp_encoding);
i::FLAG_harmony_regexp_match_indices = flag_was_enabled;
}
TEST_F(ValueSerializerTest, RoundTripMap) {
Local<Value> value = RoundTripTest("var m = new Map(); m.set(42, 'foo'); m;");
ASSERT_TRUE(value->IsMap());
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment