// 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" #include "src/regexp/regexp.h" namespace v8 { namespace internal { MaybeHandle JSRegExpResult::GetAndCacheIndices( Isolate* isolate, Handle 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 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 regexp(JSRegExp::cast(*indices_or_regexp), isolate); Handle input_object( GetProperty(isolate, regexp_result, isolate->factory()->regexp_result_regexp_input_symbol()) .ToHandleChecked()); Handle subject(String::cast(*input_object), isolate); Handle last_index_object( GetProperty( isolate, regexp_result, isolate->factory()->regexp_result_regexp_last_index_symbol()) .ToHandleChecked()); int capture_count = regexp->CaptureCount(); Handle match_info = RegExpMatchInfo::New(isolate, capture_count); int last_index = Smi::ToInt(*last_index_object); Handle result; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, RegExp::Exec(isolate, regexp, subject, last_index, match_info), JSArray); DCHECK_EQ(*result, *match_info); Handle 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::cast(indices_or_regexp); } Handle JSRegExpResultIndices::BuildIndices( Isolate* isolate, Handle match_info, Handle maybe_names) { Handle indices(Handle::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::zero()); // Build indices array from RegExpMatchInfo. int num_indices = match_info->NumberOfCaptureRegisters(); int num_results = num_indices >> 1; Handle 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 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 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 (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 names(Handle::cast(maybe_names)); int num_names = names->length() >> 1; Handle 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 name(String::cast(names->get(name_offset)), isolate); Handle smi_index(Smi::cast(names->get(index_offset)), isolate); Handle capture_indices(indices_array->get(smi_index->value()), isolate); if (!capture_indices->IsUndefined(isolate)) { capture_indices = Handle::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 elements = isolate->factory()->empty_fixed_array(); Handle null = Handle::cast(isolate->factory()->null_value()); Handle js_group_names = isolate->factory()->NewSlowJSObjectWithPropertiesAndElements( null, group_names, elements); indices->RawFastPropertyAtPut(groups_index, *js_group_names); return indices; } uint32_t JSRegExp::BacktrackLimit() const { CHECK_EQ(TypeTag(), IRREGEXP); return static_cast(Smi::ToInt(DataAt(kIrregexpBacktrackLimit))); } } // namespace internal } // namespace v8