blob: dd5104463e6f545887c3219e930823c2e9d4ede9 [file]
// 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 <optional>
#include "src/base/memory.h"
#include "src/base/strings.h"
#include "src/common/globals.h"
#include "src/objects/code.h"
#include "src/objects/dictionary-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/regexp/regexp.h"
namespace v8::internal {
DirectHandle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
Isolate* isolate, DirectHandle<RegExpMatchInfo> match_info,
DirectHandle<RegExpData> re_data) {
DirectHandle<JSRegExpResultIndices> indices(
Cast<JSRegExpResultIndices>(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::zero());
// Build indices array from RegExpMatchInfo.
int num_indices = match_info->number_of_capture_registers();
int num_results = num_indices >> 1;
DirectHandle<FixedArray> indices_array =
isolate->factory()->NewFixedArray(num_results);
JSArray::SetContent(isolate, indices, indices_array);
for (int i = 0; i < num_results; i++) {
const int start_offset =
match_info->capture(RegExpMatchInfo::capture_start_index(i));
const int end_offset =
match_info->capture(RegExpMatchInfo::capture_end_index(i));
// 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 {
DirectHandle<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));
DirectHandle<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(), InternalIndex(kGroupsDescriptorIndex));
if (re_data->type_tag() != RegExpData::Type::IRREGEXP ||
!TrustedCast<IrRegExpData>(re_data)->has_capture_name_map()) {
indices->FastPropertyAtPut(groups_index,
ReadOnlyRoots(isolate).undefined_value());
return indices;
}
// Create a groups property which returns a dictionary of named captures to
// their corresponding capture indices.
auto names = direct_handle(
TrustedCast<IrRegExpData>(re_data)->capture_name_map(), isolate);
const int num_names = static_cast<int>(names->ulength().value() >> 1);
DirectHandle<HeapObject> group_names;
if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
group_names = isolate->factory()->NewSwissNameDictionary(num_names);
} else {
group_names = isolate->factory()->NewNameDictionary(num_names);
}
DirectHandle<PropertyDictionary> group_names_dict =
Cast<PropertyDictionary>(group_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;
DirectHandle<String> name(Cast<String>(names->get(name_offset)), isolate);
Tagged<Smi> smi_index = Cast<Smi>(names->get(index_offset));
DirectHandle<Object> capture_indices(indices_array->get(smi_index.value()),
isolate);
if (!IsUndefined(*capture_indices)) {
capture_indices = Cast<JSArray>(capture_indices);
}
InternalIndex group_entry = group_names_dict->FindEntry(isolate, name);
// Duplicate group entries are possible if the capture groups are in
// different alternatives, i.e. only one of them can actually match.
// Therefore when we find a duplicate entry, either the current entry is
// undefined (didn't match anything) or the indices for the current capture
// are undefined. In the latter case we don't do anything, in the former
// case we update the entry.
if (group_entry.is_found()) {
if (!IsUndefined(*capture_indices)) {
DCHECK(IsUndefined(group_names_dict->ValueAt(group_entry)));
group_names_dict->ValueAtPut(group_entry, *capture_indices);
}
} else {
group_names_dict =
PropertyDictionary::Add(isolate, group_names_dict, name,
capture_indices, PropertyDetails::Empty())
.ToHandleChecked();
}
}
// Convert group_names to a JSObject and store at the groups property of the
// result indices.
DirectHandle<FixedArrayBase> elements =
isolate->factory()->empty_fixed_array();
DirectHandle<Null> null = isolate->factory()->null_value();
DirectHandle<JSObject> js_group_names =
isolate->factory()->NewSlowJSObjectWithPropertiesAndElements(
null, group_names, elements);
indices->FastPropertyAtPut(groups_index, *js_group_names);
return indices;
}
// static
std::optional<JSRegExp::Flags> JSRegExp::FlagsFromString(
Isolate* isolate, DirectHandle<String> flags) {
const int length = flags->length();
// A longer flags string cannot be valid.
if (length > JSRegExp::kFlagCount) return {};
regexp::Flags value;
FlatStringReader reader(isolate, String::Flatten(isolate, flags));
for (int i = 0; i < length; i++) {
std::optional<regexp::Flag> flag = JSRegExp::FlagFromChar(reader.Get(i));
if (!flag.has_value()) return {};
if (value & flag.value()) return {}; // Duplicate.
value |= flag.value();
}
return JSRegExp::AsJSRegExpFlags(value);
}
// static
DirectHandle<String> JSRegExp::StringFromFlags(Isolate* isolate,
JSRegExp::Flags flags) {
FlagsBuffer buffer;
return isolate->factory()->NewStringFromAsciiChecked(
FlagsToString(flags, &buffer));
}
// static
MaybeDirectHandle<JSRegExp> JSRegExp::New(Isolate* isolate,
DirectHandle<String> original_source,
Flags flags,
uint32_t backtrack_limit) {
DirectHandle<JSFunction> constructor = isolate->regexp_function();
DirectHandle<JSRegExp> regexp =
Cast<JSRegExp>(isolate->factory()->NewJSObject(constructor));
// Clear the data field, as a GC can be triggered before the field is set
// during compilation.
regexp->clear_data();
return JSRegExp::Initialize(isolate, regexp, original_source, flags,
backtrack_limit);
}
// static
MaybeDirectHandle<JSRegExp> JSRegExp::Initialize(
Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<String> original_source, DirectHandle<String> flags_string) {
std::optional<Flags> flags = JSRegExp::FlagsFromString(isolate, flags_string);
if (!flags.has_value() ||
!RegExp::VerifyFlags(JSRegExp::AsRegExpFlags(flags.value()))) {
THROW_NEW_ERROR(
isolate,
NewSyntaxError(MessageTemplate::kInvalidRegExpFlags, flags_string));
}
return Initialize(isolate, regexp, original_source, flags.value());
}
// static
MaybeDirectHandle<JSRegExp> JSRegExp::Initialize(
Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<String> original_source, Flags flags,
uint32_t backtrack_limit) {
Factory* factory = isolate->factory();
// If source is the empty string we set it to "(?:)" instead as
// suggested by ECMA-262, 5th, section 15.10.4.1.
if (original_source->length() == 0) {
original_source = factory->query_colon_string();
}
original_source = String::Flatten(isolate, original_source);
RETURN_ON_EXCEPTION(isolate, RegExp::Compile(isolate, regexp, original_source,
JSRegExp::AsRegExpFlags(flags),
backtrack_limit));
regexp->set_flags(Smi::FromInt(flags));
Tagged<Map> map = regexp->map();
Tagged<Object> constructor = map->GetConstructor();
if (IsJSFunction(constructor) &&
Cast<JSFunction>(constructor)->initial_map() == map) {
// If we still have the original map, set in-object properties directly.
regexp->InObjectPropertyPutAtOffset(JSRegExp::kLastIndexOffset,
Smi::FromInt(kInitialLastIndexValue),
SKIP_WRITE_BARRIER);
} else {
// Map has changed, so use generic, but slower, method.
RETURN_ON_EXCEPTION(
isolate,
Object::SetProperty(
isolate, regexp, factory->lastIndex_string(),
DirectHandle<Smi>(Smi::FromInt(kInitialLastIndexValue), isolate)));
}
return regexp;
}
bool RegExpData::HasCompiledCode() const {
if (type_tag() != Type::IRREGEXP) return false;
Tagged<IrRegExpData> re_data = TrustedCast<IrRegExpData>(this);
return re_data->has_latin1_code() || re_data->has_uc16_code();
}
bool RegExpData::QuickCheckRejects(base::Vector<const uint8_t> subject,
int index) const {
DCHECK_LE(0, index);
DCHECK_LE(index, subject.length());
if ((internal_flags() & kHasQuickCheck) == 0) return false;
const int remaining = subject.length() - index;
// The mask compares four characters at once, so it needs four to be left.
// Near the end of the subject we skip it and fall through to the bitset,
// which only ever looks at one.
if (quick_check_mask() != 0 &&
remaining >= static_cast<int>(sizeof(uint32_t))) {
uint32_t chars = base::ReadUnalignedValue<uint32_t>(
reinterpret_cast<Address>(subject.begin() + index));
if ((chars & quick_check_mask()) != quick_check_value()) return true;
}
// A set bit means "no match can start with this character", so an all-zero
// bitset rejects nothing and costs us only the test. There is nothing to
// look at only once the position is past the last character.
if (remaining == 0) return false;
const auto [word, bit] = QuickCheckBitsetBit(subject[index]);
return (quick_check_reject_bitset_[word] & bit) != 0;
}
// Only irregexps are subject to tier-up.
bool IrRegExpData::CanTierUp() {
return v8_flags.regexp_tier_up && type_tag() == Type::IRREGEXP;
}
// An irregexp is considered to be marked for tier up if the tier-up ticks
// value reaches zero.
bool IrRegExpData::MarkedForTierUp() {
if (!CanTierUp()) {
return false;
}
return ticks_until_tier_up() == 0;
}
void IrRegExpData::ResetLastTierUpTick() {
DCHECK(v8_flags.regexp_tier_up);
int tier_up_ticks = ticks_until_tier_up();
set_ticks_until_tier_up(tier_up_ticks + 1);
}
void IrRegExpData::TierUpTick() {
int tier_up_ticks = ticks_until_tier_up();
if (tier_up_ticks == 0) {
return;
}
set_ticks_until_tier_up(tier_up_ticks - 1);
}
void IrRegExpData::MarkTierUpForNextExec() {
DCHECK(v8_flags.regexp_tier_up);
set_ticks_until_tier_up(0);
}
bool IrRegExpData::ShouldProduceBytecode() {
return v8_flags.regexp_interpret_all ||
(v8_flags.regexp_tier_up && !MarkedForTierUp());
}
void IrRegExpData::DiscardCompiledCodeForSerialization() {
DCHECK(HasCompiledCode());
clear_latin1_code();
clear_uc16_code();
clear_latin1_bytecode();
clear_uc16_bytecode();
}
void IrRegExpData::SetBytecodeForExperimental(
Isolate* isolate, Tagged<TrustedByteArray> bytecode) {
set_latin1_bytecode(bytecode);
set_uc16_bytecode(bytecode);
Tagged<Code> trampoline =
*BUILTIN_CODE(isolate, RegExpExperimentalTrampoline);
set_latin1_code(trampoline);
set_uc16_code(trampoline);
}
} // namespace v8::internal