Skip to content

Commit

Permalink
[browser][bindings] Fix error with SharedArrayBuffer when used as a b…
Browse files Browse the repository at this point in the history
…acking 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 f817638.

* Address support for SharedArrayBuffer which fails under Firefox.  Without all the whitespace changes.
  • Loading branch information
kjpou1 authored Jan 8, 2021
1 parent 1b1ff80 commit f3c6cb0
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ internal SharedArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ow
/// <value>The size, in bytes, of the array.</value>
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -18,4 +19,4 @@
<!-- Part of the shared framework but not exposed. -->
<ProjectReference Include="..\src\System.Private.Runtime.InteropServices.JavaScript.csproj" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -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 };
}

}
}
14 changes: 10 additions & 4 deletions src/mono/wasm/runtime/binding_support.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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) {

Expand All @@ -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);
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit f3c6cb0

Please sign in to comment.