#include "src/objects/js-array-buffer.h"
#include "src/execution/protectors-inl.h"
#include "src/logging/counters.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/property-descriptor.h"
namespace v8 {
namespace internal {
namespace {
bool CanonicalNumericIndexString(Isolate* isolate,
const PropertyKey& lookup_key,
bool* is_minus_zero) {
DCHECK(lookup_key.is_element() || IsString(*lookup_key.name()));
*is_minus_zero = false;
if (lookup_key.is_element()) return true;
DirectHandle<String> key = Cast<String>(lookup_key.name());
DirectHandle<Object> result = String::ToNumber(isolate, key);
if (IsMinusZero(*result)) {
*is_minus_zero = true;
} else {
DirectHandle<String> str =
Object::ToString(isolate, result).ToHandleChecked();
if (!Object::SameValue(*str, *key)) return false;
}
return true;
}
}
void JSArrayBuffer::Setup(SharedFlag shared, ResizableFlag resizable,
std::shared_ptr<BackingStore> backing_store,
Isolate* isolate) {
auto finish_setup = [shared, isolate]() {
if (shared == SharedFlag::kShared) {
isolate->CountUsage(
v8::Isolate::UseCounterFeature::kSharedArrayBufferConstructed);
}
};
clear_padding();
init_extension();
set_detach_key(ReadOnlyRoots(isolate).undefined_value());
set_bit_field(0);
set_is_shared(shared == SharedFlag::kShared);
set_is_resizable_by_js(resizable == ResizableFlag::kResizable);
set_is_detachable(shared != SharedFlag::kShared);
SetupLazilyInitializedCppHeapPointerField(
JSAPIObjectWithEmbedderSlots::kCppHeapWrappableOffset);
for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) {
SetEmbedderField(i, Smi::zero());
}
if (!backing_store) {
set_backing_store(isolate, EmptyBackingStoreBuffer());
set_byte_length(0);
set_max_byte_length(0);
finish_setup();
return;
}
DCHECK_EQ(is_shared(), backing_store->is_shared());
DCHECK((is_resizable_by_js() == backing_store->is_resizable_by_js()) ||
(backing_store->is_wasm_memory() && is_shared()));
DCHECK_IMPLIES(
!backing_store->is_wasm_memory() && !backing_store->is_resizable_by_js(),
backing_store->byte_length() == backing_store->max_byte_length());
void* backing_store_buffer = backing_store->buffer_start();
DCHECK_IMPLIES(backing_store->is_wasm_memory(), !backing_store->IsEmpty());
DCHECK_IMPLIES(backing_store_buffer == EmptyBackingStoreBuffer(),
backing_store->IsEmpty());
DCHECK_IMPLIES(V8_ENABLE_SANDBOX_BOOL, backing_store_buffer != nullptr);
set_backing_store(isolate, backing_store_buffer);
auto byte_len =
(is_shared() && is_resizable_by_js()) ? 0 : backing_store->byte_length();
CHECK_LE(backing_store->byte_length(), kMaxByteLength);
set_byte_length(byte_len);
auto max_byte_len = is_resizable_by_js() ? backing_store->max_byte_length()
: backing_store->byte_length();
set_max_byte_length(max_byte_len);
if (backing_store->is_wasm_memory()) {
set_is_detachable(false);
}
CreateExtension(isolate, std::move(backing_store));
finish_setup();
}
Maybe<bool> JSArrayBuffer::Detach(DirectHandle<JSArrayBuffer> buffer,
bool force_for_wasm_memory,
DirectHandle<Object> maybe_key) {
Isolate* const isolate = Isolate::Current();
DirectHandle<Object> detach_key(buffer->detach_key(), isolate);
bool key_mismatch = false;
if (!IsUndefined(*detach_key, isolate)) {
key_mismatch =
maybe_key.is_null() || !Object::StrictEquals(*maybe_key, *detach_key);
} else {
key_mismatch =
!maybe_key.is_null() && !Object::StrictEquals(*maybe_key, *detach_key);
}
if (key_mismatch) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kArrayBufferDetachKeyDoesntMatch));
}
if (buffer->was_detached()) return Just(true);
if (force_for_wasm_memory) {
} else if (!buffer->is_detachable()) {
return Just(true);
}
buffer->DetachInternal(force_for_wasm_memory, isolate);
return Just(true);
}
void JSArrayBuffer::DetachInternal(bool force_for_wasm_memory,
Isolate* isolate) {
ArrayBufferExtension* extension = this->extension();
if (extension) {
DisallowGarbageCollection disallow_gc;
isolate->heap()->DetachArrayBufferExtension(extension);
std::shared_ptr<BackingStore> backing_store = RemoveExtension();
CHECK_IMPLIES(force_for_wasm_memory, backing_store->is_wasm_memory());
}
if (Protectors::IsArrayBufferDetachingIntact(isolate)) {
Protectors::InvalidateArrayBufferDetaching(isolate);
}
DCHECK(!is_shared());
set_backing_store(isolate, EmptyBackingStoreBuffer());
set_byte_length(0);
set_was_detached(true);
}
size_t JSArrayBuffer::GsabByteLength(Isolate* isolate,
Address raw_array_buffer) {
DisallowGarbageCollection no_gc;
DisallowJavascriptExecution no_js(isolate);
Tagged<JSArrayBuffer> buffer =
Cast<JSArrayBuffer>(Tagged<Object>(raw_array_buffer));
CHECK(buffer->is_resizable_by_js());
CHECK(buffer->is_shared());
return buffer->GetBackingStore()->byte_length(std::memory_order_seq_cst);
}
Maybe<bool> JSArrayBuffer::GetResizableBackingStorePageConfiguration(
Isolate* isolate, size_t byte_length, size_t max_byte_length,
ShouldThrow should_throw, size_t* page_size, size_t* initial_pages,
size_t* max_pages) {
DCHECK_NOT_NULL(page_size);
DCHECK_NOT_NULL(initial_pages);
DCHECK_NOT_NULL(max_pages);
*page_size = AllocatePageSize();
if (!RoundUpToPageSize(byte_length, *page_size, JSArrayBuffer::kMaxByteLength,
initial_pages)) {
if (should_throw == kDontThrow) return Nothing<bool>();
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kInvalidArrayBufferLength));
}
if (!RoundUpToPageSize(max_byte_length, *page_size,
JSArrayBuffer::kMaxByteLength, max_pages)) {
if (should_throw == kDontThrow) return Nothing<bool>();
THROW_NEW_ERROR(
isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferMaxLength));
}
return Just(true);
}
std::optional<MessageTemplate>
JSArrayBuffer::GetResizableBackingStorePageConfigurationImpl(
Isolate* isolate, size_t byte_length, size_t max_byte_length,
size_t* page_size, size_t* initial_pages, size_t* max_pages) {
DCHECK_NOT_NULL(page_size);
DCHECK_NOT_NULL(initial_pages);
DCHECK_NOT_NULL(max_pages);
*page_size = AllocatePageSize();
if (!RoundUpToPageSize(byte_length, *page_size, JSArrayBuffer::kMaxByteLength,
initial_pages)) {
return MessageTemplate::kInvalidArrayBufferLength;
}
if (!RoundUpToPageSize(max_byte_length, *page_size,
JSArrayBuffer::kMaxByteLength, max_pages)) {
return MessageTemplate::kInvalidArrayBufferMaxLength;
}
return {};
}
ArrayBufferExtension* JSArrayBuffer::CreateExtension(
Isolate* isolate, std::shared_ptr<BackingStore> backing_store) {
const auto age =
HeapLayout::InYoungGeneration(UncheckedCast<JSArrayBuffer>(*this))
? ArrayBufferExtension::Age::kYoung
: ArrayBufferExtension::Age::kOld;
ArrayBufferExtension* extension =
new ArrayBufferExtension(std::move(backing_store), age);
set_extension(extension);
isolate->heap()->AppendArrayBufferExtension(extension);
return extension;
}
std::shared_ptr<BackingStore> JSArrayBuffer::RemoveExtension() {
ArrayBufferExtension* extension = this->extension();
DCHECK_NOT_NULL(extension);
auto result = extension->RemoveBackingStore();
set_extension(nullptr);
return result;
}
void JSArrayBuffer::MarkExtension() {
ArrayBufferExtension* extension = this->extension();
if (extension) {
extension->Mark();
}
}
void JSArrayBuffer::YoungMarkExtension() {
ArrayBufferExtension* extension = this->extension();
if (extension) {
DCHECK_EQ(ArrayBufferExtension::Age::kYoung, extension->age());
extension->YoungMark();
}
}
void JSArrayBuffer::YoungMarkExtensionPromoted() {
ArrayBufferExtension* extension = this->extension();
if (extension) {
extension->YoungMarkPromoted();
}
}
Handle<JSArrayBuffer> JSTypedArray::GetBuffer(Isolate* isolate) {
DirectHandle<JSTypedArray> self(*this, isolate);
DCHECK(IsTypedArrayOrRabGsabTypedArrayElementsKind(self->GetElementsKind()));
Handle<JSArrayBuffer> array_buffer(Cast<JSArrayBuffer>(self->buffer()),
isolate);
if (!is_on_heap()) {
return array_buffer;
}
DCHECK(!array_buffer->is_resizable_by_js());
DCHECK(array_buffer->IsEmpty());
size_t byte_length = self->byte_length();
auto backing_store =
BackingStore::Allocate(isolate, byte_length, SharedFlag::kNotShared,
InitializedFlag::kUninitialized);
if (!backing_store) {
isolate->heap()->FatalProcessOutOfMemory("JSTypedArray::GetBuffer");
}
if (byte_length > 0) {
memcpy(backing_store->buffer_start(), self->DataPtr(), byte_length);
}
array_buffer->Setup(SharedFlag::kNotShared, ResizableFlag::kNotResizable,
std::move(backing_store), isolate);
self->set_elements(ReadOnlyRoots(isolate).empty_byte_array());
self->SetOffHeapDataPtr(isolate, array_buffer->backing_store(), 0);
DCHECK(!self->is_on_heap());
return array_buffer;
}
Maybe<bool> JSTypedArray::DefineOwnProperty(Isolate* isolate,
DirectHandle<JSTypedArray> o,
DirectHandle<Object> key,
PropertyDescriptor* desc,
Maybe<ShouldThrow> should_throw) {
DCHECK(IsName(*key) || IsNumber(*key));
PropertyKey lookup_key(isolate, key);
if (lookup_key.is_element() || IsSmi(*key) || IsString(*key)) {
bool is_minus_zero = false;
if (IsSmi(*key) ||
CanonicalNumericIndexString(isolate, lookup_key, &is_minus_zero)) {
size_t index = lookup_key.index();
bool out_of_bounds = false;
size_t length = o->GetLengthOrOutOfBounds(out_of_bounds);
if (o->WasDetached() || out_of_bounds || index >= length) {
RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kInvalidTypedArrayIndex));
}
if (!lookup_key.is_element() || is_minus_zero) {
RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kInvalidTypedArrayIndex));
}
if (PropertyDescriptor::IsAccessorDescriptor(desc)) {
RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed, key));
}
if ((desc->has_configurable() && !desc->configurable()) ||
(desc->has_enumerable() && !desc->enumerable()) ||
(desc->has_writable() && !desc->writable())) {
RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed, key));
}
if (desc->has_value()) {
if (!desc->has_configurable()) desc->set_configurable(true);
if (!desc->has_enumerable()) desc->set_enumerable(true);
if (!desc->has_writable()) desc->set_writable(true);
DirectHandle<Object> value = desc->value();
LookupIterator it(isolate, o, index, LookupIterator::OWN);
RETURN_ON_EXCEPTION_VALUE(
isolate,
DefineOwnPropertyIgnoreAttributes(&it, value, desc->ToAttributes()),
Nothing<bool>());
}
return Just(true);
}
}
return OrdinaryDefineOwnProperty(isolate, o, lookup_key, desc, should_throw);
}
ExternalArrayType JSTypedArray::type() const {
return TypeAndElementSizeFor(map()->elements_kind()).first;
}
size_t JSTypedArray::element_size() const {
return TypeAndElementSizeFor(map()->elements_kind()).second;
}
size_t JSTypedArray::LengthTrackingGsabBackedTypedArrayLength(
Isolate* isolate, Address raw_array) {
DisallowGarbageCollection no_gc;
DisallowJavascriptExecution no_js(isolate);
Tagged<JSTypedArray> array = Cast<JSTypedArray>(Tagged<Object>(raw_array));
CHECK(array->is_length_tracking());
Tagged<JSArrayBuffer> buffer = array->buffer();
CHECK(buffer->is_resizable_by_js());
CHECK(buffer->is_shared());
size_t backing_byte_length =
buffer->GetBackingStore()->byte_length(std::memory_order_seq_cst);
CHECK_GE(backing_byte_length, array->byte_offset());
auto element_byte_size = ElementsKindToByteSize(array->GetElementsKind());
return (backing_byte_length - array->byte_offset()) / element_byte_size;
}
size_t JSTypedArray::GetVariableByteLengthOrOutOfBounds(
bool& out_of_bounds) const {
DCHECK(!WasDetached());
size_t own_byte_offset = byte_offset();
if (is_length_tracking()) {
size_t own_element_size = element_size();
if (is_backed_by_rab()) {
size_t buffer_byte_length = buffer()->byte_length();
if (own_byte_offset > buffer_byte_length) {
out_of_bounds = true;
return 0;
}
return RoundDown(buffer_byte_length - own_byte_offset, own_element_size);
}
size_t buffer_byte_length =
buffer()->GetBackingStore()->byte_length(std::memory_order_seq_cst);
SBXCHECK(own_byte_offset <= buffer_byte_length);
return RoundDown(buffer_byte_length - own_byte_offset, own_element_size);
}
DCHECK(is_backed_by_rab());
size_t own_byte_length = byte_length();
size_t buffer_byte_length = buffer()->byte_length();
if (own_byte_length > buffer_byte_length ||
own_byte_offset > buffer_byte_length - own_byte_length) {
out_of_bounds = true;
return 0;
}
return own_byte_length;
}
size_t JSTypedArray::GetVariableLengthOrOutOfBounds(bool& out_of_bounds) const {
return GetVariableByteLengthOrOutOfBounds(out_of_bounds) / element_size();
}
}
}