diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp index d21f03e542..b2bd832234 100644 --- a/src/dawn/node/binding/Converter.cpp +++ b/src/dawn/node/binding/Converter.cpp @@ -149,6 +149,7 @@ namespace wgpu::binding { auto arr = v.ArrayBuffer(); out.data = arr.Data(); out.size = arr.ByteLength(); + out.bytesPerElement = v.ElementSize(); }, *view); return true; @@ -156,6 +157,7 @@ namespace wgpu::binding { if (auto* arr = std::get_if(&in)) { out.data = arr->Data(); out.size = arr->ByteLength(); + out.bytesPerElement = 1; return true; } Napi::Error::New(env, "invalid value for BufferSource").ThrowAsJavaScriptException(); diff --git a/src/dawn/node/binding/Converter.h b/src/dawn/node/binding/Converter.h index cfe38e681c..f0fa75314d 100644 --- a/src/dawn/node/binding/Converter.h +++ b/src/dawn/node/binding/Converter.h @@ -95,7 +95,8 @@ namespace wgpu::binding { // BufferSource is the converted type of interop::BufferSource. struct BufferSource { void* data; - size_t size; + size_t size; // in bytes + size_t bytesPerElement; // 1 for ArrayBuffers }; private: diff --git a/src/dawn/node/binding/GPUQueue.cpp b/src/dawn/node/binding/GPUQueue.cpp index 6081e00ae5..0e3a0beec7 100644 --- a/src/dawn/node/binding/GPUQueue.cpp +++ b/src/dawn/node/binding/GPUQueue.cpp @@ -14,6 +14,8 @@ #include "src/dawn/node/binding/GPUQueue.h" +#include +#include #include #include "src/dawn/node/binding/Converter.h" @@ -73,8 +75,8 @@ namespace wgpu::binding { interop::Interface buffer, interop::GPUSize64 bufferOffset, interop::BufferSource data, - interop::GPUSize64 dataOffset, - std::optional size) { + interop::GPUSize64 dataOffsetElements, + std::optional sizeElements) { wgpu::Buffer buf = *buffer.As(); Converter::BufferSource src{}; Converter conv(env); @@ -82,16 +84,43 @@ namespace wgpu::binding { return; } - // TODO(crbug.com/dawn/1132): Bounds check - if (src.data) { - src.data = reinterpret_cast(src.data) + dataOffset; + // Note that in the JS semantics of WebGPU, writeBuffer works in number of elements of the + // typed arrays. + if (dataOffsetElements > uint64_t(src.size / src.bytesPerElement)) { + binding::Errors::OperationError(env, "dataOffset is larger than data's size.") + .ThrowAsJavaScriptException(); + return; } + uint64_t dataOffset = dataOffsetElements * src.bytesPerElement; + src.data = reinterpret_cast(src.data) + dataOffset; src.size -= dataOffset; - if (size.has_value()) { - src.size = size.value(); + + // Size defaults to dataSize - dataOffset. Instead of computing in elements, we directly + // use it in bytes, and convert the provided value, if any, in bytes. + uint64_t size64 = uint64_t(src.size); + if (sizeElements.has_value()) { + if (sizeElements.value() > std::numeric_limits::max() / src.bytesPerElement) { + binding::Errors::OperationError(env, "size overflows.") + .ThrowAsJavaScriptException(); + return; + } + size64 = sizeElements.value() * src.bytesPerElement; } - queue_.WriteBuffer(buf, bufferOffset, src.data, src.size); + if (size64 > uint64_t(src.size)) { + binding::Errors::OperationError(env, "size + dataOffset is larger than data's size.") + .ThrowAsJavaScriptException(); + return; + } + + if (size64 % 4 != 0) { + binding::Errors::OperationError(env, "size is not a multiple of 4 bytes.") + .ThrowAsJavaScriptException(); + return; + } + + assert(size64 <= std::numeric_limits::max()); + queue_.WriteBuffer(buf, bufferOffset, src.data, static_cast(size64)); } void GPUQueue::writeTexture(Napi::Env env,