blob: 07d1b5d8f38ec7edd3dd1acc0b7043ca2666da7b [file] [log] [blame]
// 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<String> RegExpUtils::GenericCaptureGetter(
Isolate* isolate, Handle<RegExpMatchInfo> 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<String> 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<Object> RegExpUtils::SetLastIndex(Isolate* isolate,
Handle<JSReceiver> recv,
uint64_t value) {
Handle<Object> 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<Object> RegExpUtils::GetLastIndex(Isolate* isolate,
Handle<JSReceiver> 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<Object> RegExpUtils::RegExpExec(Isolate* isolate,
Handle<JSReceiver> regexp,
Handle<String> string,
Handle<Object> 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<Handle<Object>> argv(argc);
argv[0] = string;
Handle<Object> 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<JSFunction> regexp_exec = isolate->regexp_exec_function();
const int argc = 1;
ScopedVector<Handle<Object>> argv(argc);
argv[0] = string;
return Execution::Call(isolate, regexp_exec, regexp, argc, argv.begin());
}
}
Maybe<bool> RegExpUtils::IsRegExp(Isolate* isolate, Handle<Object> object) {
if (!object->IsJSReceiver()) return Just(false);
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(object);
Handle<Object> match;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, match,
JSObject::GetProperty(isolate, receiver,
isolate->factory()->match_symbol()),
Nothing<bool>());
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<Object> 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<Map> 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).
InternalIndex kExecIndex(JSRegExp::kExecFunctionDescriptorIndex);
DCHECK_EQ(*(isolate->factory()->exec_string()),
proto_map.instance_descriptors(kRelaxedLoad).GetKey(kExecIndex));
if (proto_map.instance_descriptors(kRelaxedLoad)
.GetDetails(kExecIndex)
.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::IsRegExpSpeciesLookupChainIntact(isolate)) 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> string, uint64_t index,
bool unicode) {
DCHECK_LE(static_cast<double>(index), kMaxSafeInteger);
const uint64_t string_length = static_cast<uint64_t>(string->length());
if (unicode && index < string_length) {
const uint16_t first = string->Get(static_cast<uint32_t>(index));
if (first >= 0xD800 && first <= 0xDBFF && index + 1 < string_length) {
DCHECK_LT(index, std::numeric_limits<uint64_t>::max());
const uint16_t second = string->Get(static_cast<uint32_t>(index + 1));
if (second >= 0xDC00 && second <= 0xDFFF) {
return index + 2;
}
}
}
return index + 1;
}
MaybeHandle<Object> RegExpUtils::SetAdvancedStringIndex(
Isolate* isolate, Handle<JSReceiver> regexp, Handle<String> string,
bool unicode) {
Handle<Object> 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