From f3c6cb060954f928915bc83343838bbcdacaebf1 Mon Sep 17 00:00:00 2001 From: Kenneth Pouncey Date: Fri, 8 Jan 2021 05:33:24 +0100 Subject: [PATCH] [browser][bindings] Fix error with SharedArrayBuffer when used as a backing view. (#46625) * Add code to check for a backing ArrayBuffer as well as a backing SharedBuffer. - Resolves the error `"Object '...' is not a typed array"` * Add other Slice methods per documentation of JavaScript docs - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/slice * Add tests for SharedArrayBuffer. * Address review comment about unused usings * Address review comments. * Address review comments for unnecessary mod in commit * Add back whitespace * Modify comment description as per review comment * Address support for SharedArrayBuffer which fails under Firefox. * Revert "Address support for SharedArrayBuffer which fails under Firefox." This reverts commit f817638f897b2c558c49a80ac47f044683c58911. * Address support for SharedArrayBuffer which fails under Firefox. Without all the whitespace changes. --- .../JavaScript/SharedArrayBuffer.cs | 15 ++ ...Runtime.InteropServices.JavaScript.Tests.csproj | 3 +- .../JavaScript/SharedArrayBufferTests.cs | 156 +++++++++++++++++++++ src/mono/wasm/runtime/binding_support.js | 14 +- 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/SharedArrayBufferTests.cs diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs index 256313a..463c81c 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs @@ -24,6 +24,21 @@ namespace System.Runtime.InteropServices.JavaScript public int ByteLength => (int)GetObjectProperty("byteLength"); /// + /// Returns a new JavaScript Core SharedArrayBuffer whose contents are a copy of this SharedArrayBuffer's bytes. + /// + /// a new JavaScript Core SharedArrayBuffer + public SharedArrayBuffer Slice() => (SharedArrayBuffer)Invoke("slice"); + + /// + /// Returns a new JavaScript Core SharedArrayBuffer whose contents are a copy of this SharedArrayBuffer's bytes from begin, + /// inclusive, through to the end of the sequence, exclusive. If begin is negative, it refers to an index from the end + /// of the array, as opposed to from the beginning. + /// + /// a new JavaScript Core SharedArrayBuffer + /// Beginning index of copy zero based. + public SharedArrayBuffer Slice(int begin) => (SharedArrayBuffer)Invoke("slice", begin); + + /// /// Returns a new JavaScript Core SharedArrayBuffer whose contents are a copy of this SharedArrayBuffer's bytes from begin, /// inclusive, up to end, exclusive. If either begin or end is negative, it refers to an index from the end /// of the array, as opposed to from the beginning. diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj index 444b70c..11dec89 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj @@ -8,6 +8,7 @@ + @@ -18,4 +19,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/SharedArrayBufferTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/SharedArrayBufferTests.cs new file mode 100644 index 0000000..78bd384 --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/SharedArrayBufferTests.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public static class SharedArrayBufferTests + { + private static Function _objectPrototype; + + public static IEnumerable Object_Prototype() + { + _objectPrototype ??= new Function("return Object.prototype.toString;"); + yield return new object[] { _objectPrototype.Call() }; + } + + [Theory] + [MemberData(nameof(Object_Prototype))] + public static void SharedArrayBuffer_NonZeroLength(Function objectPrototype) + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal("[object SharedArrayBuffer]", objectPrototype.Call(d)); + Assert.Equal(50, d.ByteLength); + } + + [Fact] + public static void SharedArrayBufferSlice() + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal(50, d.Slice().ByteLength); + } + + [Fact] + public static void SharedArrayBuffer_Slice_BeginEndForFullArray() + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal(50, d.Slice(0, 50).ByteLength); + } + + [Fact] + public static void SharedArrayBuffer_Slice_BeginZero() + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal(50, d.Slice(0).ByteLength); + } + + [Fact] + public static void SharedArrayBuffer_Slice_BeginNegative() + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal(3, d.Slice(-3).ByteLength); + } + + [Fact] + public static void SharedArrayBuffer_Slice_BeginEndSubset() + { + SharedArrayBuffer d = new SharedArrayBuffer(50); + Assert.Equal(3, d.Slice(1, 4).ByteLength); + } + + [Fact] + public static void SharedArrayBufferSliceAndDice() + { + // create a SharedArrayBuffer with a size in bytes + SharedArrayBuffer buffer = new SharedArrayBuffer(16); + Int32Array int32View = new Int32Array(buffer); // create view + // produces Int32Array [0, 0, 0, 0] + + int32View[1] = 42; + + Assert.Equal(4, int32View.Length); + Assert.Equal(42, int32View[1]); + + Int32Array sliced = new Int32Array(buffer.Slice(4,12)); + // expected output: Int32Array [42, 0] + + Assert.Equal(2, sliced.Length); + Assert.Equal(42, sliced[0]); + Assert.Equal(0, sliced[1]); + } + + [Fact] + public static void SharedArrayBufferSliceAndDiceAndUseThroughSpan() + { + // create a SharedArrayBuffer with a size in bytes + SharedArrayBuffer buffer = new SharedArrayBuffer(16); + Int32Array int32View = new Int32Array(buffer); // create view + // produces Int32Array [0, 0, 0, 0] + + int32View[1] = 42; + + Assert.Equal(4, int32View.Length); + Assert.Equal(42, int32View[1]); + + Int32Array sliced = new Int32Array(buffer.Slice(4,12)); + // expected output: Int32Array [42, 0] + + Span nativeArray = sliced; + + int sum = 0; + for (int i = 0; i < nativeArray.Length; i++) + { + sum += nativeArray[i]; + } + + Assert.Equal(42, sum); + } + + [Theory] + [MemberData(nameof(GetTestData), 16)] + public static void SharedArrayBufferSliceAndDice3_Subset(SharedArrayBuffer buffer) + { + Int32Array sliced = new Int32Array(buffer.Slice(4,12)); + + Assert.Equal(2, sliced.Length); + Assert.Equal(42, sliced[0]); + Assert.Equal(12, sliced[1]); + } + + [Theory] + [MemberData(nameof(GetTestData), 16)] + public static void SharedArrayBufferSliceAndDice3_SubsetFromTheBack(SharedArrayBuffer buffer) + { + Int32Array sliced = new Int32Array(buffer.Slice(-4)); + + Assert.Equal(1, sliced.Length); + Assert.Equal(13, sliced[0]); + } + + [Theory] + [MemberData(nameof(GetTestData), 16)] + public static void SharedArrayBufferSliceAndDice3_SubsetFromTheBackWithEnd(SharedArrayBuffer buffer) + { + Int32Array sliced = new Int32Array(buffer.Slice(-12, -4)); + + Assert.Equal(2, sliced.Length); + Assert.Equal(42, sliced[0]); + Assert.Equal(12, sliced[1]); + } + + private static TheoryData GetTestData(int length) + { + // create a SharedArrayBuffer with a size in bytes + SharedArrayBuffer buffer = new SharedArrayBuffer(length); + Int32Array int32View = new Int32Array(buffer); // create view + for (int i = 0; i < int32View.Length; i ++) + int32View[i] = i + 10; + + int32View[1] = 42; + return new TheoryData { buffer }; + } + + } +} diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index c16b6d7..b9de49a 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -14,6 +14,7 @@ var BindingSupportLib = { mono_bindings_init: function (binding_asm) { this.BINDING_ASM = binding_asm; }, + mono_sab_supported: typeof SharedArrayBuffer !== "undefined", export_functions: function (module) { module ["mono_bindings_init"] = BINDING.mono_bindings_init.bind(BINDING); @@ -39,7 +40,7 @@ var BindingSupportLib = { DataView.prototype[Symbol.for("wasm type")] = 3; Function.prototype[Symbol.for("wasm type")] = 4; Map.prototype[Symbol.for("wasm type")] = 5; - if (typeof SharedArrayBuffer !== "undefined") + if (BINDING.mono_sab_supported) SharedArrayBuffer.prototype[Symbol.for("wasm type")] = 6; Int8Array.prototype[Symbol.for("wasm type")] = 10; Uint8Array.prototype[Symbol.for("wasm type")] = 11; @@ -486,6 +487,11 @@ var BindingSupportLib = { return this.extract_mono_obj (js_obj); } }, + has_backing_array_buffer: function (js_obj) { + return BINDING.mono_sab_supported + ? js_obj.buffer instanceof ArrayBuffer || js_obj.buffer instanceof SharedArrayBuffer + : js_obj.buffer instanceof ArrayBuffer; + }, js_typed_array_to_array : function (js_obj) { @@ -497,7 +503,7 @@ var BindingSupportLib = { // you need to use a view. A view provides a context — that is, a data type, starting offset, // and number of elements — that turns the data into an actual typed array. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays - if (!!(js_obj.buffer instanceof ArrayBuffer && js_obj.BYTES_PER_ELEMENT)) + if (!!(this.has_backing_array_buffer(js_obj) && js_obj.BYTES_PER_ELEMENT)) { var arrayType = js_obj[Symbol.for("wasm type")]; var heapBytes = this.js_typedarray_to_heap(js_obj); @@ -523,7 +529,7 @@ var BindingSupportLib = { // you need to use a view. A view provides a context — that is, a data type, starting offset, // and number of elements — that turns the data into an actual typed array. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays - if (!!(typed_array.buffer instanceof ArrayBuffer && typed_array.BYTES_PER_ELEMENT)) + if (!!(this.has_backing_array_buffer(typed_array) && typed_array.BYTES_PER_ELEMENT)) { // Some sanity checks of what is being asked of us // lets play it safe and throw an error here instead of assuming to much. @@ -566,7 +572,7 @@ var BindingSupportLib = { // you need to use a view. A view provides a context — that is, a data type, starting offset, // and number of elements — that turns the data into an actual typed array. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays - if (!!(typed_array.buffer instanceof ArrayBuffer && typed_array.BYTES_PER_ELEMENT)) + if (!!(this.has_backing_array_buffer(typed_array) && typed_array.BYTES_PER_ELEMENT)) { // Some sanity checks of what is being asked of us // lets play it safe and throw an error here instead of assuming to much. -- 2.7.4