| // Copyright 2018 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-array-buffer.h" | |
| #include "src/objects/js-array-buffer-inl.h" | |
| #include "src/property-descriptor.h" | |
| namespace v8 { | |
| namespace internal { | |
| namespace { | |
| bool CanonicalNumericIndexString(Isolate* isolate, Handle<Object> s, | |
| Handle<Object>* index) { | |
| DCHECK(s->IsString() || s->IsSmi()); | |
| Handle<Object> result; | |
| if (s->IsSmi()) { | |
| result = s; | |
| } else { | |
| result = String::ToNumber(isolate, Handle<String>::cast(s)); | |
| if (!result->IsMinusZero()) { | |
| Handle<String> str = Object::ToString(isolate, result).ToHandleChecked(); | |
| // Avoid treating strings like "2E1" and "20" as the same key. | |
| if (!str->SameValue(*s)) return false; | |
| } | |
| } | |
| *index = result; | |
| return true; | |
| } | |
| inline int ConvertToMb(size_t size) { | |
| return static_cast<int>(size / static_cast<size_t>(MB)); | |
| } | |
| } // anonymous namespace | |
| void JSArrayBuffer::Neuter() { | |
| CHECK(is_neuterable()); | |
| CHECK(!was_neutered()); | |
| CHECK(is_external()); | |
| set_backing_store(nullptr); | |
| set_byte_length(Smi::kZero); | |
| set_was_neutered(true); | |
| set_is_neuterable(false); | |
| // Invalidate the neutering protector. | |
| Isolate* const isolate = GetIsolate(); | |
| if (isolate->IsArrayBufferNeuteringIntact()) { | |
| isolate->InvalidateArrayBufferNeuteringProtector(); | |
| } | |
| } | |
| void JSArrayBuffer::StopTrackingWasmMemory(Isolate* isolate) { | |
| DCHECK(is_wasm_memory()); | |
| isolate->wasm_engine()->memory_tracker()->ReleaseAllocation(isolate, | |
| backing_store()); | |
| set_is_wasm_memory(false); | |
| } | |
| void JSArrayBuffer::FreeBackingStoreFromMainThread() { | |
| if (allocation_base() == nullptr) { | |
| return; | |
| } | |
| FreeBackingStore(GetIsolate(), {allocation_base(), allocation_length(), | |
| backing_store(), is_wasm_memory()}); | |
| // Zero out the backing store and allocation base to avoid dangling | |
| // pointers. | |
| set_backing_store(nullptr); | |
| } | |
| // static | |
| void JSArrayBuffer::FreeBackingStore(Isolate* isolate, Allocation allocation) { | |
| if (allocation.is_wasm_memory) { | |
| wasm::WasmMemoryTracker* memory_tracker = | |
| isolate->wasm_engine()->memory_tracker(); | |
| if (!memory_tracker->FreeMemoryIfIsWasmMemory(isolate, | |
| allocation.backing_store)) { | |
| CHECK(FreePages(allocation.allocation_base, allocation.length)); | |
| } | |
| } else { | |
| isolate->array_buffer_allocator()->Free(allocation.allocation_base, | |
| allocation.length); | |
| } | |
| } | |
| void JSArrayBuffer::set_is_wasm_memory(bool is_wasm_memory) { | |
| set_bit_field(IsWasmMemory::update(bit_field(), is_wasm_memory)); | |
| } | |
| void JSArrayBuffer::Setup(Handle<JSArrayBuffer> array_buffer, Isolate* isolate, | |
| bool is_external, void* data, size_t byte_length, | |
| SharedFlag shared, bool is_wasm_memory) { | |
| DCHECK_EQ(array_buffer->GetEmbedderFieldCount(), | |
| v8::ArrayBuffer::kEmbedderFieldCount); | |
| for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) { | |
| array_buffer->SetEmbedderField(i, Smi::kZero); | |
| } | |
| array_buffer->set_bit_field(0); | |
| array_buffer->set_is_external(is_external); | |
| array_buffer->set_is_neuterable(shared == SharedFlag::kNotShared); | |
| array_buffer->set_is_shared(shared == SharedFlag::kShared); | |
| array_buffer->set_is_wasm_memory(is_wasm_memory); | |
| Handle<Object> heap_byte_length = | |
| isolate->factory()->NewNumberFromSize(byte_length); | |
| CHECK(heap_byte_length->IsSmi() || heap_byte_length->IsHeapNumber()); | |
| array_buffer->set_byte_length(*heap_byte_length); | |
| // Initialize backing store at last to avoid handling of |JSArrayBuffers| that | |
| // are currently being constructed in the |ArrayBufferTracker|. The | |
| // registration method below handles the case of registering a buffer that has | |
| // already been promoted. | |
| array_buffer->set_backing_store(data); | |
| if (data && !is_external) { | |
| isolate->heap()->RegisterNewArrayBuffer(*array_buffer); | |
| } | |
| } | |
| bool JSArrayBuffer::SetupAllocatingData(Handle<JSArrayBuffer> array_buffer, | |
| Isolate* isolate, | |
| size_t allocated_length, | |
| bool initialize, SharedFlag shared) { | |
| void* data; | |
| CHECK_NOT_NULL(isolate->array_buffer_allocator()); | |
| if (allocated_length != 0) { | |
| if (allocated_length >= MB) | |
| isolate->counters()->array_buffer_big_allocations()->AddSample( | |
| ConvertToMb(allocated_length)); | |
| if (shared == SharedFlag::kShared) | |
| isolate->counters()->shared_array_allocations()->AddSample( | |
| ConvertToMb(allocated_length)); | |
| if (initialize) { | |
| data = isolate->array_buffer_allocator()->Allocate(allocated_length); | |
| } else { | |
| data = isolate->array_buffer_allocator()->AllocateUninitialized( | |
| allocated_length); | |
| } | |
| if (data == nullptr) { | |
| isolate->counters()->array_buffer_new_size_failures()->AddSample( | |
| ConvertToMb(allocated_length)); | |
| return false; | |
| } | |
| } else { | |
| data = nullptr; | |
| } | |
| const bool is_external = false; | |
| JSArrayBuffer::Setup(array_buffer, isolate, is_external, data, | |
| allocated_length, shared); | |
| return true; | |
| } | |
| Handle<JSArrayBuffer> JSTypedArray::MaterializeArrayBuffer( | |
| Handle<JSTypedArray> typed_array) { | |
| DCHECK(typed_array->is_on_heap()); | |
| Isolate* isolate = typed_array->GetIsolate(); | |
| DCHECK(IsFixedTypedArrayElementsKind(typed_array->GetElementsKind())); | |
| Handle<FixedTypedArrayBase> fixed_typed_array( | |
| FixedTypedArrayBase::cast(typed_array->elements()), isolate); | |
| Handle<JSArrayBuffer> buffer(JSArrayBuffer::cast(typed_array->buffer()), | |
| isolate); | |
| // This code does not know how to materialize from wasm buffers. | |
| DCHECK(!buffer->is_wasm_memory()); | |
| void* backing_store = | |
| isolate->array_buffer_allocator()->AllocateUninitialized( | |
| fixed_typed_array->DataSize()); | |
| if (backing_store == nullptr) { | |
| isolate->heap()->FatalProcessOutOfMemory( | |
| "JSTypedArray::MaterializeArrayBuffer"); | |
| } | |
| buffer->set_is_external(false); | |
| DCHECK(buffer->byte_length()->IsSmi() || | |
| buffer->byte_length()->IsHeapNumber()); | |
| DCHECK(NumberToInt32(buffer->byte_length()) == fixed_typed_array->DataSize()); | |
| // Initialize backing store at last to avoid handling of |JSArrayBuffers| that | |
| // are currently being constructed in the |ArrayBufferTracker|. The | |
| // registration method below handles the case of registering a buffer that has | |
| // already been promoted. | |
| buffer->set_backing_store(backing_store); | |
| // RegisterNewArrayBuffer expects a valid length for adjusting counters. | |
| isolate->heap()->RegisterNewArrayBuffer(*buffer); | |
| memcpy(buffer->backing_store(), fixed_typed_array->DataPtr(), | |
| fixed_typed_array->DataSize()); | |
| Handle<FixedTypedArrayBase> new_elements = | |
| isolate->factory()->NewFixedTypedArrayWithExternalPointer( | |
| fixed_typed_array->length(), typed_array->type(), | |
| static_cast<uint8_t*>(buffer->backing_store())); | |
| typed_array->set_elements(*new_elements); | |
| DCHECK(!typed_array->is_on_heap()); | |
| return buffer; | |
| } | |
| Handle<JSArrayBuffer> JSTypedArray::GetBuffer() { | |
| if (!is_on_heap()) { | |
| Handle<JSArrayBuffer> array_buffer(JSArrayBuffer::cast(buffer()), | |
| GetIsolate()); | |
| return array_buffer; | |
| } | |
| Handle<JSTypedArray> self(this, GetIsolate()); | |
| return MaterializeArrayBuffer(self); | |
| } | |
| // ES#sec-integer-indexed-exotic-objects-defineownproperty-p-desc | |
| // static | |
| Maybe<bool> JSTypedArray::DefineOwnProperty(Isolate* isolate, | |
| Handle<JSTypedArray> o, | |
| Handle<Object> key, | |
| PropertyDescriptor* desc, | |
| ShouldThrow should_throw) { | |
| // 1. Assert: IsPropertyKey(P) is true. | |
| DCHECK(key->IsName() || key->IsNumber()); | |
| // 2. Assert: O is an Object that has a [[ViewedArrayBuffer]] internal slot. | |
| // 3. If Type(P) is String, then | |
| if (key->IsString() || key->IsSmi()) { | |
| // 3a. Let numericIndex be ! CanonicalNumericIndexString(P) | |
| // 3b. If numericIndex is not undefined, then | |
| Handle<Object> numeric_index; | |
| if (CanonicalNumericIndexString(isolate, key, &numeric_index)) { | |
| // 3b i. If IsInteger(numericIndex) is false, return false. | |
| // 3b ii. If numericIndex = -0, return false. | |
| // 3b iii. If numericIndex < 0, return false. | |
| // FIXME: the standard allows up to 2^53 elements. | |
| uint32_t index; | |
| if (numeric_index->IsMinusZero() || !numeric_index->ToUint32(&index)) { | |
| RETURN_FAILURE(isolate, should_throw, | |
| NewTypeError(MessageTemplate::kInvalidTypedArrayIndex)); | |
| } | |
| // 3b iv. Let length be O.[[ArrayLength]]. | |
| uint32_t length = o->length()->Number(); | |
| // 3b v. If numericIndex ≥ length, return false. | |
| if (index >= length) { | |
| RETURN_FAILURE(isolate, should_throw, | |
| NewTypeError(MessageTemplate::kInvalidTypedArrayIndex)); | |
| } | |
| // 3b vi. If IsAccessorDescriptor(Desc) is true, return false. | |
| if (PropertyDescriptor::IsAccessorDescriptor(desc)) { | |
| RETURN_FAILURE(isolate, should_throw, | |
| NewTypeError(MessageTemplate::kRedefineDisallowed, key)); | |
| } | |
| // 3b vii. If Desc has a [[Configurable]] field and if | |
| // Desc.[[Configurable]] is true, return false. | |
| // 3b viii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] | |
| // is false, return false. | |
| // 3b ix. If Desc has a [[Writable]] field and if Desc.[[Writable]] is | |
| // false, return false. | |
| if ((desc->has_configurable() && desc->configurable()) || | |
| (desc->has_enumerable() && !desc->enumerable()) || | |
| (desc->has_writable() && !desc->writable())) { | |
| RETURN_FAILURE(isolate, should_throw, | |
| NewTypeError(MessageTemplate::kRedefineDisallowed, key)); | |
| } | |
| // 3b x. If Desc has a [[Value]] field, then | |
| // 3b x 1. Let value be Desc.[[Value]]. | |
| // 3b x 2. Return ? IntegerIndexedElementSet(O, numericIndex, value). | |
| if (desc->has_value()) { | |
| if (!desc->has_configurable()) desc->set_configurable(false); | |
| if (!desc->has_enumerable()) desc->set_enumerable(true); | |
| if (!desc->has_writable()) desc->set_writable(true); | |
| Handle<Object> value = desc->value(); | |
| RETURN_ON_EXCEPTION_VALUE(isolate, | |
| SetOwnElementIgnoreAttributes( | |
| o, index, value, desc->ToAttributes()), | |
| Nothing<bool>()); | |
| } | |
| // 3b xi. Return true. | |
| return Just(true); | |
| } | |
| } | |
| // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc). | |
| return OrdinaryDefineOwnProperty(isolate, o, key, desc, should_throw); | |
| } | |
| ExternalArrayType JSTypedArray::type() { | |
| switch (elements()->map()->instance_type()) { | |
| #define INSTANCE_TYPE_TO_ARRAY_TYPE(Type, type, TYPE, ctype) \ | |
| case FIXED_##TYPE##_ARRAY_TYPE: \ | |
| return kExternal##Type##Array; | |
| TYPED_ARRAYS(INSTANCE_TYPE_TO_ARRAY_TYPE) | |
| #undef INSTANCE_TYPE_TO_ARRAY_TYPE | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| size_t JSTypedArray::element_size() { | |
| switch (elements()->map()->instance_type()) { | |
| #define INSTANCE_TYPE_TO_ELEMENT_SIZE(Type, type, TYPE, ctype) \ | |
| case FIXED_##TYPE##_ARRAY_TYPE: \ | |
| return sizeof(ctype); | |
| TYPED_ARRAYS(INSTANCE_TYPE_TO_ELEMENT_SIZE) | |
| #undef INSTANCE_TYPE_TO_ELEMENT_SIZE | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| } // namespace internal | |
| } // namespace v8 |