/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/builtins/builtins-arraybuffer.cc
889 строк
36 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// 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/builtins/builtins-utils-inl.h" #include "src/builtins/builtins.h" #include "src/execution/protectors-inl.h" #include "src/execution/protectors.h" #include "src/handles/maybe-handles-inl.h" #include "src/logging/counters.h" #include "src/numbers/conversions.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/objects-inl.h" namespace v8 { namespace internal { #define CHECK_SHARED(expected, name, method) \ if (name->is_shared() != expected) { \ THROW_NEW_ERROR_RETURN_FAILURE( \ isolate, \ NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, \ isolate->factory()->NewStringFromAsciiChecked(method), \ name)); \ } #define CHECK_RESIZABLE(expected, name, method) \ if (name->is_resizable_by_js() != expected) { \ THROW_NEW_ERROR_RETURN_FAILURE( \ isolate, \ NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, \ isolate->factory()->NewStringFromAsciiChecked(method), \ name)); \ } // ----------------------------------------------------------------------------- // ES#sec-arraybuffer-objects namespace { // Tries to allocate a BackingStore given the input configuration. Either // returns the BackingStore or a message template that should be thrown as // RangeError. std::pair<std::unique_ptr<BackingStore>, std::optional<MessageTemplate>> TryAllocateBackingStore(Isolate* isolate, SharedFlag shared, ResizableFlag resizable, DirectHandle<Object> length, DirectHandle<Object> max_length, InitializedFlag initialized) { DisallowJavascriptExecution no_js(isolate); size_t byte_length; size_t max_byte_length = 0; std::unique_ptr<BackingStore> backing_store; size_t max_allocatable = isolate->array_buffer_allocator()->MaxAllocationSize(); DCHECK(max_allocatable <= JSArrayBuffer::kMaxByteLength); static_assert(JSArrayBuffer::kMaxByteLength == JSTypedArray::kMaxByteLength); if (!TryNumberToSize(*length, &byte_length) || byte_length > max_allocatable) { return {nullptr, MessageTemplate::kInvalidArrayBufferLength}; } switch (resizable) { case ResizableFlag::kNotResizable: backing_store = BackingStore::Allocate(isolate, byte_length, shared, initialized); break; case ResizableFlag::kResizable: if (!TryNumberToSize(*max_length, &max_byte_length) || max_byte_length > max_allocatable) { return {nullptr, MessageTemplate::kInvalidArrayBufferMaxLength}; } if (byte_length > max_byte_length) { return {nullptr, MessageTemplate::kInvalidArrayBufferMaxLength}; } size_t page_size, initial_pages, max_pages; const auto maybe_range_error = JSArrayBuffer::GetResizableBackingStorePageConfigurationImpl( isolate, byte_length, max_byte_length, &page_size, &initial_pages, &max_pages); if (maybe_range_error.has_value()) { return {nullptr, maybe_range_error.value()}; } backing_store = BackingStore::TryAllocateAndPartiallyCommitMemory( isolate, byte_length, max_byte_length, page_size, initial_pages, max_pages, WasmMemoryFlag::kNotWasm, shared); break; } // Range errors bailed out earlier; only the failing allocation needs to be // caught here. if (!backing_store) { return {nullptr, MessageTemplate::kArrayBufferAllocationFailed}; } return {std::move(backing_store), std::nullopt}; } Tagged<Object> ConstructBuffer(Isolate* isolate, DirectHandle<JSFunction> target, DirectHandle<JSReceiver> new_target, DirectHandle<Object> length, DirectHandle<Object> max_length, InitializedFlag initialized) { // We first try to convert the sizes and collect any possible range errors. If // no errors are observable we create the BackingStore before the // JSArrayBuffer to avoid a complex dance during setup. We then always create // the AB before throwing a possible error as the creation is observable. const SharedFlag shared = *target != target->native_context()->array_buffer_fun() ? SharedFlag::kShared : SharedFlag::kNotShared; const ResizableFlag resizable = max_length.is_null() ? ResizableFlag::kNotResizable : ResizableFlag::kResizable; // BackingStore allocation may GC which is not observable itself. auto [backing_store, range_error] = TryAllocateBackingStore( isolate, shared, resizable, length, max_length, initialized); DirectHandle<JSObject> result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, result, JSObject::New(target, new_target, {}, NewJSObjectType::kMaybeEmbedderFieldsAndApiWrapper)); auto array_buffer = Cast<JSArrayBuffer>(result); const bool backing_store_creation_failed = !backing_store; array_buffer->Setup(shared, resizable, std::move(backing_store), isolate); if (backing_store_creation_failed) { CHECK(range_error.has_value()); THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewRangeError(range_error.value())); } return *array_buffer; } } // namespace // ES #sec-arraybuffer-constructor BUILTIN(ArrayBufferConstructor) { HandleScope scope(isolate); DirectHandle<JSFunction> target = args.target(); DCHECK(*target == target->native_context()->array_buffer_fun() || *target == target->native_context()->shared_array_buffer_fun()); if (IsUndefined(*args.new_target(), isolate)) { // [[Call]] THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kConstructorNotFunction, direct_handle(target->shared()->Name(), isolate))); } // [[Construct]] DirectHandle<JSReceiver> new_target = Cast<JSReceiver>(args.new_target()); DirectHandle<Object> length = args.atOrUndefined(isolate, 1); DirectHandle<Object> number_length; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, number_length, Object::ToInteger(isolate, length)); if (Object::NumberValue(*number_length) < 0.0) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferLength)); } DirectHandle<Object> number_max_length; DirectHandle<Object> max_length; DirectHandle<Object> options = args.atOrUndefined(isolate, 2); ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, max_length, JSObject::ReadFromOptionsBag( options, isolate->factory()->max_byte_length_string(), isolate)); if (!IsUndefined(*max_length, isolate)) { if (*target == target->native_context()->array_buffer_fun()) { isolate->CountUsage( v8::Isolate::UseCounterFeature::kResizableArrayBuffer); } else { isolate->CountUsage( v8::Isolate::UseCounterFeature::kGrowableSharedArrayBuffer); } ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, number_max_length, Object::ToInteger(isolate, max_length)); if (Object::NumberValue(*number_length) > Object::NumberValue(*number_max_length)) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferMaxLength)); } } return ConstructBuffer(isolate, target, new_target, number_length, number_max_length, InitializedFlag::kZeroInitialized); } // This is a helper to construct an ArrayBuffer with uinitialized memory. // This means the caller must ensure the buffer is totally initialized in // all cases, or we will expose uinitialized memory to user code. BUILTIN(ArrayBufferConstructor_DoNotInitialize) { HandleScope scope(isolate); DirectHandle<JSFunction> target(isolate->native_context()->array_buffer_fun(), isolate); DirectHandle<Object> length = args.atOrUndefined(isolate, 1); return ConstructBuffer(isolate, target, target, length, {}, InitializedFlag::kUninitialized); } static Tagged<Object> SliceHelper(BuiltinArguments args, Isolate* isolate, const char* kMethodName, bool is_shared, bool to_immutable) { HandleScope scope(isolate); DirectHandle<Object> start = args.at(1); DirectHandle<Object> end = args.atOrUndefined(isolate, 2); // * If Type(O) is not Object, throw a TypeError exception. // * If O does not have an [[ArrayBufferData]] internal slot, throw a // TypeError exception. CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); // * [AB] If IsSharedArrayBuffer(O) is true, throw a TypeError exception. // * [SAB] If IsSharedArrayBuffer(O) is false, throw a TypeError exception. CHECK_SHARED(is_shared, array_buffer, kMethodName); // * [AB] If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if (!is_shared && array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } double new_len, final_, first; { // * [AB] Let len be O.[[ArrayBufferByteLength]]. // * [SAB] Let len be O.[[ArrayBufferByteLength]]. double const len = array_buffer->GetByteLength(); // * Let relativeStart be ? ToInteger(start). double relative_start; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, relative_start, Object::IntegerValue(isolate, start)); // * If relativeStart < 0, let first be max((len + relativeStart), 0); else // let first be min(relativeStart, len). first = (relative_start < 0) ? std::max(len + relative_start, 0.0) : std::min(relative_start, len); // * If end is undefined, let relativeEnd be len; else let relativeEnd be ? // ToInteger(end). double relative_end; if (IsUndefined(*end, isolate)) { relative_end = len; } else { ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, relative_end, Object::IntegerValue(isolate, end)); } // * If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let // final be min(relativeEnd, len). final_ = (relative_end < 0) ? std::max(len + relative_end, 0.0) : std::min(relative_end, len); // * Let newLen be max(final-first, 0). new_len = std::max(final_ - first, 0.0); } DirectHandle<Object> new_len_obj = isolate->factory()->NewNumber(new_len); // * [AB] Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). // * [SAB] Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%). DirectHandle<JSFunction> constructor_fun = is_shared ? isolate->shared_array_buffer_fun() : isolate->array_buffer_fun(); DirectHandle<Object> ctor; if (to_immutable) { // In the sliceToImmutable case we unconditionally create an immutable // buffer with the default ctr. // * Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, // fromBuf, first, newLen). ctor = constructor_fun; } else { ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, ctor, Object::SpeciesConstructor(isolate, Cast<JSReceiver>(args.receiver()), constructor_fun)); } // * Let new be ? Construct(ctor, newLen). DirectHandle<JSReceiver> new_; { constexpr int argc = 1; std::array<DirectHandle<Object>, argc> ctor_args = {new_len_obj}; DirectHandle<Object> new_obj; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, new_obj, Execution::New(isolate, ctor, base::VectorOf(ctor_args))); new_ = Cast<JSReceiver>(new_obj); } // * NOTE: Side-effects of the above steps may have detached or resized O. // * If new does not have an [[ArrayBufferData]] internal slot, throw a // TypeError exception. if (to_immutable) { DCHECK(IsJSArrayBuffer(*new_)); } else if (!IsJSArrayBuffer(*new_)) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, isolate->factory()->NewStringFromAsciiChecked(kMethodName), new_)); } // * [AB] If IsSharedArrayBuffer(new) is true, throw a TypeError exception. // * [SAB] If IsSharedArrayBuffer(new) is false, throw a TypeError exception. DirectHandle<JSArrayBuffer> new_array_buffer = Cast<JSArrayBuffer>(new_); CHECK_SHARED(is_shared, new_array_buffer, kMethodName); if (to_immutable) { new_array_buffer->set_is_immutable(true); if (auto backing_store = new_array_buffer->GetBackingStore()) { backing_store->set_is_immutable(true); } DCHECK(!new_array_buffer->was_detached()); // * If IsDetachedBuffer(O) is true, throw a TypeError exception. // * Let fromBuf be O.[[ArrayBufferData]]. // * Let currentLen be O.[[ArrayBufferByteLength]]. // * If currentLen < final, throw a RangeError exception. DCHECK(!is_shared); if (array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } size_t current_len = array_buffer->GetByteLength(); if (current_len < final_) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferLength)); } } else { // The created ArrayBuffer might or might not be resizable, since the // species constructor might return a non-resizable or a resizable buffer. // * [AB] If IsDetachedBuffer(new) is true, throw a TypeError exception. if (!is_shared && new_array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } if (!is_shared && new_array_buffer->is_immutable()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayImmutableBufferErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } // * [AB] If SameValue(new, O) is true, throw a TypeError exception. if (!is_shared && Object::SameValue(*new_, *args.receiver())) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kArrayBufferSpeciesThis)); } // * [SAB] If new.[[ArrayBufferData]] and O.[[ArrayBufferData]] are the same // Shared Data Block values, throw a TypeError exception. if (is_shared && new_array_buffer->backing_store() == array_buffer->backing_store()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kSharedArrayBufferSpeciesThis)); } // * If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. size_t new_array_buffer_byte_length = new_array_buffer->GetByteLength(); if (new_array_buffer_byte_length < new_len) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(is_shared ? MessageTemplate::kSharedArrayBufferTooShort : MessageTemplate::kArrayBufferTooShort)); } // * [AB] NOTE: Side-effects of the above steps may have detached O. // * [AB] If IsDetachedBuffer(O) is true, throw a TypeError exception. if (!is_shared && array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } } // * Let fromBuf be O.[[ArrayBufferData]]. // * Let toBuf be new.[[ArrayBufferData]]. // * Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). size_t first_size = first; size_t new_len_size = new_len; DCHECK_GE(new_array_buffer->GetByteLength(), new_len_size); if (new_len_size != 0) { size_t from_byte_length = array_buffer->GetByteLength(); DCHECK_IMPLIES(to_immutable, new_len_size <= from_byte_length - first_size); if (V8_UNLIKELY(!to_immutable && !is_shared && array_buffer->is_resizable_by_js())) { // The above steps might have resized the underlying buffer. In that case, // only copy the still-accessible portion of the underlying data. if (first_size > from_byte_length) { return *new_; // Nothing to copy. } if (new_len_size > from_byte_length - first_size) { new_len_size = from_byte_length - first_size; } } DCHECK(first_size <= from_byte_length); DCHECK(from_byte_length - first_size >= new_len_size); uint8_t* from_data = reinterpret_cast<uint8_t*>(array_buffer->backing_store()) + first_size; uint8_t* to_data = reinterpret_cast<uint8_t*>(new_array_buffer->backing_store()); if (is_shared) { base::Relaxed_Memcpy(reinterpret_cast<base::Atomic8*>(to_data), reinterpret_cast<base::Atomic8*>(from_data), new_len_size); } else { CopyBytes(to_data, from_data, new_len_size); } } return *new_; } // ES #sec-sharedarraybuffer.prototype.slice BUILTIN(SharedArrayBufferPrototypeSlice) { const char* const kMethodName = "SharedArrayBuffer.prototype.slice"; return SliceHelper(args, isolate, kMethodName, true, false); } // ES #sec-arraybuffer.prototype.slice // ArrayBuffer.prototype.slice ( start, end ) BUILTIN(ArrayBufferPrototypeSlice) { const char* const kMethodName = "ArrayBuffer.prototype.slice"; return SliceHelper(args, isolate, kMethodName, false, false); } static Tagged<Object> ResizeHelper(BuiltinArguments args, Isolate* isolate, const char* kMethodName, bool is_shared) { HandleScope scope(isolate); // 1 Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]). CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); CHECK_RESIZABLE(true, array_buffer, kMethodName); // [RAB] 3. If IsSharedArrayBuffer(O) is true, throw a *TypeError* exception // [GSAB] 3. If IsSharedArrayBuffer(O) is false, throw a *TypeError* exception CHECK_SHARED(is_shared, array_buffer, kMethodName); // Let newByteLength to ? ToIntegerOrInfinity(newLength). DirectHandle<Object> new_length = args.at(1); DirectHandle<Object> number_new_byte_length; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, number_new_byte_length, Object::ToInteger(isolate, new_length)); // [RAB] If IsDetachedBuffer(O) is true, throw a TypeError exception. if (!is_shared) { if (array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } if (array_buffer->is_immutable()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayImmutableBufferErrorOperation, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } } // [RAB] If newByteLength < 0 or newByteLength > // O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. // [GSAB] If newByteLength < currentByteLength or newByteLength > // O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. size_t new_byte_length; if (!TryNumberToSize(*number_new_byte_length, &new_byte_length)) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } if (is_shared && new_byte_length < array_buffer->GetByteLength()) { // GrowableSharedArrayBuffer is only allowed to grow. THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } if (new_byte_length > array_buffer->max_byte_length()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } // [RAB] Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength). // [GSAB] Let hostHandled be ? HostGrowArrayBuffer(O, newByteLength). // If hostHandled is handled, return undefined. #ifdef V8_ENABLE_WEBASSEMBLY auto backing_store = array_buffer->GetBackingStore(); if (backing_store->is_wasm_memory()) { size_t old_byte_length = backing_store->byte_length(std::memory_order_seq_cst); // WebAssembly memories cannot shrink, and must be a multiple of the page // size. if (new_byte_length < old_byte_length || (new_byte_length % wasm::kWasmPageSize) != 0) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError( MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } Handle<Object> memory = Object::GetProperty( isolate, array_buffer, isolate->factory()->array_buffer_wasm_memory_symbol()) .ToHandleChecked(); CHECK(IsWasmMemoryObject(*memory)); // WasmMemoryObject::Grow handles updating byte_length, as it's used by both // ArrayBuffer.prototype.resize and WebAssembly.Memory.prototype.grow. uint32_t delta_pages = static_cast<uint32_t>(new_byte_length - old_byte_length) / wasm::kWasmPageSize; if (WasmMemoryObject::Grow(isolate, Cast<WasmMemoryObject>(memory), delta_pages) == -1) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kOutOfMemory, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } return ReadOnlyRoots(isolate).undefined_value(); } #endif // V8_ENABLE_WEBASSEMBLY if (!is_shared) { // [RAB] Let oldBlock be O.[[ArrayBufferData]]. // [RAB] Let newBlock be ? CreateByteDataBlock(newByteLength). // [RAB] Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]). // [RAB] Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength). // [RAB] NOTE: Neither creation of the new Data Block nor copying from the // old Data Block are observable. Implementations reserve the right to // implement this method as in-place growth or shrinkage. if (array_buffer->GetBackingStore()->ResizeInPlace(isolate, new_byte_length) != BackingStore::ResizeOrGrowResult::kSuccess) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kOutOfMemory, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } // TypedsArrays in optimized code may go out of bounds. Trigger deopts // through the ArrayBufferDetaching protector. if (new_byte_length < array_buffer->byte_length()) { // TODO(olivf, 467645277): Try to resize only the view and avoid firing // the protector. if (Protectors::IsArrayBufferDetachingIntact(isolate)) { Protectors::InvalidateArrayBufferDetaching(isolate); } } isolate->heap()->ResizeArrayBufferExtension( array_buffer->extension(), static_cast<int64_t>(new_byte_length) - array_buffer->byte_length()); // [RAB] Set O.[[ArrayBufferByteLength]] to newLength. array_buffer->set_byte_length(new_byte_length); } else { // [GSAB] (Detailed description of the algorithm omitted.) auto result = array_buffer->GetBackingStore()->GrowInPlace(isolate, new_byte_length); if (result == BackingStore::ResizeOrGrowResult::kFailure) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kOutOfMemory, isolate->factory()->NewStringFromAsciiChecked( kMethodName))); } if (result == BackingStore::ResizeOrGrowResult::kRace) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError( MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked(kMethodName))); } // Invariant: byte_length for a GSAB is 0 (it needs to be read from the // BackingStore). Don't use the byte_length getter, which DCHECKs that it's // not used on growable SharedArrayBuffers CHECK_EQ(0, array_buffer->byte_length_unchecked()); } return ReadOnlyRoots(isolate).undefined_value(); } // ES #sec-get-sharedarraybuffer.prototype.bytelength // get SharedArrayBuffer.prototype.byteLength BUILTIN(SharedArrayBufferPrototypeGetByteLength) { const char* const kMethodName = "get SharedArrayBuffer.prototype.byteLength"; HandleScope scope(isolate); // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. CHECK_SHARED(true, array_buffer, kMethodName); DCHECK_IMPLIES(!array_buffer->GetBackingStore()->is_wasm_memory(), array_buffer->max_byte_length() == array_buffer->GetBackingStore()->max_byte_length()); // 4. Let length be ArrayBufferByteLength(O, SeqCst). size_t byte_length = array_buffer->GetByteLength(); // 5. Return F(length). return *isolate->factory()->NewNumberFromSize(byte_length); } // ES #sec-arraybuffer.prototype.resize // ArrayBuffer.prototype.resize(new_size) BUILTIN(ArrayBufferPrototypeResize) { const char* const kMethodName = "ArrayBuffer.prototype.resize"; constexpr bool kIsShared = false; return ResizeHelper(args, isolate, kMethodName, kIsShared); } namespace { enum PreserveResizability { kToFixedLength, kPreserveResizability, kToImmutable }; Tagged<Object> ArrayBufferTransfer(Isolate* isolate, DirectHandle<JSArrayBuffer> array_buffer, DirectHandle<Object> new_length, PreserveResizability preserve_resizability, const char* method_name) { size_t max_allocatable = isolate->array_buffer_allocator()->MaxAllocationSize(); DCHECK(max_allocatable <= JSArrayBuffer::kMaxByteLength); // 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError // exception. CHECK_SHARED(false, array_buffer, method_name); size_t new_byte_length; if (IsUndefined(*new_length, isolate)) { // 3. If newLength is undefined, then // a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]]. new_byte_length = array_buffer->GetByteLength(); } else { // 4. Else, // a. Let newByteLength be ? ToIndex(newLength). DirectHandle<Object> number_new_byte_length; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, number_new_byte_length, Object::ToInteger(isolate, new_length)); if (Object::NumberValue(*number_new_byte_length) < 0.0) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferLength)); } if (!TryNumberToSize(*number_new_byte_length, &new_byte_length) || new_byte_length > max_allocatable) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError( MessageTemplate::kInvalidArrayBufferResizeLength, isolate->factory()->NewStringFromAsciiChecked(method_name))); } } // 5. If IsDetachedBuffer(arrayBuffer) is true, throw a TypeError exception. if (array_buffer->was_detached()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayDetachedErrorOperation, isolate->factory()->NewStringFromAsciiChecked(method_name))); } if (array_buffer->is_immutable()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError( MessageTemplate::kTypedArrayImmutableBufferErrorOperation, isolate->factory()->NewStringFromAsciiChecked(method_name))); } ResizableFlag resizable; size_t new_max_byte_length; if (preserve_resizability == kPreserveResizability && array_buffer->is_resizable_by_js()) { // 6. If preserveResizability is preserve-resizability and // IsResizableArrayBuffer(arrayBuffer) is true, then // a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]]. new_max_byte_length = array_buffer->max_byte_length(); resizable = ResizableFlag::kResizable; } else { // 7. Else, // a. Let newMaxByteLength be empty. new_max_byte_length = new_byte_length; resizable = ResizableFlag::kNotResizable; } // 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a // TypeError exception. if (!IsUndefined(array_buffer->DetachKey(isolate)) || !array_buffer->is_detachable()) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kDataCloneErrorNonDetachableArrayBuffer)); } // After this point the steps are not observable and are performed out of // spec order. DirectHandle<JSArrayBuffer> result_buffer; // Case 1: We don't need a BackingStore. if (new_byte_length == 0) { // 15. Perform ! DetachArrayBuffer(arrayBuffer). JSArrayBuffer::Detach(array_buffer).Check(); // 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, // newMaxByteLength). // // Nothing to do for steps 10-14. result_buffer = isolate->factory() ->NewJSArrayBufferAndBackingStore( 0, new_max_byte_length, InitializedFlag::kUninitialized, resizable) .ToHandleChecked(); } else { // Case 2: We can reuse the same BackingStore. auto from_backing_store = array_buffer->GetBackingStore(); if (from_backing_store && !from_backing_store->is_resizable_by_js() && resizable == ResizableFlag::kNotResizable && new_byte_length == array_buffer->GetByteLength()) { // TODO(syg): Consider realloc when the default ArrayBuffer allocator's // Reallocate does better than copy. // // See https://crbug.com/330575496#comment27 // 15. Perform ! DetachArrayBuffer(arrayBuffer). JSArrayBuffer::Detach(array_buffer).Check(); // 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, // newMaxByteLength). result_buffer = isolate->factory()->NewJSArrayBuffer(std::move(from_backing_store)); } else { // Case 3: We can't reuse the same BackingStore. Copy the buffer. if (new_byte_length > new_max_byte_length) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferLength)); } // 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, // newMaxByteLength). DirectHandle<JSArrayBuffer> new_buffer; MaybeDirectHandle<JSArrayBuffer> result = isolate->factory()->NewJSArrayBufferAndBackingStore( new_byte_length, new_max_byte_length, InitializedFlag::kUninitialized, resizable); if (!result.ToHandle(&new_buffer)) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewRangeError(MessageTemplate::kArrayBufferAllocationFailed)); } // 10. Let copyLength be min(newByteLength, // arrayBuffer.[[ArrayBufferByteLength]]). // // (Size comparison is done manually below instead of using min.) // 11. Let fromBlock be arrayBuffer.[[ArrayBufferData]]. uint8_t* from_data = reinterpret_cast<uint8_t*>(array_buffer->backing_store()); // 12. Let toBlock be newBuffer.[[ArrayBufferData]]. uint8_t* to_data = reinterpret_cast<uint8_t*>(new_buffer->backing_store()); // 13. Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength). // 14. NOTE: Neither creation of the new Data Block nor copying from the // old // Data Block are observable. Implementations reserve the right to // implement this method as a zero-copy move or a realloc. size_t from_byte_length = array_buffer->GetByteLength(); if (new_byte_length <= from_byte_length) { CopyBytes(to_data, from_data, new_byte_length); } else { CopyBytes(to_data, from_data, from_byte_length); memset(to_data + from_byte_length, 0, new_byte_length - from_byte_length); } // 15. Perform ! DetachArrayBuffer(arrayBuffer). JSArrayBuffer::Detach(array_buffer).Check(); result_buffer = new_buffer; } } if (preserve_resizability == kToImmutable) { result_buffer->set_is_immutable(true); if (auto backing_store = result_buffer->GetBackingStore()) { backing_store->set_is_immutable(true); } } // 16. Return newBuffer. return *result_buffer; } } // namespace // ES #sec-arraybuffer.prototype.transfer // ArrayBuffer.prototype.transfer([new_length]) BUILTIN(ArrayBufferPrototypeTransfer) { const char kMethodName[] = "ArrayBuffer.prototype.transfer"; HandleScope scope(isolate); isolate->CountUsage(v8::Isolate::kArrayBufferTransfer); // 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]). CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); DirectHandle<Object> new_length = args.atOrUndefined(isolate, 1); return ArrayBufferTransfer(isolate, array_buffer, new_length, kPreserveResizability, kMethodName); } // ES #sec-arraybuffer.prototype.transferToFixedLength // ArrayBuffer.prototype.transferToFixedLength([new_length]) BUILTIN(ArrayBufferPrototypeTransferToFixedLength) { const char kMethodName[] = "ArrayBuffer.prototype.transferToFixedLength"; HandleScope scope(isolate); isolate->CountUsage(v8::Isolate::kArrayBufferTransfer); // 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]). CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); DirectHandle<Object> new_length = args.atOrUndefined(isolate, 1); return ArrayBufferTransfer(isolate, array_buffer, new_length, kToFixedLength, kMethodName); } // ES #sec-sharedarraybuffer.prototype.grow // SharedArrayBuffer.prototype.grow(new_size) BUILTIN(SharedArrayBufferPrototypeGrow) { const char* const kMethodName = "SharedArrayBuffer.prototype.grow"; constexpr bool kIsShared = true; return ResizeHelper(args, isolate, kMethodName, kIsShared); } // ES #sec-arraybuffer.prototype.transferToImmutable BUILTIN(ArrayBufferPrototypeTransferToImmutable) { const char kMethodName[] = "ArrayBuffer.prototype.transferToImmutable"; HandleScope scope(isolate); isolate->CountUsage(v8::Isolate::kArrayBufferTransfer); // 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]). CHECK_RECEIVER(JSArrayBuffer, array_buffer, kMethodName); DirectHandle<Object> new_length = args.atOrUndefined(isolate, 1); return ArrayBufferTransfer(isolate, array_buffer, new_length, kToImmutable, kMethodName); } // ES #sec-arraybuffer.prototype.sliceToImmutable BUILTIN(ArrayBufferPrototypeSliceToImmutable) { const char* const kMethodName = "ArrayBuffer.prototype.sliceToImmutable"; return SliceHelper(args, isolate, kMethodName, false, true); } } // namespace internal } // namespace v8