[browser][bindings] Fix error with SharedArrayBuffer when used as a backing view...
authorKenneth Pouncey <kjpou@pt.lu>
Fri, 8 Jan 2021 04:33:24 +0000 (05:33 +0100)
committerGitHub <noreply@github.com>
Fri, 8 Jan 2021 04:33:24 +0000 (05:33 +0100)
* 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.

src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/SharedArrayBufferTests.cs [new file with mode: 0644]
src/mono/wasm/runtime/binding_support.js

index 256313a..463c81c 100644 (file)
@@ -24,6 +24,21 @@ namespace System.Runtime.InteropServices.JavaScript
         public int ByteLength => (int)GetObjectProperty("byteLength");
 
         /// <summary>
+        /// Returns a new JavaScript Core SharedArrayBuffer whose contents are a copy of this SharedArrayBuffer's bytes.
+        /// </summary>
+        /// <returns>a new JavaScript Core SharedArrayBuffer</returns>
+        public SharedArrayBuffer Slice() => (SharedArrayBuffer)Invoke("slice");
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <returns>a new JavaScript Core SharedArrayBuffer</returns>
+        /// <param name="begin">Beginning index of copy zero based.</param>
+        public SharedArrayBuffer Slice(int begin) => (SharedArrayBuffer)Invoke("slice", begin);
+
+        /// <summary>
         /// 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.
index 444b70c..11dec89 100644 (file)
@@ -8,6 +8,7 @@
     <Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTests.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\DataViewTests.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\TypedArrayTests.cs" />
+    <Compile Include="System\Runtime\InteropServices\JavaScript\SharedArrayBufferTests.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\ArrayTests.cs" />
     <!-- <Compile Include="System\Runtime\InteropServices\JavaScript\MapTests.cs" /> -->
     <Compile Include="System\Runtime\InteropServices\JavaScript\MarshalTests.cs" />
@@ -18,4 +19,4 @@
     <!-- Part of the shared framework but not exposed. -->
     <ProjectReference Include="..\src\System.Private.Runtime.InteropServices.JavaScript.csproj" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
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 (file)
index 0000000..78bd384
--- /dev/null
@@ -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[]> 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<int> 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<SharedArrayBuffer> 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<SharedArrayBuffer> { buffer };
+        }
+
+    }
+}
index c16b6d7..b9de49a 100644 (file)
@@ -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.