// Copyright 2016 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/regexp/regexp-utils.h" #include "src/execution/isolate.h" #include "src/execution/protectors-inl.h" #include "src/heap/factory.h" #include "src/objects/js-regexp-inl.h" #include "src/objects/objects-inl.h" #include "src/regexp/regexp.h" namespace v8 { namespace internal { Handle RegExpUtils::GenericCaptureGetter( Isolate* isolate, Handle match_info, int capture, bool* ok) { const int index = capture * 2; if (index >= match_info->NumberOfCaptureRegisters()) { if (ok != nullptr) *ok = false; return isolate->factory()->empty_string(); } const int match_start = match_info->Capture(index); const int match_end = match_info->Capture(index + 1); if (match_start == -1 || match_end == -1) { if (ok != nullptr) *ok = false; return isolate->factory()->empty_string(); } if (ok != nullptr) *ok = true; Handle last_subject(match_info->LastSubject(), isolate); return isolate->factory()->NewSubString(last_subject, match_start, match_end); } namespace { V8_INLINE bool HasInitialRegExpMap(Isolate* isolate, JSReceiver recv) { return recv.map() == isolate->regexp_function()->initial_map(); } } // namespace MaybeHandle RegExpUtils::SetLastIndex(Isolate* isolate, Handle recv, uint64_t value) { Handle value_as_object = isolate->factory()->NewNumberFromInt64(value); if (HasInitialRegExpMap(isolate, *recv)) { JSRegExp::cast(*recv).set_last_index(*value_as_object, SKIP_WRITE_BARRIER); return recv; } else { return Object::SetProperty( isolate, recv, isolate->factory()->lastIndex_string(), value_as_object, StoreOrigin::kMaybeKeyed, Just(kThrowOnError)); } } MaybeHandle RegExpUtils::GetLastIndex(Isolate* isolate, Handle recv) { if (HasInitialRegExpMap(isolate, *recv)) { return handle(JSRegExp::cast(*recv).last_index(), isolate); } else { return Object::GetProperty(isolate, recv, isolate->factory()->lastIndex_string()); } } // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) // Also takes an optional exec method in case our caller // has already fetched exec. MaybeHandle RegExpUtils::RegExpExec(Isolate* isolate, Handle regexp, Handle string, Handle exec) { if (exec->IsUndefined(isolate)) { ASSIGN_RETURN_ON_EXCEPTION( isolate, exec, Object::GetProperty(isolate, regexp, isolate->factory()->exec_string()), Object); } if (exec->IsCallable()) { const int argc = 1; ScopedVector> argv(argc); argv[0] = string; Handle result; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, Execution::Call(isolate, exec, regexp, argc, argv.begin()), Object); if (!result->IsJSReceiver() && !result->IsNull(isolate)) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kInvalidRegExpExecResult), Object); } return result; } if (!regexp->IsJSRegExp()) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, isolate->factory()->NewStringFromAsciiChecked( "RegExp.prototype.exec"), regexp), Object); } { Handle regexp_exec = isolate->regexp_exec_function(); const int argc = 1; ScopedVector> argv(argc); argv[0] = string; return Execution::Call(isolate, regexp_exec, regexp, argc, argv.begin()); } } Maybe RegExpUtils::IsRegExp(Isolate* isolate, Handle object) { if (!object->IsJSReceiver()) return Just(false); Handle receiver = Handle::cast(object); Handle match; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, match, JSObject::GetProperty(isolate, receiver, isolate->factory()->match_symbol()), Nothing()); if (!match->IsUndefined(isolate)) { const bool match_as_boolean = match->BooleanValue(isolate); if (match_as_boolean && !object->IsJSRegExp()) { isolate->CountUsage(v8::Isolate::kRegExpMatchIsTrueishOnNonJSRegExp); } else if (!match_as_boolean && object->IsJSRegExp()) { isolate->CountUsage(v8::Isolate::kRegExpMatchIsFalseishOnJSRegExp); } return Just(match_as_boolean); } return Just(object->IsJSRegExp()); } bool RegExpUtils::IsUnmodifiedRegExp(Isolate* isolate, Handle obj) { #ifdef V8_ENABLE_FORCE_SLOW_PATH if (isolate->force_slow_path()) return false; #endif if (!obj->IsJSReceiver()) return false; JSReceiver recv = JSReceiver::cast(*obj); if (!HasInitialRegExpMap(isolate, recv)) return false; // Check the receiver's prototype's map. Object proto = recv.map().prototype(); if (!proto.IsJSReceiver()) return false; Handle initial_proto_initial_map = isolate->regexp_prototype_map(); Map proto_map = JSReceiver::cast(proto).map(); if (proto_map != *initial_proto_initial_map) { return false; } // Check that the "exec" method is unmodified. // Check that the index refers to "exec" method (this has to be consistent // with the init order in the bootstrapper). DCHECK_EQ(*(isolate->factory()->exec_string()), proto_map.instance_descriptors().GetKey( JSRegExp::kExecFunctionDescriptorIndex)); if (proto_map.instance_descriptors() .GetDetails(JSRegExp::kExecFunctionDescriptorIndex) .constness() != PropertyConstness::kConst) { return false; } // Note: Unlike the more involved check in CSA (see BranchIfFastRegExp), this // does not go on to check the actual value of the exec property. This would // not be valid since this method is called from places that access the flags // property. Similar spots in CSA would use BranchIfFastRegExp_Strict in this // case. if (!Protectors::IsRegExpSpeciesLookupChainProtectorIntact( recv.GetCreationContext())) { return false; } // The smi check is required to omit ToLength(lastIndex) calls with possible // user-code execution on the fast path. Object last_index = JSRegExp::cast(recv).last_index(); return last_index.IsSmi() && Smi::ToInt(last_index) >= 0; } uint64_t RegExpUtils::AdvanceStringIndex(Handle string, uint64_t index, bool unicode) { DCHECK_LE(static_cast(index), kMaxSafeInteger); const uint64_t string_length = static_cast(string->length()); if (unicode && index < string_length) { const uint16_t first = string->Get(static_cast(index)); if (first >= 0xD800 && first <= 0xDBFF && index + 1 < string_length) { DCHECK_LT(index, std::numeric_limits::max()); const uint16_t second = string->Get(static_cast(index + 1)); if (second >= 0xDC00 && second <= 0xDFFF) { return index + 2; } } } return index + 1; } MaybeHandle RegExpUtils::SetAdvancedStringIndex( Isolate* isolate, Handle regexp, Handle string, bool unicode) { Handle last_index_obj; ASSIGN_RETURN_ON_EXCEPTION( isolate, last_index_obj, Object::GetProperty(isolate, regexp, isolate->factory()->lastIndex_string()), Object); ASSIGN_RETURN_ON_EXCEPTION(isolate, last_index_obj, Object::ToLength(isolate, last_index_obj), Object); const uint64_t last_index = PositiveNumberToUint64(*last_index_obj); const uint64_t new_last_index = AdvanceStringIndex(string, last_index, unicode); return SetLastIndex(isolate, regexp, new_last_index); } } // namespace internal } // namespace v8