From a635abbf2136f5512543551af6a6c37b5e9aa4ba Mon Sep 17 00:00:00 2001 From: Hajime-san <41257923+Hajime-san@users.noreply.github.com> Date: Mon, 6 May 2024 22:26:25 +0900 Subject: [PATCH] fix(ext/webgpu): correctly validate GPUExtent3D, GPUOrigin3D, GPUOrigin2D & GPUColor (#23413) --- ext/webgpu/01_webgpu.js | 40 +++++- tests/unit/webgpu_test.ts | 262 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+), 1 deletion(-) diff --git a/ext/webgpu/01_webgpu.js b/ext/webgpu/01_webgpu.js index 5ca8c0fdbdedf2..be0b87cdf020be 100644 --- a/ext/webgpu/01_webgpu.js +++ b/ext/webgpu/01_webgpu.js @@ -1136,10 +1136,11 @@ class GPUDevice extends EventTarget { "Argument 1", ); const device = assertDevice(this, prefix, "this"); + // assign normalized size to descriptor due to createGPUTexture needs it + descriptor.size = normalizeGPUExtent3D(descriptor.size); const { rid, err } = op_webgpu_create_texture({ deviceRid: device.rid, ...descriptor, - size: normalizeGPUExtent3D(descriptor.size), }); device.pushError(err); @@ -5501,6 +5502,16 @@ webidl.converters["GPUExtent3D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUExtent3D + const min = 1; + const max = 3; + if (V.length < min || V.length > max) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUExtent3D must have between ${min} and ${max} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUExtent3DDict"](V, opts); @@ -6836,6 +6847,15 @@ webidl.converters["GPUOrigin3D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUOrigin3D + const length = 3; + if (V.length > length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUOrigin3D must have at most ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUOrigin3DDict"](V, opts); @@ -6904,6 +6924,15 @@ webidl.converters["GPUOrigin2D"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUOrigin2D + const length = 2; + if (V.length > length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUOrigin2D must have at most ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUOrigin2DDict"](V, opts); @@ -6989,6 +7018,15 @@ webidl.converters["GPUColor"] = (V, opts) => { if (typeof V === "object") { const method = V[SymbolIterator]; if (method !== undefined) { + // validate length of GPUColor + const length = 4; + if (V.length !== length) { + throw webidl.makeException( + TypeError, + `A sequence of number used as a GPUColor must have exactly ${length} elements.`, + opts, + ); + } return webidl.converters["sequence"](V, opts); } return webidl.converters["GPUColorDict"](V, opts); diff --git a/tests/unit/webgpu_test.ts b/tests/unit/webgpu_test.ts index 517c75f9eac3d1..4b65e003384402 100644 --- a/tests/unit/webgpu_test.ts +++ b/tests/unit/webgpu_test.ts @@ -252,6 +252,268 @@ Deno.test(function getPreferredCanvasFormat() { assert(preferredFormat === "bgra8unorm" || preferredFormat === "rgba8unorm"); }); +Deno.test({ + ignore: isWsl || isLinuxOrMacCI, +}, async function validateGPUColor() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + const device = await adapter.requestDevice(); + assert(device); + + const format = "rgba8unorm-srgb"; + const encoder = device.createCommandEncoder(); + const texture = device.createTexture({ + size: [256, 256], + format, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + }); + const view = texture.createView(); + const storeOp = "store"; + const loadOp = "clear"; + + // values for validating GPUColor + const invalidSize = [0, 0, 0]; + + const msgIncludes = + "A sequence of number used as a GPUColor must have exactly 4 elements."; + + // validate the argument of descriptor.colorAttachments[@@iterator].clearValue property's length of GPUCommandEncoder.beginRenderPass when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-beginrenderpass + assertThrows( + () => + encoder.beginRenderPass({ + colorAttachments: [ + { + view, + storeOp, + loadOp, + clearValue: invalidSize, + }, + ], + }), + TypeError, + msgIncludes, + ); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view, + storeOp, + loadOp, + clearValue: [0, 0, 0, 1], + }, + ], + }); + // validate the argument of color length of GPURenderPassEncoder.setBlendConstant when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpurenderpassencoder-setblendconstant + assertThrows( + () => renderPass.setBlendConstant(invalidSize), + TypeError, + msgIncludes, + ); + + device.destroy(); + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); + +Deno.test({ + ignore: isWsl || isLinuxOrMacCI, +}, async function validateGPUExtent3D() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + const device = await adapter.requestDevice(); + assert(device); + + const format = "rgba8unorm-srgb"; + const encoder = device.createCommandEncoder(); + const buffer = device.createBuffer({ + size: new Uint32Array([1, 4, 3, 295]).byteLength, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + const usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC; + const texture = device.createTexture({ + size: [256, 256], + format, + usage, + }); + + // values for validating GPUExtent3D + const belowSize: Array = []; + const overSize = [256, 256, 1, 1]; + + const msgIncludes = + "A sequence of number used as a GPUExtent3D must have between 1 and 3 elements."; + + // validate the argument of descriptor.size property's length of GPUDevice.createTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpudevice-createtexture + assertThrows( + () => device.createTexture({ size: belowSize, format, usage }), + TypeError, + msgIncludes, + ); + assertThrows( + () => device.createTexture({ size: overSize, format, usage }), + TypeError, + msgIncludes, + ); + // validate the argument of copySize property's length of GPUCommandEncoder.copyBufferToTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copybuffertotexture + assertThrows( + () => encoder.copyBufferToTexture({ buffer }, { texture }, belowSize), + TypeError, + msgIncludes, + ); + assertThrows( + () => encoder.copyBufferToTexture({ buffer }, { texture }, overSize), + TypeError, + msgIncludes, + ); + // validate the argument of copySize property's length of GPUCommandEncoder.copyTextureToBuffer when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetobuffer + assertThrows( + () => encoder.copyTextureToBuffer({ texture }, { buffer }, belowSize), + TypeError, + msgIncludes, + ); + assertThrows( + () => encoder.copyTextureToBuffer({ texture }, { buffer }, overSize), + TypeError, + msgIncludes, + ); + // validate the argument of copySize property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetotexture + assertThrows( + () => encoder.copyTextureToTexture({ texture }, { texture }, belowSize), + TypeError, + msgIncludes, + ); + assertThrows( + () => encoder.copyTextureToTexture({ texture }, { texture }, overSize), + TypeError, + msgIncludes, + ); + const data = new Uint8Array([1 * 255, 1 * 255, 1 * 255, 1 * 255]); + // validate the argument of size property's length of GPUQueue.writeTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpuqueue-writetexture + assertThrows( + () => device.queue.writeTexture({ texture }, data, {}, belowSize), + TypeError, + msgIncludes, + ); + assertThrows( + () => device.queue.writeTexture({ texture }, data, {}, overSize), + TypeError, + msgIncludes, + ); + // NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of copySize property's length when its a sequence, but it is not implemented yet + + device.destroy(); + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); + +Deno.test({ + ignore: true, +}, async function validateGPUOrigin2D() { + // NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of source.origin property's length when its a sequence, but it is not implemented yet +}); + +Deno.test({ + ignore: isWsl || isLinuxOrMacCI, +}, async function validateGPUOrigin3D() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + const device = await adapter.requestDevice(); + assert(device); + + const format = "rgba8unorm-srgb"; + const encoder = device.createCommandEncoder(); + const buffer = device.createBuffer({ + size: new Uint32Array([1, 4, 3, 295]).byteLength, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + const usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC; + const size = [256, 256, 1]; + const texture = device.createTexture({ + size, + format, + usage, + }); + + // value for validating GPUOrigin3D + const overSize = [256, 256, 1, 1]; + + const msgIncludes = + "A sequence of number used as a GPUOrigin3D must have at most 3 elements."; + + // validate the argument of destination.origin property's length of GPUCommandEncoder.copyBufferToTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copybuffertotexture + assertThrows( + () => + encoder.copyBufferToTexture( + { buffer }, + { texture, origin: overSize }, + size, + ), + TypeError, + msgIncludes, + ); + // validate the argument of source.origin property's length of GPUCommandEncoder.copyTextureToBuffer when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetobuffer + assertThrows( + () => + encoder.copyTextureToBuffer( + { texture, origin: overSize }, + { buffer }, + size, + ), + TypeError, + msgIncludes, + ); + // validate the argument of source.origin property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetotexture + assertThrows( + () => + encoder.copyTextureToTexture( + { texture, origin: overSize }, + { texture }, + size, + ), + TypeError, + msgIncludes, + ); + // validate the argument of destination.origin property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence + assertThrows( + () => + encoder.copyTextureToTexture( + { texture }, + { texture, origin: overSize }, + size, + ), + TypeError, + msgIncludes, + ); + // validate the argument of destination.origin property's length of GPUQueue.writeTexture when its a sequence + // https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpuqueue-writetexture + assertThrows( + () => + device.queue.writeTexture( + { texture, origin: overSize }, + new Uint8Array([1 * 255, 1 * 255, 1 * 255, 1 * 255]), + {}, + size, + ), + TypeError, + msgIncludes, + ); + // NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of destination.origin property's length when its a sequence, but it is not implemented yet + + device.destroy(); + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); + async function checkIsWsl() { return Deno.build.os === "linux" && await hasMicrosoftProcVersion();