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 { ...@@ -6136,9 +6136,10 @@ class V8_EXPORT RegExp : public Object {
kUnicode = 1 << 4, kUnicode = 1 << 4,
kDotAll = 1 << 5, kDotAll = 1 << 5,
kLinear = 1 << 6, 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 * Creates a regular expression from the given pattern string and
......
...@@ -7032,6 +7032,7 @@ REGEXP_FLAG_ASSERT_EQ(kIgnoreCase); ...@@ -7032,6 +7032,7 @@ REGEXP_FLAG_ASSERT_EQ(kIgnoreCase);
REGEXP_FLAG_ASSERT_EQ(kMultiline); REGEXP_FLAG_ASSERT_EQ(kMultiline);
REGEXP_FLAG_ASSERT_EQ(kSticky); REGEXP_FLAG_ASSERT_EQ(kSticky);
REGEXP_FLAG_ASSERT_EQ(kUnicode); REGEXP_FLAG_ASSERT_EQ(kUnicode);
REGEXP_FLAG_ASSERT_EQ(kHasIndices);
REGEXP_FLAG_ASSERT_EQ(kLinear); REGEXP_FLAG_ASSERT_EQ(kLinear);
#undef REGEXP_FLAG_ASSERT_EQ #undef REGEXP_FLAG_ASSERT_EQ
......
...@@ -256,6 +256,7 @@ void CallPrinter::VisitRegExpLiteral(RegExpLiteral* node) { ...@@ -256,6 +256,7 @@ void CallPrinter::VisitRegExpLiteral(RegExpLiteral* node) {
Print("/"); Print("/");
PrintLiteral(node->pattern(), false); PrintLiteral(node->pattern(), false);
Print("/"); Print("/");
if (node->flags() & RegExp::kHasIndices) Print("d");
if (node->flags() & RegExp::kGlobal) Print("g"); if (node->flags() & RegExp::kGlobal) Print("g");
if (node->flags() & RegExp::kIgnoreCase) Print("i"); if (node->flags() & RegExp::kIgnoreCase) Print("i");
if (node->flags() & RegExp::kLinear) Print("l"); if (node->flags() & RegExp::kLinear) Print("l");
...@@ -1156,6 +1157,7 @@ void AstPrinter::VisitRegExpLiteral(RegExpLiteral* node) { ...@@ -1156,6 +1157,7 @@ void AstPrinter::VisitRegExpLiteral(RegExpLiteral* node) {
PrintLiteralIndented("PATTERN", node->raw_pattern(), false); PrintLiteralIndented("PATTERN", node->raw_pattern(), false);
int i = 0; int i = 0;
EmbeddedVector<char, 128> buf; EmbeddedVector<char, 128> buf;
if (node->flags() & RegExp::kHasIndices) buf[i++] = 'd';
if (node->flags() & RegExp::kGlobal) buf[i++] = 'g'; if (node->flags() & RegExp::kGlobal) buf[i++] = 'g';
if (node->flags() & RegExp::kIgnoreCase) buf[i++] = 'i'; if (node->flags() & RegExp::kIgnoreCase) buf[i++] = 'i';
if (node->flags() & RegExp::kLinear) buf[i++] = 'l'; if (node->flags() & RegExp::kLinear) buf[i++] = 'l';
......
...@@ -842,32 +842,5 @@ Handle<AccessorInfo> Accessors::MakeErrorStackInfo(Isolate* isolate) { ...@@ -842,32 +842,5 @@ Handle<AccessorInfo> Accessors::MakeErrorStackInfo(Isolate* isolate) {
&ErrorStackGetter, &ErrorStackSetter); &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 internal
} // namespace v8 } // namespace v8
...@@ -44,8 +44,6 @@ class JavaScriptFrame; ...@@ -44,8 +44,6 @@ class JavaScriptFrame;
kHasSideEffectToReceiver) \ kHasSideEffectToReceiver) \
V(_, function_prototype, FunctionPrototype, kHasNoSideEffect, \ V(_, function_prototype, FunctionPrototype, kHasNoSideEffect, \
kHasSideEffectToReceiver) \ kHasSideEffectToReceiver) \
V(_, regexp_result_indices, RegExpResultIndices, kHasSideEffectToReceiver, \
kHasSideEffectToReceiver) \
V(_, string_length, StringLength, kHasNoSideEffect, kHasSideEffectToReceiver) V(_, string_length, StringLength, kHasNoSideEffect, kHasSideEffectToReceiver)
#define ACCESSOR_SETTER_LIST(V) \ #define ACCESSOR_SETTER_LIST(V) \
......
This diff is collapsed.
...@@ -22,13 +22,14 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -22,13 +22,14 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
TNode<RawPtrT> LoadCodeObjectEntry(TNode<Code> code); TNode<RawPtrT> LoadCodeObjectEntry(TNode<Code> code);
// Allocate a RegExpResult with the given length (the number of captures, // Allocate either a JSRegExpResult or a JSRegExpResultWithIndices (depending
// including the match itself), index (the index where the match starts), // on has_indices) with the given length (the number of captures, including
// and input string. // the match itself), index (the index where the match starts), and input
// string.
TNode<JSRegExpResult> AllocateRegExpResult( TNode<JSRegExpResult> AllocateRegExpResult(
TNode<Context> context, TNode<Smi> length, TNode<Smi> index, TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
TNode<String> input, TNode<JSRegExp> regexp, TNode<Number> last_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<Object> FastLoadLastIndexBeforeSmiCheck(TNode<JSRegExp> regexp);
TNode<Smi> FastLoadLastIndex(TNode<JSRegExp> regexp) { TNode<Smi> FastLoadLastIndex(TNode<JSRegExp> regexp) {
......
...@@ -51,8 +51,8 @@ transitioning macro RegExpExec(implicit context: Context)( ...@@ -51,8 +51,8 @@ transitioning macro RegExpExec(implicit context: Context)(
} }
extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
implicit context: Context)( implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number):
JSRegExp, RegExpMatchInfo, String, Number): JSRegExpResult; JSRegExpResult|JSRegExpResultWithIndices;
const kGlobalOrSticky: constexpr int31 const kGlobalOrSticky: constexpr int31
generates 'JSRegExp::kGlobal | JSRegExp::kSticky'; generates 'JSRegExp::kGlobal | JSRegExp::kSticky';
...@@ -185,6 +185,7 @@ extern enum Flag constexpr 'JSRegExp::Flag' { ...@@ -185,6 +185,7 @@ extern enum Flag constexpr 'JSRegExp::Flag' {
kSticky, kSticky,
kUnicode, kUnicode,
kDotAll, kDotAll,
kHasIndices,
kLinear kLinear
} }
...@@ -243,6 +244,13 @@ transitioning javascript builtin RegExpPrototypeMultilineGetter( ...@@ -243,6 +244,13 @@ transitioning javascript builtin RegExpPrototypeMultilineGetter(
'RegExp.prototype.multiline'); '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( transitioning javascript builtin RegExpPrototypeLinearGetter(
js-implicit context: NativeContext, receiver: JSAny)(): JSAny { js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
return FlagGetter( return FlagGetter(
......
...@@ -462,6 +462,11 @@ ExternalReference ExternalReference::address_of_builtin_subclassing_flag() { ...@@ -462,6 +462,11 @@ ExternalReference ExternalReference::address_of_builtin_subclassing_flag() {
return ExternalReference(&FLAG_builtin_subclassing); 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() { ExternalReference ExternalReference::address_of_runtime_stats_flag() {
return ExternalReference(&TracingFlags::runtime_stats); return ExternalReference(&TracingFlags::runtime_stats);
} }
......
...@@ -99,16 +99,18 @@ class StatsCounter; ...@@ -99,16 +99,18 @@ class StatsCounter;
#define EXTERNAL_REFERENCE_LIST(V) \ #define EXTERNAL_REFERENCE_LIST(V) \
V(abort_with_reason, "abort_with_reason") \ 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_abs_constant, "double_absolute_constant") \
V(address_of_double_neg_constant, "double_negate_constant") \ V(address_of_double_neg_constant, "double_negate_constant") \
V(address_of_enable_experimental_regexp_engine, \ V(address_of_enable_experimental_regexp_engine, \
"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_abs_constant, "float_absolute_constant") \
V(address_of_float_neg_constant, "float_negate_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_min_int, "LDoubleConstant::min_int") \
V(address_of_mock_arraybuffer_allocator_flag, \ V(address_of_mock_arraybuffer_allocator_flag, \
"FLAG_mock_arraybuffer_allocator") \ "FLAG_mock_arraybuffer_allocator") \
V(address_of_builtin_subclassing_flag, "FLAG_builtin_subclassing") \
V(address_of_one_half, "LDoubleConstant::one_half") \ V(address_of_one_half, "LDoubleConstant::one_half") \
V(address_of_runtime_stats_flag, "TracingFlags::runtime_stats") \ V(address_of_runtime_stats_flag, "TracingFlags::runtime_stats") \
V(address_of_the_hole_nan, "the_hole_nan") \ V(address_of_the_hole_nan, "the_hole_nan") \
......
...@@ -843,6 +843,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) { ...@@ -843,6 +843,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kRegExpPrototypeSplit: case Builtins::kRegExpPrototypeSplit:
case Builtins::kRegExpPrototypeFlagsGetter: case Builtins::kRegExpPrototypeFlagsGetter:
case Builtins::kRegExpPrototypeGlobalGetter: case Builtins::kRegExpPrototypeGlobalGetter:
case Builtins::kRegExpPrototypeHasIndicesGetter:
case Builtins::kRegExpPrototypeIgnoreCaseGetter: case Builtins::kRegExpPrototypeIgnoreCaseGetter:
case Builtins::kRegExpPrototypeMatchAll: case Builtins::kRegExpPrototypeMatchAll:
case Builtins::kRegExpPrototypeMultilineGetter: case Builtins::kRegExpPrototypeMultilineGetter:
......
...@@ -4436,13 +4436,31 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() { ...@@ -4436,13 +4436,31 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() {
void Genesis::InitializeGlobal_harmony_regexp_match_indices() { void Genesis::InitializeGlobal_harmony_regexp_match_indices() {
if (!FLAG_harmony_regexp_match_indices) return; if (!FLAG_harmony_regexp_match_indices) return;
// Add indices accessor to JSRegExpResult's initial map. Handle<Map> source_map(native_context()->regexp_result_map(), isolate());
Handle<Map> initial_map(native_context()->regexp_result_map(), isolate()); Handle<Map> initial_map =
Descriptor d = Descriptor::AccessorConstant( Map::Copy(isolate(), source_map, "JSRegExpResult with indices");
factory()->indices_string(), factory()->regexp_result_indices_accessor(), initial_map->set_instance_size(JSRegExpResultWithIndices::kSize);
NONE); DCHECK_EQ(initial_map->GetInObjectProperties(),
Map::EnsureDescriptorSlack(isolate(), initial_map, 1); JSRegExpResultWithIndices::kInObjectPropertyCount);
initial_map->AppendDescriptor(isolate(), &d);
// 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() { void Genesis::InitializeGlobal_harmony_string_replaceall() {
...@@ -4788,16 +4806,6 @@ bool Genesis::InstallABunchOfRandomThings() { ...@@ -4788,16 +4806,6 @@ bool Genesis::InstallABunchOfRandomThings() {
{ {
PropertyAttributes attribs = DONT_ENUM; 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. // names descriptor.
{ {
Descriptor d = Descriptor::DataField( Descriptor d = Descriptor::DataField(
......
...@@ -209,6 +209,7 @@ ...@@ -209,6 +209,7 @@
V(_, globalThis_string, "globalThis") \ V(_, globalThis_string, "globalThis") \
V(_, groups_string, "groups") \ V(_, groups_string, "groups") \
V(_, has_string, "has") \ V(_, has_string, "has") \
V(_, has_indices_string, "hasIndices") \
V(_, ignoreCase_string, "ignoreCase") \ V(_, ignoreCase_string, "ignoreCase") \
V(_, illegal_access_string, "illegal access") \ V(_, illegal_access_string, "illegal access") \
V(_, illegal_argument_string, "illegal argument") \ V(_, illegal_argument_string, "illegal argument") \
...@@ -357,7 +358,6 @@ ...@@ -357,7 +358,6 @@
V(_, promise_debug_message_symbol) \ V(_, promise_debug_message_symbol) \
V(_, promise_forwarding_handler_symbol) \ V(_, promise_forwarding_handler_symbol) \
V(_, promise_handled_by_symbol) \ V(_, promise_handled_by_symbol) \
V(_, regexp_result_cached_indices_or_regexp_symbol) \
V(_, regexp_result_names_symbol) \ V(_, regexp_result_names_symbol) \
V(_, regexp_result_regexp_input_symbol) \ V(_, regexp_result_regexp_input_symbol) \
V(_, regexp_result_regexp_last_index_symbol) \ V(_, regexp_result_regexp_last_index_symbol) \
......
...@@ -214,6 +214,7 @@ enum ContextLookupFlags { ...@@ -214,6 +214,7 @@ enum ContextLookupFlags {
V(REGEXP_PROTOTYPE_MAP_INDEX, Map, regexp_prototype_map) \ V(REGEXP_PROTOTYPE_MAP_INDEX, Map, regexp_prototype_map) \
V(REGEXP_REPLACE_FUNCTION_INDEX, JSFunction, regexp_replace_function) \ V(REGEXP_REPLACE_FUNCTION_INDEX, JSFunction, regexp_replace_function) \
V(REGEXP_RESULT_MAP_INDEX, Map, regexp_result_map) \ 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_RESULT_INDICES_MAP_INDEX, Map, regexp_result_indices_map) \
V(REGEXP_SEARCH_FUNCTION_INDEX, JSFunction, regexp_search_function) \ V(REGEXP_SEARCH_FUNCTION_INDEX, JSFunction, regexp_search_function) \
V(REGEXP_SPLIT_FUNCTION_INDEX, JSFunction, regexp_split_function) \ V(REGEXP_SPLIT_FUNCTION_INDEX, JSFunction, regexp_split_function) \
......
...@@ -24,7 +24,13 @@ TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExp) ...@@ -24,7 +24,13 @@ TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExp)
OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResult, JSArray) OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResult, JSArray)
OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResultIndices, JSArray) OBJECT_CONSTRUCTORS_IMPL_CHECK_SUPER(JSRegExpResultIndices, JSArray)
inline JSRegExpResultWithIndices::JSRegExpResultWithIndices(Address ptr)
: JSRegExpResult(ptr) {
SLOW_DCHECK(IsJSArray());
}
CAST_ACCESSOR(JSRegExpResult) CAST_ACCESSOR(JSRegExpResult)
CAST_ACCESSOR(JSRegExpResultWithIndices)
CAST_ACCESSOR(JSRegExpResultIndices) CAST_ACCESSOR(JSRegExpResultIndices)
ACCESSORS(JSRegExp, last_index, Object, kLastIndexOffset) ACCESSORS(JSRegExp, last_index, Object, kLastIndexOffset)
......
...@@ -12,78 +12,6 @@ ...@@ -12,78 +12,6 @@
namespace v8 { namespace v8 {
namespace internal { 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( Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
Isolate* isolate, Handle<RegExpMatchInfo> match_info, Isolate* isolate, Handle<RegExpMatchInfo> match_info,
Handle<Object> maybe_names) { Handle<Object> maybe_names) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef V8_OBJECTS_JS_REGEXP_H_ #ifndef V8_OBJECTS_JS_REGEXP_H_
#define V8_OBJECTS_JS_REGEXP_H_ #define V8_OBJECTS_JS_REGEXP_H_
#include "src/objects/contexts.h"
#include "src/objects/js-array.h" #include "src/objects/js-array.h"
#include "torque-generated/bit-fields.h" #include "torque-generated/bit-fields.h"
...@@ -43,7 +44,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> { ...@@ -43,7 +44,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
DEFINE_TORQUE_GENERATED_JS_REG_EXP_FLAGS() DEFINE_TORQUE_GENERATED_JS_REG_EXP_FLAGS()
static base::Optional<Flag> FlagFromChar(char c) { static base::Optional<Flag> FlagFromChar(char c) {
STATIC_ASSERT(kFlagCount == 7); STATIC_ASSERT(kFlagCount == 8);
// clang-format off // clang-format off
return c == 'g' ? base::Optional<Flag>(kGlobal) return c == 'g' ? base::Optional<Flag>(kGlobal)
: c == 'i' ? base::Optional<Flag>(kIgnoreCase) : c == 'i' ? base::Optional<Flag>(kIgnoreCase)
...@@ -53,6 +54,8 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> { ...@@ -53,6 +54,8 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
: c == 's' ? base::Optional<Flag>(kDotAll) : c == 's' ? base::Optional<Flag>(kDotAll)
: (FLAG_enable_experimental_regexp_engine && c == 'l') : (FLAG_enable_experimental_regexp_engine && c == 'l')
? base::Optional<Flag>(kLinear) ? base::Optional<Flag>(kLinear)
: (FLAG_harmony_regexp_match_indices && c == 'd')
? base::Optional<Flag>(kHasIndices)
: base::Optional<Flag>(); : base::Optional<Flag>();
// clang-format on // clang-format on
} }
...@@ -65,6 +68,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> { ...@@ -65,6 +68,7 @@ class JSRegExp : public TorqueGeneratedJSRegExp<JSRegExp, JSObject> {
STATIC_ASSERT(static_cast<int>(kUnicode) == v8::RegExp::kUnicode); STATIC_ASSERT(static_cast<int>(kUnicode) == v8::RegExp::kUnicode);
STATIC_ASSERT(static_cast<int>(kDotAll) == v8::RegExp::kDotAll); STATIC_ASSERT(static_cast<int>(kDotAll) == v8::RegExp::kDotAll);
STATIC_ASSERT(static_cast<int>(kLinear) == v8::RegExp::kLinear); STATIC_ASSERT(static_cast<int>(kLinear) == v8::RegExp::kLinear);
STATIC_ASSERT(static_cast<int>(kHasIndices) == v8::RegExp::kHasIndices);
STATIC_ASSERT(kFlagCount == v8::RegExp::kFlagCount); STATIC_ASSERT(kFlagCount == v8::RegExp::kFlagCount);
DECL_ACCESSORS(last_index, Object) DECL_ACCESSORS(last_index, Object)
...@@ -249,24 +253,40 @@ class JSRegExpResult : public JSArray { ...@@ -249,24 +253,40 @@ class JSRegExpResult : public JSArray {
DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kHeaderSize, DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kHeaderSize,
TORQUE_GENERATED_JS_REG_EXP_RESULT_FIELDS) TORQUE_GENERATED_JS_REG_EXP_RESULT_FIELDS)
static MaybeHandle<JSArray> GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result);
// Indices of in-object properties. // Indices of in-object properties.
static const int kIndexIndex = 0; static const int kIndexIndex = 0;
static const int kInputIndex = 1; static const int kInputIndex = 1;
static const int kGroupsIndex = 2; static const int kGroupsIndex = 2;
// Private internal only fields. // Private internal only fields.
static const int kCachedIndicesOrRegExpIndex = 3; static const int kNamesIndex = 3;
static const int kNamesIndex = 4; static const int kRegExpInputIndex = 4;
static const int kRegExpInputIndex = 5; static const int kRegExpLastIndex = 5;
static const int kRegExpLastIndex = 6; static const int kInObjectPropertyCount = 6;
static const int kInObjectPropertyCount = 7;
static const int kMapIndexInContext = Context::REGEXP_RESULT_MAP_INDEX;
OBJECT_CONSTRUCTORS(JSRegExpResult, JSArray); 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. // JSRegExpResultIndices is just a JSArray with a specific initial map.
// This initial map adds in-object properties for "group" // This initial map adds in-object properties for "group"
// properties, as assigned by RegExp.prototype.exec, which allows // properties, as assigned by RegExp.prototype.exec, which allows
......
...@@ -10,6 +10,7 @@ bitfield struct JSRegExpFlags extends uint31 { ...@@ -10,6 +10,7 @@ bitfield struct JSRegExpFlags extends uint31 {
unicode: bool: 1 bit; unicode: bool: 1 bit;
dot_all: bool: 1 bit; dot_all: bool: 1 bit;
linear: bool: 1 bit; linear: bool: 1 bit;
has_indices: bool: 1 bit;
} }
@generateCppClass @generateCppClass
...@@ -34,6 +35,10 @@ RegExpBuiltinsAssembler::FastLoadLastIndex(FastJSRegExp): Smi; ...@@ -34,6 +35,10 @@ RegExpBuiltinsAssembler::FastLoadLastIndex(FastJSRegExp): Smi;
extern operator '.lastIndex=' macro extern operator '.lastIndex=' macro
RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void; RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void;
@doNotGenerateCast
extern class JSRegExpConstructor extends JSFunction
generates 'TNode<JSFunction>';
extern shape JSRegExpResult extends JSArray { extern shape JSRegExpResult extends JSArray {
// In-object properties: // In-object properties:
// The below fields are externally exposed. // The below fields are externally exposed.
...@@ -42,15 +47,14 @@ extern shape JSRegExpResult extends JSArray { ...@@ -42,15 +47,14 @@ extern shape JSRegExpResult extends JSArray {
groups: JSAny; groups: JSAny;
// The below fields are for internal use only. // The below fields are for internal use only.
cached_indices_or_regexp: JSRegExpResultIndices|JSRegExp;
names: FixedArray|Undefined; names: FixedArray|Undefined;
regexp_input: String; regexp_input: String;
regexp_last_index: Smi; regexp_last_index: Smi;
} }
@doNotGenerateCast extern shape JSRegExpResultWithIndices extends JSRegExpResult {
extern class JSRegExpConstructor extends JSFunction indices: JSAny;
generates 'TNode<JSFunction>'; }
extern shape JSRegExpResultIndices extends JSArray { extern shape JSRegExpResultIndices extends JSArray {
// In-object properties: // In-object properties:
......
...@@ -1662,6 +1662,9 @@ MaybeHandle<JSRegExp> ValueDeserializer::ReadJSRegExp() { ...@@ -1662,6 +1662,9 @@ MaybeHandle<JSRegExp> ValueDeserializer::ReadJSRegExp() {
if (!FLAG_enable_experimental_regexp_engine) { if (!FLAG_enable_experimental_regexp_engine) {
bad_flags_mask |= JSRegExp::kLinear; bad_flags_mask |= JSRegExp::kLinear;
} }
if (!FLAG_harmony_regexp_match_indices) {
bad_flags_mask |= JSRegExp::kHasIndices;
}
if ((raw_flags & bad_flags_mask) || if ((raw_flags & bad_flags_mask) ||
!JSRegExp::New(isolate_, pattern, static_cast<JSRegExp::Flags>(raw_flags)) !JSRegExp::New(isolate_, pattern, static_cast<JSRegExp::Flags>(raw_flags))
.ToHandle(&regexp)) { .ToHandle(&regexp)) {
......
...@@ -894,6 +894,21 @@ RUNTIME_FUNCTION(Runtime_RegExpExperimentalOneshotExec) { ...@@ -894,6 +894,21 @@ RUNTIME_FUNCTION(Runtime_RegExpExperimentalOneshotExec) {
last_match_info)); 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 { namespace {
class MatchInfoBackedMatch : public String::Match { class MatchInfoBackedMatch : public String::Match {
......
...@@ -385,6 +385,7 @@ namespace internal { ...@@ -385,6 +385,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_REGEXP(F, I) \ #define FOR_EACH_INTRINSIC_REGEXP(F, I) \
I(IsRegExp, 1, 1) \ I(IsRegExp, 1, 1) \
F(RegExpBuildIndices, 3, 1) \
F(RegExpExec, 4, 1) \ F(RegExpExec, 4, 1) \
F(RegExpExperimentalOneshotExec, 4, 1) \ F(RegExpExperimentalOneshotExec, 4, 1) \
F(RegExpExecMultiple, 4, 1) \ F(RegExpExecMultiple, 4, 1) \
......
...@@ -243,8 +243,9 @@ std::string GenerateRandomFlags(FuzzerArgs* args) { ...@@ -243,8 +243,9 @@ std::string GenerateRandomFlags(FuzzerArgs* args) {
// TODO(mbid,v8:10765): Find a way to generate the kLinear flag sometimes, // TODO(mbid,v8:10765): Find a way to generate the kLinear flag sometimes,
// but only for patterns that are supported by the experimental engine. // but only for patterns that are supported by the experimental engine.
constexpr size_t kFlagCount = JSRegExp::kFlagCount; constexpr size_t kFlagCount = JSRegExp::kFlagCount;
CHECK_EQ(JSRegExp::kLinear, 1 << (kFlagCount - 1)); CHECK_EQ(JSRegExp::kHasIndices, 1 << (kFlagCount - 1));
CHECK_EQ(JSRegExp::kDotAll, 1 << (kFlagCount - 2)); CHECK_EQ(JSRegExp::kLinear, 1 << (kFlagCount - 2));
CHECK_EQ(JSRegExp::kDotAll, 1 << (kFlagCount - 3));
STATIC_ASSERT((1 << kFlagCount) - 1 <= 0xFF); STATIC_ASSERT((1 << kFlagCount) - 1 <= 0xFF);
const size_t flags = RandomByte(args) & ((1 << kFlagCount) - 1); const size_t flags = RandomByte(args) & ((1 << kFlagCount) - 1);
...@@ -258,6 +259,7 @@ std::string GenerateRandomFlags(FuzzerArgs* args) { ...@@ -258,6 +259,7 @@ std::string GenerateRandomFlags(FuzzerArgs* args) {
if (flags & JSRegExp::kSticky) buffer[cursor++] = 'y'; if (flags & JSRegExp::kSticky) buffer[cursor++] = 'y';
if (flags & JSRegExp::kUnicode) buffer[cursor++] = 'u'; if (flags & JSRegExp::kUnicode) buffer[cursor++] = 'u';
if (flags & JSRegExp::kDotAll) buffer[cursor++] = 's'; if (flags & JSRegExp::kDotAll) buffer[cursor++] = 's';
if (flags & JSRegExp::kHasIndices) buffer[cursor++] = 'd';
return std::string(buffer, cursor); 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 @@ ...@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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. // Sanity test.
{ {
const re = /a+(?<Z>z)?/; const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz"); const m = re.exec("xaaaz");
assertEquals(m.indices, [[1, 5], [4, 5]]); assertEquals(m.indices, [[1, 5], [4, 5]]);
...@@ -15,7 +17,7 @@ ...@@ -15,7 +17,7 @@
// Capture groups that are not matched return `undefined`. // Capture groups that are not matched return `undefined`.
{ {
const re = /a+(?<Z>z)?/; const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaay"); const m = re.exec("xaaay");
assertEquals(m.indices, [[1, 4], undefined]); assertEquals(m.indices, [[1, 4], undefined]);
...@@ -24,7 +26,7 @@ ...@@ -24,7 +26,7 @@
// Two capture groups. // Two capture groups.
{ {
const re = /a+(?<A>zz)?(?<B>ii)?/; const re = /a+(?<A>zz)?(?<B>ii)?/d;
const m = re.exec("xaaazzii"); const m = re.exec("xaaazzii");
assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]); assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]);
...@@ -33,16 +35,16 @@ ...@@ -33,16 +35,16 @@
// No capture groups. // No capture groups.
{ {
const re = /a+/; const re = /a+/d;
const m = re.exec("xaaazzii"); const m = re.exec("xaaazzii");
assertEquals(m.indices [[1, 4]]); assertEquals(m.indices, [[1, 4]]);
assertEquals(m.indices.groups, undefined); assertEquals(m.indices.groups, undefined);
} }
// No match. // No match.
{ {
const re = /a+/; const re = /a+/d;
const m = re.exec("xzzii"); const m = re.exec("xzzii");
assertEquals(null, m); assertEquals(null, m);
...@@ -50,8 +52,8 @@ ...@@ -50,8 +52,8 @@
// Unnamed capture groups. // Unnamed capture groups.
{ {
const re = /a+(z)?/; const re = /a+(z)?/d;
const m = re.exec("xaaaz") const m = re.exec("xaaaz");
assertEquals(m.indices, [[1, 5], [4, 5]]); assertEquals(m.indices, [[1, 5], [4, 5]]);
assertEquals(m.indices.groups, undefined) assertEquals(m.indices.groups, undefined)
...@@ -59,7 +61,7 @@ ...@@ -59,7 +61,7 @@
// Named and unnamed capture groups. // Named and unnamed capture groups.
{ {
const re = /a+(z)?(?<Y>y)?/; const re = /a+(z)?(?<Y>y)?/d;
const m = re.exec("xaaazyy") const m = re.exec("xaaazyy")
assertEquals(m.indices, [[1, 6], [4, 5], [5, 6]]); assertEquals(m.indices, [[1, 6], [4, 5], [5, 6]]);
...@@ -69,7 +71,7 @@ ...@@ -69,7 +71,7 @@
// Verify property overwrite. // Verify property overwrite.
{ {
const re = /a+(?<Z>z)?/; const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz"); const m = re.exec("xaaaz");
m.indices = null; m.indices = null;
...@@ -98,7 +100,7 @@ ...@@ -98,7 +100,7 @@
} }
}); });
const re = /a+(?<Z>z)?/; const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz"); const m = re.exec("xaaaz");
assertEquals(m.indices.groups, {'Z': [4, 5]}) assertEquals(m.indices.groups, {'Z': [4, 5]})
...@@ -106,14 +108,14 @@ ...@@ -106,14 +108,14 @@
// Test atomic regexp. // Test atomic regexp.
{ {
const m = /undefined/.exec(); const m = (/undefined/d).exec();
assertEquals(m.indices, [[0, 9]]); assertEquals(m.indices, [[0, 9]]);
} }
// Test deleting unrelated fields does not break. // Test deleting unrelated fields does not break.
{ {
const m = /undefined/.exec(); const m = (/undefined/d).exec();
delete m['index']; delete m['index'];
gc(); gc();
assertEquals(m.indices, [[0, 9]]); assertEquals(m.indices, [[0, 9]]);
...@@ -121,7 +123,7 @@ ...@@ -121,7 +123,7 @@
// Stack overflow. // Stack overflow.
{ {
const re = /a+(?<Z>z)?/; const re = /a+(?<Z>z)?/d;
const m = re.exec("xaaaz"); const m = re.exec("xaaaz");
function rec() { function rec() {
...@@ -138,9 +140,30 @@ ...@@ -138,9 +140,30 @@
// Match between matches. // Match between matches.
{ {
const re = /a+(?<A>zz)?(?<B>ii)?/; const re = /a+(?<A>zz)?(?<B>ii)?/d;
const m = re.exec("xaaazzii"); const m = re.exec("xaaazzii");
assertTrue(/b+(?<C>cccc)?/.test("llllllbbbbbbcccc")); assertTrue(/b+(?<C>cccc)?/.test("llllllbbbbbbcccc"));
assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]); assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]);
assertEquals(m.indices.groups, {'A': [4, 6], 'B': [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); ...@@ -33,3 +33,10 @@ assertFalse((/asdf/ymsg).linear);
// unmodified regexps slow. // unmodified regexps slow.
assertTrue(%RegexpIsUnmodified(/asdf/)); assertTrue(%RegexpIsUnmodified(/asdf/));
assertTrue(%RegexpIsUnmodified(/asdf/l)); 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) ...@@ -15,8 +15,10 @@ assertThrows(() => new RegExp("((a*)*)*\1", "l"), SyntaxError)
assertFalse(RegExp.prototype.hasOwnProperty('linear')); assertFalse(RegExp.prototype.hasOwnProperty('linear'));
assertFalse(/123/.hasOwnProperty('linear')); assertFalse(/123/.hasOwnProperty('linear'));
// Redefined .linear shouldn't reflect in flags without
// --enable-experimental-regexp-engine.
{ {
let re = /./; let re = /./;
re.linear = true; Object.defineProperty(re, "linear", { get: function() { return true; } });
assertEquals("", re.flags); assertEquals("", re.flags);
} }
...@@ -581,6 +581,9 @@ ...@@ -581,6 +581,9 @@
'built-ins/String/prototype/at/*': [FAIL], 'built-ins/String/prototype/at/*': [FAIL],
'built-ins/TypedArray/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 ########################### ######################## NEEDS INVESTIGATION ###########################
# https://bugs.chromium.org/p/v8/issues/detail?id=7833 # https://bugs.chromium.org/p/v8/issues/detail?id=7833
......
...@@ -1511,6 +1511,25 @@ TEST_F(ValueSerializerTest, DecodeLinearRegExp) { ...@@ -1511,6 +1511,25 @@ TEST_F(ValueSerializerTest, DecodeLinearRegExp) {
i::FLAG_enable_experimental_regexp_engine = flag_was_enabled; 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) { TEST_F(ValueSerializerTest, RoundTripMap) {
Local<Value> value = RoundTripTest("var m = new Map(); m.set(42, 'foo'); m;"); Local<Value> value = RoundTripTest("var m = new Map(); m.set(42, 'foo'); m;");
ASSERT_TRUE(value->IsMap()); 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