Commit 8c79bead authored by Joshua Litt's avatar Joshua Litt Committed by Commit Bot

Reland "[regexp] Implement the match indices proposal"

Implements match indices for regexp, as specified by
https://github.com/tc39/proposal-regexp-match-indices,
a stage 3 TC39 proposal. This implementation is hidden
behind the '--harmony-regexp-match-indices' flag.

Regexp match indices extends the JSRegExpResult object
with an array of indices of matches, as well as a
dictionary of capture names to match indices.

Bug: v8:9548
Change-Id: Ia9efcee00d997dda6158539b8d0f4c4e5965e5e7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1771379
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63581}
parent 6192ecb0
...@@ -913,6 +913,7 @@ action("postmortem-metadata") { ...@@ -913,6 +913,7 @@ action("postmortem-metadata") {
"src/objects/js-promise-inl.h", "src/objects/js-promise-inl.h",
"src/objects/js-promise.h", "src/objects/js-promise.h",
"src/objects/js-regexp-inl.h", "src/objects/js-regexp-inl.h",
"src/objects/js-regexp.cc",
"src/objects/js-regexp.h", "src/objects/js-regexp.h",
"src/objects/js-regexp-string-iterator-inl.h", "src/objects/js-regexp-string-iterator-inl.h",
"src/objects/js-regexp-string-iterator.h", "src/objects/js-regexp-string-iterator.h",
...@@ -2558,6 +2559,7 @@ v8_source_set("v8_base_without_compiler") { ...@@ -2558,6 +2559,7 @@ v8_source_set("v8_base_without_compiler") {
"src/objects/js-regexp-inl.h", "src/objects/js-regexp-inl.h",
"src/objects/js-regexp-string-iterator-inl.h", "src/objects/js-regexp-string-iterator-inl.h",
"src/objects/js-regexp-string-iterator.h", "src/objects/js-regexp-string-iterator.h",
"src/objects/js-regexp.cc",
"src/objects/js-regexp.h", "src/objects/js-regexp.h",
"src/objects/js-relative-time-format-inl.h", "src/objects/js-relative-time-format-inl.h",
"src/objects/js-relative-time-format.cc", "src/objects/js-relative-time-format.cc",
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "src/objects/contexts.h" #include "src/objects/contexts.h"
#include "src/objects/field-index-inl.h" #include "src/objects/field-index-inl.h"
#include "src/objects/js-array-inl.h" #include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/objects/module-inl.h" #include "src/objects/module-inl.h"
#include "src/objects/property-details.h" #include "src/objects/property-details.h"
#include "src/objects/prototype.h" #include "src/objects/prototype.h"
...@@ -840,5 +841,25 @@ Handle<AccessorInfo> Accessors::MakeErrorStackInfo(Isolate* isolate) { ...@@ -840,5 +841,25 @@ 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())));
Handle<Object> indices(
JSRegExpResult::GetAndCacheIndices(isolate, regexp_result));
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
...@@ -43,6 +43,8 @@ class JavaScriptFrame; ...@@ -43,6 +43,8 @@ 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) \
......
...@@ -1460,9 +1460,22 @@ RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void; ...@@ -1460,9 +1460,22 @@ RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void;
@hasSameInstanceTypeAsParent @hasSameInstanceTypeAsParent
extern class JSRegExpResult extends JSArray { extern class JSRegExpResult extends JSArray {
// In-object properties:
// The below fields are externally exposed.
index: JSAny; index: JSAny;
input: JSAny; input: JSAny;
groups: JSAny; groups: JSAny;
// The below fields are for internal use only.
cached_indices_or_match_info: JSRegExpResultIndices | RegExpMatchInfo;
names: FixedArray | Undefined;
}
@hasSameInstanceTypeAsParent
extern class JSRegExpResultIndices extends JSArray {
// In-object properties:
// The groups field is externally exposed.
groups: JSAny;
} }
@generateCppClass @generateCppClass
......
...@@ -80,7 +80,8 @@ TNode<RawPtrT> RegExpBuiltinsAssembler::LoadCodeObjectEntry(TNode<Code> code) { ...@@ -80,7 +80,8 @@ TNode<RawPtrT> RegExpBuiltinsAssembler::LoadCodeObjectEntry(TNode<Code> code) {
TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult( TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult(
TNode<Context> context, TNode<Smi> length, TNode<Smi> index, TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
TNode<String> input, TNode<FixedArray>* elements_out) { TNode<String> input, TNode<RegExpMatchInfo> match_info,
TNode<FixedArray>* elements_out) {
CSA_ASSERT(this, SmiLessThanOrEqual( CSA_ASSERT(this, SmiLessThanOrEqual(
length, SmiConstant(JSArray::kMaxFastArrayLength))); length, SmiConstant(JSArray::kMaxFastArrayLength)));
CSA_ASSERT(this, SmiGreaterThan(length, SmiConstant(0))); CSA_ASSERT(this, SmiGreaterThan(length, SmiConstant(0)));
...@@ -107,11 +108,22 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult( ...@@ -107,11 +108,22 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult(
TNode<JSRegExpResult> result = CAST(array); TNode<JSRegExpResult> result = CAST(array);
// Load undefined value once here to avoid multiple LoadRoots.
TNode<Oddball> undefined_value = UncheckedCast<Oddball>(
CodeAssembler::LoadRoot(RootIndex::kUndefinedValue));
StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kIndexOffset, index); StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kIndexOffset, index);
// TODO(jgruber,tebbi): Could skip barrier but the MemoryOptimizer complains. // TODO(jgruber,tebbi): Could skip barrier but the MemoryOptimizer complains.
StoreObjectField(result, JSRegExpResult::kInputOffset, input); StoreObjectField(result, JSRegExpResult::kInputOffset, input);
StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kGroupsOffset, StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kGroupsOffset,
UndefinedConstant()); undefined_value);
StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kNamesOffset,
undefined_value);
// Stash match_info in order to build JSRegExpResultIndices lazily when the
// 'indices' property is accessed.
StoreObjectField(result, JSRegExpResult::kCachedIndicesOrMatchInfoOffset,
match_info);
// Finish elements initialization. // Finish elements initialization.
...@@ -213,7 +225,7 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( ...@@ -213,7 +225,7 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
TNode<FixedArray> result_elements; TNode<FixedArray> result_elements;
TNode<JSRegExpResult> result = AllocateRegExpResult( TNode<JSRegExpResult> result = AllocateRegExpResult(
context, num_results, start, string, &result_elements); context, num_results, start, string, match_info, &result_elements);
UnsafeStoreFixedArrayElement(result_elements, 0, first); UnsafeStoreFixedArrayElement(result_elements, 0, first);
...@@ -289,6 +301,9 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( ...@@ -289,6 +301,9 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
TNode<IntPtrT> names_length = LoadAndUntagFixedArrayBaseLength(names); TNode<IntPtrT> names_length = LoadAndUntagFixedArrayBaseLength(names);
CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrZero())); CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrZero()));
// Stash names in case we need them to build the indices array later.
StoreObjectField(result, JSRegExpResult::kNamesOffset, names);
// Allocate a new object to store the named capture properties. // Allocate a new object to store the named capture properties.
// TODO(jgruber): Could be optimized by adding the object map to the heap // TODO(jgruber): Could be optimized by adding the object map to the heap
// root list. // root list.
......
...@@ -37,7 +37,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -37,7 +37,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
// and input string. // 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<FixedArray>* elements_out = nullptr); TNode<String> input, TNode<RegExpMatchInfo> match_info,
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) {
......
...@@ -208,6 +208,7 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import) ...@@ -208,6 +208,7 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import)
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \ V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
V(harmony_weak_refs, "harmony weak references") \ V(harmony_weak_refs, "harmony weak references") \
V(harmony_optional_chaining, "harmony optional chaining syntax") \ V(harmony_optional_chaining, "harmony optional chaining syntax") \
V(harmony_regexp_match_indices, "harmony regexp match indices") \
V(harmony_nullish, "harmony nullish operator") V(harmony_nullish, "harmony nullish operator")
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
......
...@@ -284,6 +284,9 @@ class Genesis { ...@@ -284,6 +284,9 @@ class Genesis {
void TransferNamedProperties(Handle<JSObject> from, Handle<JSObject> to); void TransferNamedProperties(Handle<JSObject> from, Handle<JSObject> to);
void TransferIndexedProperties(Handle<JSObject> from, Handle<JSObject> to); void TransferIndexedProperties(Handle<JSObject> from, Handle<JSObject> to);
Handle<Map> CreateInitialMapForArraySubclass(int size,
int inobject_properties);
static bool CompileExtension(Isolate* isolate, v8::Extension* extension); static bool CompileExtension(Isolate* isolate, v8::Extension* extension);
Isolate* isolate_; Isolate* isolate_;
...@@ -4419,6 +4422,18 @@ void Genesis::InitializeGlobal_harmony_promise_all_settled() { ...@@ -4419,6 +4422,18 @@ void Genesis::InitializeGlobal_harmony_promise_all_settled() {
} }
} }
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);
}
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_intl_date_format_range() { void Genesis::InitializeGlobal_harmony_intl_date_format_range() {
...@@ -4900,42 +4915,10 @@ bool Genesis::InstallNatives() { ...@@ -4900,42 +4915,10 @@ bool Genesis::InstallNatives() {
// predefines the properties index, input, and groups). // predefines the properties index, input, and groups).
{ {
// JSRegExpResult initial map. // JSRegExpResult initial map.
// Add additional slack to the initial map in case regexp_match_indices
// Find global.Array.prototype to inherit from. // are enabled to account for the additional descriptor.
Handle<JSFunction> array_constructor(native_context()->array_function(), Handle<Map> initial_map = CreateInitialMapForArraySubclass(
isolate()); JSRegExpResult::kSize, JSRegExpResult::kInObjectPropertyCount);
Handle<JSObject> array_prototype(
JSObject::cast(array_constructor->instance_prototype()), isolate());
// Add initial map.
Handle<Map> initial_map = factory()->NewMap(
JS_ARRAY_TYPE, JSRegExpResult::kSize, TERMINAL_FAST_ELEMENTS_KIND,
JSRegExpResult::kInObjectPropertyCount);
initial_map->SetConstructor(*array_constructor);
// Set prototype on map.
initial_map->set_has_non_instance_prototype(false);
Map::SetPrototype(isolate(), initial_map, array_prototype);
// Update map with length accessor from Array and add "index", "input" and
// "groups".
Map::EnsureDescriptorSlack(isolate(), initial_map,
JSRegExpResult::kInObjectPropertyCount + 1);
// length descriptor.
{
JSFunction array_function = native_context()->array_function();
Handle<DescriptorArray> array_descriptors(
array_function.initial_map().instance_descriptors(), isolate());
Handle<String> length = factory()->length_string();
int old = array_descriptors->SearchWithCache(
isolate(), *length, array_function.initial_map());
DCHECK_NE(old, DescriptorArray::kNotFound);
Descriptor d = Descriptor::AccessorConstant(
length, handle(array_descriptors->GetStrongValue(old), isolate()),
array_descriptors->GetDetails(old).attributes());
initial_map->AppendDescriptor(isolate(), &d);
}
// index descriptor. // index descriptor.
{ {
...@@ -4961,9 +4944,53 @@ bool Genesis::InstallNatives() { ...@@ -4961,9 +4944,53 @@ bool Genesis::InstallNatives() {
initial_map->AppendDescriptor(isolate(), &d); initial_map->AppendDescriptor(isolate(), &d);
} }
// Private internal only fields. All of the remaining fields have special
// symbols to prevent their use in Javascript.
// cached_indices_or_match_info descriptor.
{
PropertyAttributes attribs = DONT_ENUM;
{
Descriptor d = Descriptor::DataField(
isolate(),
factory()->regexp_result_cached_indices_or_match_info_symbol(),
JSRegExpResult::kCachedIndicesOrMatchInfoIndex, attribs,
Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
}
// names descriptor.
{
Descriptor d = Descriptor::DataField(
isolate(), factory()->regexp_result_names_symbol(),
JSRegExpResult::kNamesIndex, attribs, Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
}
}
native_context()->set_regexp_result_map(*initial_map); native_context()->set_regexp_result_map(*initial_map);
} }
// Create a constructor for JSRegExpResultIndices (a variant of Array that
// predefines the groups property).
{
// JSRegExpResultIndices initial map.
Handle<Map> initial_map = CreateInitialMapForArraySubclass(
JSRegExpResultIndices::kSize,
JSRegExpResultIndices::kInObjectPropertyCount);
// groups descriptor.
{
Descriptor d = Descriptor::DataField(
isolate(), factory()->groups_string(),
JSRegExpResultIndices::kGroupsIndex, NONE, Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
DCHECK_EQ(initial_map->LastAdded(),
JSRegExpResultIndices::kGroupsDescriptorIndex);
}
native_context()->set_regexp_result_indices_map(*initial_map);
}
// Add @@iterator method to the arguments object maps. // Add @@iterator method to the arguments object maps.
{ {
PropertyAttributes attribs = DONT_ENUM; PropertyAttributes attribs = DONT_ENUM;
...@@ -5365,6 +5392,45 @@ void Genesis::TransferObject(Handle<JSObject> from, Handle<JSObject> to) { ...@@ -5365,6 +5392,45 @@ void Genesis::TransferObject(Handle<JSObject> from, Handle<JSObject> to) {
JSObject::ForceSetPrototype(to, proto); JSObject::ForceSetPrototype(to, proto);
} }
Handle<Map> Genesis::CreateInitialMapForArraySubclass(int size,
int inobject_properties) {
// Find global.Array.prototype to inherit from.
Handle<JSFunction> array_constructor(native_context()->array_function(),
isolate());
Handle<JSObject> array_prototype(native_context()->initial_array_prototype(),
isolate());
// Add initial map.
Handle<Map> initial_map = factory()->NewMap(
JS_ARRAY_TYPE, size, TERMINAL_FAST_ELEMENTS_KIND, inobject_properties);
initial_map->SetConstructor(*array_constructor);
// Set prototype on map.
initial_map->set_has_non_instance_prototype(false);
Map::SetPrototype(isolate(), initial_map, array_prototype);
// Update map with length accessor from Array.
static constexpr int kTheLengthAccessor = 1;
Map::EnsureDescriptorSlack(isolate(), initial_map,
inobject_properties + kTheLengthAccessor);
// length descriptor.
{
JSFunction array_function = native_context()->array_function();
Handle<DescriptorArray> array_descriptors(
array_function.initial_map().instance_descriptors(), isolate());
Handle<String> length = factory()->length_string();
int old = array_descriptors->SearchWithCache(isolate(), *length,
array_function.initial_map());
DCHECK_NE(old, DescriptorArray::kNotFound);
Descriptor d = Descriptor::AccessorConstant(
length, handle(array_descriptors->GetStrongValue(old), isolate()),
array_descriptors->GetDetails(old).attributes());
initial_map->AppendDescriptor(isolate(), &d);
}
return initial_map;
}
Genesis::Genesis( Genesis::Genesis(
Isolate* isolate, MaybeHandle<JSGlobalProxy> maybe_global_proxy, Isolate* isolate, MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_proxy_template, v8::Local<v8::ObjectTemplate> global_proxy_template,
......
...@@ -202,6 +202,7 @@ ...@@ -202,6 +202,7 @@
V(_, illegal_access_string, "illegal access") \ V(_, illegal_access_string, "illegal access") \
V(_, illegal_argument_string, "illegal argument") \ V(_, illegal_argument_string, "illegal argument") \
V(_, index_string, "index") \ V(_, index_string, "index") \
V(_, indices_string, "indices") \
V(_, Infinity_string, "Infinity") \ V(_, Infinity_string, "Infinity") \
V(_, infinity_string, "infinity") \ V(_, infinity_string, "infinity") \
V(_, input_string, "input") \ V(_, input_string, "input") \
...@@ -342,6 +343,8 @@ ...@@ -342,6 +343,8 @@
V(_, promise_debug_marker_symbol) \ V(_, promise_debug_marker_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_match_info_symbol) \
V(_, regexp_result_names_symbol) \
V(_, sealed_symbol) \ V(_, sealed_symbol) \
V(_, stack_trace_symbol) \ V(_, stack_trace_symbol) \
V(_, strict_function_transition_symbol) \ V(_, strict_function_transition_symbol) \
......
...@@ -233,6 +233,7 @@ enum ContextLookupFlags { ...@@ -233,6 +233,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_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) \
V(INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX, Map, \ V(INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX, Map, \
......
...@@ -451,6 +451,7 @@ V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, ...@@ -451,6 +451,7 @@ V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
V(JSProxy, JS_PROXY_TYPE) \ V(JSProxy, JS_PROXY_TYPE) \
V(JSRegExp, JS_REGEXP_TYPE) \ V(JSRegExp, JS_REGEXP_TYPE) \
V(JSRegExpResult, JS_ARRAY_TYPE) \ V(JSRegExpResult, JS_ARRAY_TYPE) \
V(JSRegExpResultIndices, JS_ARRAY_TYPE) \
V(JSRegExpStringIterator, JS_REGEXP_STRING_ITERATOR_TYPE) \ V(JSRegExpStringIterator, JS_REGEXP_STRING_ITERATOR_TYPE) \
V(JSSet, JS_SET_TYPE) \ V(JSSet, JS_SET_TYPE) \
V(JSStringIterator, JS_STRING_ITERATOR_TYPE) \ V(JSStringIterator, JS_STRING_ITERATOR_TYPE) \
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "src/objects/js-regexp.h" #include "src/objects/js-regexp.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/objects-inl.h" // Needed for write barriers #include "src/objects/objects-inl.h" // Needed for write barriers
#include "src/objects/smi.h" #include "src/objects/smi.h"
#include "src/objects/string.h" #include "src/objects/string.h"
...@@ -18,9 +19,20 @@ namespace v8 { ...@@ -18,9 +19,20 @@ namespace v8 {
namespace internal { namespace internal {
TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExp) TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExp)
// TQ_OBJECT_CONSTRUCTORS_IMPL(JSRegExpResult, JSArray)
OBJECT_CONSTRUCTORS_IMPL(JSRegExpResult, JSArray)
OBJECT_CONSTRUCTORS_IMPL(JSRegExpResultIndices, JSArray)
// CAST_ACCESSOR(JSRegExp)
CAST_ACCESSOR(JSRegExpResult)
CAST_ACCESSOR(JSRegExpResultIndices)
ACCESSORS(JSRegExp, last_index, Object, kLastIndexOffset) ACCESSORS(JSRegExp, last_index, Object, kLastIndexOffset)
ACCESSORS(JSRegExpResult, cached_indices_or_match_info, Object,
kCachedIndicesOrMatchInfoOffset)
ACCESSORS(JSRegExpResult, names, Object, kNamesOffset)
JSRegExp::Type JSRegExp::TypeTag() const { JSRegExp::Type JSRegExp::TypeTag() const {
Object data = this->data(); Object data = this->data();
if (data.IsUndefined()) return JSRegExp::NOT_COMPILED; if (data.IsUndefined()) return JSRegExp::NOT_COMPILED;
......
// Copyright 2019 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.
#include "src/objects/js-regexp.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h"
namespace v8 {
namespace internal {
Handle<JSArray> JSRegExpResult::GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result) {
// Check for cached indices.
Handle<Object> indices_or_match_info(
regexp_result->cached_indices_or_match_info(), isolate);
if (indices_or_match_info->IsRegExpMatchInfo()) {
// 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.
Handle<RegExpMatchInfo> match_info(
RegExpMatchInfo::cast(regexp_result->cached_indices_or_match_info()),
isolate);
Handle<Object> maybe_names(regexp_result->names(), isolate);
indices_or_match_info =
JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);
// Cache the result and clear the names array.
regexp_result->set_cached_indices_or_match_info(*indices_or_match_info);
regexp_result->set_names(ReadOnlyRoots(isolate).undefined_value());
}
return Handle<JSArray>::cast(indices_or_match_info);
}
Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
Isolate* isolate, Handle<RegExpMatchInfo> match_info,
Handle<Object> maybe_names) {
Handle<JSRegExpResultIndices> indices(Handle<JSRegExpResultIndices>::cast(
isolate->factory()->NewJSObjectFromMap(
isolate->regexp_result_indices_map())));
// Initialize indices length to avoid having a partially initialized object
// should GC be triggered by creating a NewFixedArray.
indices->set_length(Smi::kZero);
// Build indices array from RegExpMatchInfo.
int num_indices = match_info->NumberOfCaptureRegisters();
int num_results = num_indices >> 1;
Handle<FixedArray> indices_array =
isolate->factory()->NewFixedArray(num_results);
JSArray::SetContent(indices, indices_array);
for (int i = 0; i < num_results; i++) {
int base_offset = i * 2;
int start_offset = match_info->Capture(base_offset);
int end_offset = match_info->Capture(base_offset + 1);
// Any unmatched captures are set to undefined, otherwise we set them to a
// subarray of the indices.
if (start_offset == -1) {
indices_array->set(i, ReadOnlyRoots(isolate).undefined_value());
} else {
Handle<FixedArray> indices_sub_array(
isolate->factory()->NewFixedArray(2));
indices_sub_array->set(0, Smi::FromInt(start_offset));
indices_sub_array->set(1, Smi::FromInt(end_offset));
Handle<JSArray> indices_sub_jsarray =
isolate->factory()->NewJSArrayWithElements(indices_sub_array,
PACKED_SMI_ELEMENTS, 2);
indices_array->set(i, *indices_sub_jsarray);
}
}
// If there are no capture groups, set the groups property to undefined.
FieldIndex groups_index =
FieldIndex::ForDescriptor(indices->map(), kGroupsDescriptorIndex);
if (maybe_names->IsUndefined(isolate)) {
indices->RawFastPropertyAtPut(groups_index,
ReadOnlyRoots(isolate).undefined_value());
return indices;
}
// Create a groups property which returns a dictionary of named captures to
// their corresponding capture indices.
Handle<FixedArray> names(Handle<FixedArray>::cast(maybe_names));
int num_names = names->length() >> 1;
Handle<NameDictionary> group_names = NameDictionary::New(isolate, num_names);
for (int i = 0; i < num_names; i++) {
int base_offset = i * 2;
int name_offset = base_offset;
int index_offset = base_offset + 1;
Handle<String> name(String::cast(names->get(name_offset)), isolate);
Handle<Smi> smi_index(Smi::cast(names->get(index_offset)), isolate);
Handle<Object> capture_indices(indices_array->get(smi_index->value()),
isolate);
if (!capture_indices->IsUndefined(isolate)) {
capture_indices = Handle<JSArray>::cast(capture_indices);
}
group_names = NameDictionary::Add(
isolate, group_names, name, capture_indices, PropertyDetails::Empty());
}
// Convert group_names to a JSObject and store at the groups property of the
// result indices.
Handle<FixedArrayBase> elements = isolate->factory()->empty_fixed_array();
Handle<HeapObject> null =
Handle<HeapObject>::cast(isolate->factory()->null_value());
Handle<JSObject> js_group_names =
isolate->factory()->NewSlowJSObjectWithPropertiesAndElements(
null, group_names, elements);
indices->RawFastPropertyAtPut(groups_index, *js_group_names);
return indices;
}
} // namespace internal
} // namespace v8
...@@ -208,18 +208,63 @@ DEFINE_OPERATORS_FOR_FLAGS(JSRegExp::Flags) ...@@ -208,18 +208,63 @@ DEFINE_OPERATORS_FOR_FLAGS(JSRegExp::Flags)
// After creation the result must be treated as a JSArray in all regards. // After creation the result must be treated as a JSArray in all regards.
class JSRegExpResult : public JSArray { class JSRegExpResult : public JSArray {
public: public:
DECL_CAST(JSRegExpResult)
// TODO(joshualitt): We would like to add printers and verifiers to
// JSRegExpResult, and maybe JSRegExpResultIndices, but both have the same
// instance type as JSArray.
// cached_indices_or_match_info and names, are used to construct the
// JSRegExpResultIndices returned from the indices property lazily.
DECL_ACCESSORS(cached_indices_or_match_info, Object)
DECL_ACCESSORS(names, Object)
// Layout description. // Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kSize, DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kSize,
TORQUE_GENERATED_JSREG_EXP_RESULT_FIELDS) TORQUE_GENERATED_JSREG_EXP_RESULT_FIELDS)
static Handle<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;
static const int kInObjectPropertyCount = 3;
private: // Private internal only fields.
DISALLOW_IMPLICIT_CONSTRUCTORS(JSRegExpResult); static const int kCachedIndicesOrMatchInfoIndex = 3;
static const int kNamesIndex = 4;
static const int kInObjectPropertyCount = 5;
OBJECT_CONSTRUCTORS(JSRegExpResult, JSArray);
};
// 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
// faster creation of RegExp exec results.
// This class just holds constants used when creating the result.
// After creation the result must be treated as a JSArray in all regards.
class JSRegExpResultIndices : public JSArray {
public:
DECL_CAST(JSRegExpResultIndices)
// Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(
JSArray::kSize, TORQUE_GENERATED_JSREG_EXP_RESULT_INDICES_FIELDS)
static Handle<JSRegExpResultIndices> BuildIndices(
Isolate* isolate, Handle<RegExpMatchInfo> match_info,
Handle<Object> maybe_names);
// Indices of in-object properties.
static const int kGroupsIndex = 0;
static const int kInObjectPropertyCount = 1;
// Descriptor index of groups.
static const int kGroupsDescriptorIndex = 1;
OBJECT_CONSTRUCTORS(JSRegExpResultIndices, JSArray);
}; };
} // namespace internal } // namespace internal
......
...@@ -158,6 +158,7 @@ class ZoneForwardList; ...@@ -158,6 +158,7 @@ class ZoneForwardList;
V(JSReceiver) \ V(JSReceiver) \
V(JSRegExp) \ V(JSRegExp) \
V(JSRegExpResult) \ V(JSRegExpResult) \
V(JSRegExpResultIndices) \
V(JSRegExpStringIterator) \ V(JSRegExpStringIterator) \
V(JSSet) \ V(JSSet) \
V(JSSetIterator) \ V(JSSetIterator) \
......
// Copyright 2019 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: --harmony-regexp-match-indices
// Sanity test.
{
const re = /a+(?<Z>z)?/;
const m = re.exec("xaaaz");
assertEquals(m.indices, [[1, 5], [4, 5]]);
assertEquals(m.indices.groups, {'Z': [4, 5]})
}
// Capture groups that are not matched return `undefined`.
{
const re = /a+(?<Z>z)?/;
const m = re.exec("xaaay");
assertEquals(m.indices, [[1, 4], undefined]);
assertEquals(m.indices.groups, {'Z': undefined});
}
// Two capture groups.
{
const re = /a+(?<A>zz)?(?<B>ii)?/;
const m = re.exec("xaaazzii");
assertEquals(m.indices, [[1, 8], [4, 6], [6, 8]]);
assertEquals(m.indices.groups, {'A': [4, 6], 'B': [6, 8]});
}
// No capture groups.
{
const re = /a+/;
const m = re.exec("xaaazzii");
assertEquals(m.indices [[1, 4]]);
assertEquals(m.indices.groups, undefined);
}
// No match.
{
const re = /a+/;
const m = re.exec("xzzii");
assertEquals(null, m);
}
// Unnamed capture groups.
{
const re = /a+(z)?/;
const m = re.exec("xaaaz")
assertEquals(m.indices, [[1, 5], [4, 5]]);
assertEquals(m.indices.groups, undefined)
}
// Named and unnamed capture groups.
{
const re = /a+(z)?(?<Y>y)?/;
const m = re.exec("xaaazyy")
assertEquals(m.indices, [[1, 6], [4, 5], [5, 6]]);
assertEquals(m.indices.groups, {'Y': [5, 6]})
}
// Verify property overwrite.
{
const re = /a+(?<Z>z)?/;
const m = re.exec("xaaaz");
m.indices = null;
assertEquals(null, m.indices);
}
// Mess with array prototype, we should still do the right thing.
{
Object.defineProperty(Array.prototype, "groups", {
get: () => {
assertUnreachable();
return null;
},
set: (x) => {
assertUnreachable();
}
});
Object.defineProperty(Array.prototype, "0", {
get: () => {
assertUnreachable();
return null;
},
set: (x) => {
assertUnreachable();
}
});
const re = /a+(?<Z>z)?/;
const m = re.exec("xaaaz");
assertEquals(m.indices.groups, {'Z': [4, 5]})
}
...@@ -291,54 +291,54 @@ KNOWN_MAPS = { ...@@ -291,54 +291,54 @@ KNOWN_MAPS = {
("read_only_space", 0x023a1): (87, "EnumCacheMap"), ("read_only_space", 0x023a1): (87, "EnumCacheMap"),
("read_only_space", 0x02441): (82, "ArrayBoilerplateDescriptionMap"), ("read_only_space", 0x02441): (82, "ArrayBoilerplateDescriptionMap"),
("read_only_space", 0x02631): (90, "InterceptorInfoMap"), ("read_only_space", 0x02631): (90, "InterceptorInfoMap"),
("read_only_space", 0x04eb1): (77, "AccessCheckInfoMap"), ("read_only_space", 0x04ef9): (77, "AccessCheckInfoMap"),
("read_only_space", 0x04f01): (78, "AccessorInfoMap"), ("read_only_space", 0x04f49): (78, "AccessorInfoMap"),
("read_only_space", 0x04f51): (79, "AccessorPairMap"), ("read_only_space", 0x04f99): (79, "AccessorPairMap"),
("read_only_space", 0x04fa1): (80, "AliasedArgumentsEntryMap"), ("read_only_space", 0x04fe9): (80, "AliasedArgumentsEntryMap"),
("read_only_space", 0x04ff1): (81, "AllocationMementoMap"), ("read_only_space", 0x05039): (81, "AllocationMementoMap"),
("read_only_space", 0x05041): (83, "AsmWasmDataMap"), ("read_only_space", 0x05089): (83, "AsmWasmDataMap"),
("read_only_space", 0x05091): (84, "AsyncGeneratorRequestMap"), ("read_only_space", 0x050d9): (84, "AsyncGeneratorRequestMap"),
("read_only_space", 0x050e1): (85, "ClassPositionsMap"), ("read_only_space", 0x05129): (85, "ClassPositionsMap"),
("read_only_space", 0x05131): (86, "DebugInfoMap"), ("read_only_space", 0x05179): (86, "DebugInfoMap"),
("read_only_space", 0x05181): (88, "FunctionTemplateInfoMap"), ("read_only_space", 0x051c9): (88, "FunctionTemplateInfoMap"),
("read_only_space", 0x051d1): (89, "FunctionTemplateRareDataMap"), ("read_only_space", 0x05219): (89, "FunctionTemplateRareDataMap"),
("read_only_space", 0x05221): (91, "InterpreterDataMap"), ("read_only_space", 0x05269): (91, "InterpreterDataMap"),
("read_only_space", 0x05271): (92, "ObjectTemplateInfoMap"), ("read_only_space", 0x052b9): (92, "ObjectTemplateInfoMap"),
("read_only_space", 0x052c1): (93, "PromiseCapabilityMap"), ("read_only_space", 0x05309): (93, "PromiseCapabilityMap"),
("read_only_space", 0x05311): (94, "PromiseReactionMap"), ("read_only_space", 0x05359): (94, "PromiseReactionMap"),
("read_only_space", 0x05361): (95, "PrototypeInfoMap"), ("read_only_space", 0x053a9): (95, "PrototypeInfoMap"),
("read_only_space", 0x053b1): (96, "ScriptMap"), ("read_only_space", 0x053f9): (96, "ScriptMap"),
("read_only_space", 0x05401): (97, "SourcePositionTableWithFrameCacheMap"), ("read_only_space", 0x05449): (97, "SourcePositionTableWithFrameCacheMap"),
("read_only_space", 0x05451): (98, "SourceTextModuleInfoEntryMap"), ("read_only_space", 0x05499): (98, "SourceTextModuleInfoEntryMap"),
("read_only_space", 0x054a1): (99, "StackFrameInfoMap"), ("read_only_space", 0x054e9): (99, "StackFrameInfoMap"),
("read_only_space", 0x054f1): (100, "StackTraceFrameMap"), ("read_only_space", 0x05539): (100, "StackTraceFrameMap"),
("read_only_space", 0x05541): (101, "TemplateObjectDescriptionMap"), ("read_only_space", 0x05589): (101, "TemplateObjectDescriptionMap"),
("read_only_space", 0x05591): (102, "Tuple2Map"), ("read_only_space", 0x055d9): (102, "Tuple2Map"),
("read_only_space", 0x055e1): (103, "Tuple3Map"), ("read_only_space", 0x05629): (103, "Tuple3Map"),
("read_only_space", 0x05631): (104, "WasmCapiFunctionDataMap"), ("read_only_space", 0x05679): (104, "WasmCapiFunctionDataMap"),
("read_only_space", 0x05681): (105, "WasmDebugInfoMap"), ("read_only_space", 0x056c9): (105, "WasmDebugInfoMap"),
("read_only_space", 0x056d1): (106, "WasmExceptionTagMap"), ("read_only_space", 0x05719): (106, "WasmExceptionTagMap"),
("read_only_space", 0x05721): (107, "WasmExportedFunctionDataMap"), ("read_only_space", 0x05769): (107, "WasmExportedFunctionDataMap"),
("read_only_space", 0x05771): (108, "WasmIndirectFunctionTableMap"), ("read_only_space", 0x057b9): (108, "WasmIndirectFunctionTableMap"),
("read_only_space", 0x057c1): (109, "WasmJSFunctionDataMap"), ("read_only_space", 0x05809): (109, "WasmJSFunctionDataMap"),
("read_only_space", 0x05811): (110, "CallableTaskMap"), ("read_only_space", 0x05859): (110, "CallableTaskMap"),
("read_only_space", 0x05861): (111, "CallbackTaskMap"), ("read_only_space", 0x058a9): (111, "CallbackTaskMap"),
("read_only_space", 0x058b1): (112, "PromiseFulfillReactionJobTaskMap"), ("read_only_space", 0x058f9): (112, "PromiseFulfillReactionJobTaskMap"),
("read_only_space", 0x05901): (113, "PromiseRejectReactionJobTaskMap"), ("read_only_space", 0x05949): (113, "PromiseRejectReactionJobTaskMap"),
("read_only_space", 0x05951): (114, "PromiseResolveThenableJobTaskMap"), ("read_only_space", 0x05999): (114, "PromiseResolveThenableJobTaskMap"),
("read_only_space", 0x059a1): (115, "InternalClassMap"), ("read_only_space", 0x059e9): (115, "InternalClassMap"),
("read_only_space", 0x059f1): (116, "SmiPairMap"), ("read_only_space", 0x05a39): (116, "SmiPairMap"),
("read_only_space", 0x05a41): (117, "SmiBoxMap"), ("read_only_space", 0x05a89): (117, "SmiBoxMap"),
("read_only_space", 0x05a91): (118, "SortStateMap"), ("read_only_space", 0x05ad9): (118, "SortStateMap"),
("read_only_space", 0x05ae1): (121, "AllocationSiteWithWeakNextMap"), ("read_only_space", 0x05b29): (121, "AllocationSiteWithWeakNextMap"),
("read_only_space", 0x05b31): (121, "AllocationSiteWithoutWeakNextMap"), ("read_only_space", 0x05b79): (121, "AllocationSiteWithoutWeakNextMap"),
("read_only_space", 0x05b81): (156, "LoadHandler1Map"), ("read_only_space", 0x05bc9): (156, "LoadHandler1Map"),
("read_only_space", 0x05bd1): (156, "LoadHandler2Map"), ("read_only_space", 0x05c19): (156, "LoadHandler2Map"),
("read_only_space", 0x05c21): (156, "LoadHandler3Map"), ("read_only_space", 0x05c69): (156, "LoadHandler3Map"),
("read_only_space", 0x05c71): (164, "StoreHandler0Map"), ("read_only_space", 0x05cb9): (164, "StoreHandler0Map"),
("read_only_space", 0x05cc1): (164, "StoreHandler1Map"), ("read_only_space", 0x05d09): (164, "StoreHandler1Map"),
("read_only_space", 0x05d11): (164, "StoreHandler2Map"), ("read_only_space", 0x05d59): (164, "StoreHandler2Map"),
("read_only_space", 0x05d61): (164, "StoreHandler3Map"), ("read_only_space", 0x05da9): (164, "StoreHandler3Map"),
("map_space", 0x00119): (1057, "ExternalMap"), ("map_space", 0x00119): (1057, "ExternalMap"),
("map_space", 0x00169): (1073, "JSMessageObjectMap"), ("map_space", 0x00169): (1073, "JSMessageObjectMap"),
} }
...@@ -398,29 +398,30 @@ KNOWN_OBJECTS = { ...@@ -398,29 +398,30 @@ KNOWN_OBJECTS = {
("old_space", 0x00429): "FunctionNameAccessor", ("old_space", 0x00429): "FunctionNameAccessor",
("old_space", 0x00499): "FunctionLengthAccessor", ("old_space", 0x00499): "FunctionLengthAccessor",
("old_space", 0x00509): "FunctionPrototypeAccessor", ("old_space", 0x00509): "FunctionPrototypeAccessor",
("old_space", 0x00579): "StringLengthAccessor", ("old_space", 0x00579): "RegExpResultIndicesAccessor",
("old_space", 0x005e9): "InvalidPrototypeValidityCell", ("old_space", 0x005e9): "StringLengthAccessor",
("old_space", 0x005f9): "EmptyScript", ("old_space", 0x00659): "InvalidPrototypeValidityCell",
("old_space", 0x00679): "ManyClosuresCell", ("old_space", 0x00669): "EmptyScript",
("old_space", 0x00691): "ArrayConstructorProtector", ("old_space", 0x006e9): "ManyClosuresCell",
("old_space", 0x006a1): "NoElementsProtector", ("old_space", 0x00701): "ArrayConstructorProtector",
("old_space", 0x006c9): "IsConcatSpreadableProtector", ("old_space", 0x00711): "NoElementsProtector",
("old_space", 0x006d9): "ArraySpeciesProtector", ("old_space", 0x00739): "IsConcatSpreadableProtector",
("old_space", 0x00701): "TypedArraySpeciesProtector", ("old_space", 0x00749): "ArraySpeciesProtector",
("old_space", 0x00729): "PromiseSpeciesProtector", ("old_space", 0x00771): "TypedArraySpeciesProtector",
("old_space", 0x00751): "StringLengthProtector", ("old_space", 0x00799): "PromiseSpeciesProtector",
("old_space", 0x00761): "ArrayIteratorProtector", ("old_space", 0x007c1): "StringLengthProtector",
("old_space", 0x00789): "ArrayBufferDetachingProtector", ("old_space", 0x007d1): "ArrayIteratorProtector",
("old_space", 0x007b1): "PromiseHookProtector", ("old_space", 0x007f9): "ArrayBufferDetachingProtector",
("old_space", 0x007d9): "PromiseResolveProtector", ("old_space", 0x00821): "PromiseHookProtector",
("old_space", 0x007e9): "MapIteratorProtector", ("old_space", 0x00849): "PromiseResolveProtector",
("old_space", 0x00811): "PromiseThenProtector", ("old_space", 0x00859): "MapIteratorProtector",
("old_space", 0x00839): "SetIteratorProtector", ("old_space", 0x00881): "PromiseThenProtector",
("old_space", 0x00861): "StringIteratorProtector", ("old_space", 0x008a9): "SetIteratorProtector",
("old_space", 0x00889): "SingleCharacterStringCache", ("old_space", 0x008d1): "StringIteratorProtector",
("old_space", 0x01099): "StringSplitCache", ("old_space", 0x008f9): "SingleCharacterStringCache",
("old_space", 0x018a9): "RegExpMultipleCache", ("old_space", 0x01109): "StringSplitCache",
("old_space", 0x020b9): "BuiltinsConstantsTable", ("old_space", 0x01919): "RegExpMultipleCache",
("old_space", 0x02129): "BuiltinsConstantsTable",
} }
# List of known V8 Frame Markers. # List of known V8 Frame Markers.
......
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