diff --git a/Jint.Tests.PublicInterface/ArrayBufferTests.cs b/Jint.Tests.PublicInterface/ArrayBufferTests.cs
new file mode 100644
index 0000000000..d3da8010e3
--- /dev/null
+++ b/Jint.Tests.PublicInterface/ArrayBufferTests.cs
@@ -0,0 +1,73 @@
+using Jint.Native;
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime;
+
+public class ArrayBufferTests
+{
+ [Fact]
+ public void CanConvertByteArrayToArrayBuffer()
+ {
+ var engine = new Engine(o => o.AddObjectConverter(new BytesToArrayBufferConverter()));
+
+ var bytes = new byte[] { 17 };
+ engine.SetValue("buffer", bytes);
+
+ engine.Evaluate("var a = new Uint8Array(buffer)");
+
+ var typedArray = (JsTypedArray) engine.GetValue("a");
+ Assert.Equal((uint) 1, typedArray.Length);
+ Assert.Equal(17, typedArray[0]);
+ Assert.Equal(JsValue.Undefined, typedArray[1]);
+
+ Assert.Equal(1, engine.Evaluate("a.length"));
+ Assert.Equal(17, engine.Evaluate("a[0]"));
+ Assert.Equal(JsValue.Undefined, engine.Evaluate("a[1]"));
+
+ bytes[0] = 42;
+ Assert.Equal(42, engine.Evaluate("a[0]"));
+ }
+
+ [Fact]
+ public void CanCreateArrayBufferAndTypedArrayUsingCode()
+ {
+ var engine = new Engine();
+
+ var jsArrayBuffer = engine.Intrinsics.ArrayBuffer.Construct(1);
+ var jsTypedArray = engine.Intrinsics.Uint8Array.Construct(jsArrayBuffer);
+ jsTypedArray[0] = 17;
+
+ engine.SetValue("buffer", jsArrayBuffer);
+ engine.SetValue("a", jsTypedArray);
+
+ var typedArray = (JsTypedArray) engine.GetValue("a");
+ Assert.Equal((uint) 1, typedArray.Length);
+ Assert.Equal(17, typedArray[0]);
+ Assert.Equal(JsValue.Undefined, typedArray[1]);
+
+ Assert.Equal(1, engine.Evaluate("a.length"));
+ Assert.Equal(17, engine.Evaluate("a[0]"));
+ Assert.Equal(JsValue.Undefined, engine.Evaluate("a[1]"));
+ }
+
+ ///
+ /// Converts a byte array to an ArrayBuffer.
+ ///
+ private sealed class BytesToArrayBufferConverter : IObjectConverter
+ {
+ public bool TryConvert(Engine engine, object value, out JsValue result)
+ {
+ if (value is byte[] bytes)
+ {
+ var buffer = engine.Intrinsics.ArrayBuffer.Construct(bytes);
+ result = buffer;
+ return true;
+ }
+
+ // TODO: provide similar implementation for Memory that will affect how ArrayBufferInstance works (offset)
+
+ result = JsValue.Null;
+ return false;
+ }
+ }
+}
diff --git a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
index b978800ff6..439cb9af9c 100644
--- a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
+++ b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
@@ -11,7 +11,7 @@ namespace Jint.Native.ArrayBuffer;
///
/// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-constructor
///
-internal sealed class ArrayBufferConstructor : Constructor
+public sealed class ArrayBufferConstructor : Constructor
{
private static readonly JsString _functionName = new("ArrayBuffer");
@@ -47,20 +47,19 @@ protected override void Initialize()
}
///
- /// https://tc39.es/ecma262/#sec-arraybuffer.isview
+ /// Constructs a new JsArrayBuffer instance and takes ownership of the given byte array and uses it as backing store.
///
- private static JsValue IsView(JsValue thisObject, JsValue[] arguments)
+ public JsArrayBuffer Construct(byte[] data)
{
- var arg = arguments.At(0);
- return arg is JsDataView or JsTypedArray;
+ return CreateJsArrayBuffer(this, data, byteLength: (ulong) data.Length, maxByteLength: null);
}
///
- /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
+ /// Constructs a new JsArrayBuffer with given byte length and optional max byte length.
///
- private static JsValue Species(JsValue thisObject, JsValue[] arguments)
+ public JsArrayBuffer Construct(ulong byteLength, uint? maxByteLength = null)
{
- return thisObject;
+ return AllocateArrayBuffer(this, byteLength, maxByteLength);
}
public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
@@ -78,6 +77,23 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
return AllocateArrayBuffer(newTarget, byteLength, requestedMaxByteLength);
}
+ ///
+ /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
+ ///
+ private static JsValue Species(JsValue thisObject, JsValue[] arguments)
+ {
+ return thisObject;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-arraybuffer.isview
+ ///
+ private static JsValue IsView(JsValue thisObject, JsValue[] arguments)
+ {
+ var arg = arguments.At(0);
+ return arg is JsDataView or JsTypedArray;
+ }
+
///
/// https://tc39.es/ecma262/#sec-allocatearraybuffer
///
@@ -90,15 +106,27 @@ internal JsArrayBuffer AllocateArrayBuffer(JsValue constructor, ulong byteLength
ExceptionHelper.ThrowRangeError(_realm);
}
+ return CreateJsArrayBuffer(constructor, block: null, byteLength, maxByteLength);
+ }
+
+ private JsArrayBuffer CreateJsArrayBuffer(JsValue constructor, byte[]? block, ulong byteLength, uint? maxByteLength)
+ {
var obj = OrdinaryCreateFromConstructor(
constructor,
static intrinsics => intrinsics.ArrayBuffer.PrototypeObject,
- static (engine, _, state) => new JsArrayBuffer(engine, state!.Item1),
- new Tuple(maxByteLength));
+ static (engine, _, state) =>
+ {
+ var buffer = new JsArrayBuffer(engine, [], state.MaxByteLength)
+ {
+ _arrayBufferData = state.Block ?? (state.ByteLength > 0 ? JsArrayBuffer.CreateByteDataBlock(engine.Realm, state.ByteLength) : []),
+ };
- var block = byteLength > 0 ? JsArrayBuffer.CreateByteDataBlock(_realm, byteLength) : System.Array.Empty();
- obj._arrayBufferData = block;
+ return buffer;
+ },
+ new ConstructState(block, byteLength, maxByteLength));
return obj;
}
+
+ private readonly record struct ConstructState(byte[]? Block, ulong ByteLength, uint? MaxByteLength);
}
diff --git a/Jint/Native/JsArrayBuffer.cs b/Jint/Native/JsArrayBuffer.cs
index e28db995e4..00cc93ee20 100644
--- a/Jint/Native/JsArrayBuffer.cs
+++ b/Jint/Native/JsArrayBuffer.cs
@@ -20,6 +20,7 @@ public class JsArrayBuffer : ObjectInstance
internal JsArrayBuffer(
Engine engine,
+ byte[] data,
uint? arrayBufferMaxByteLength = null) : base(engine)
{
if (arrayBufferMaxByteLength is > int.MaxValue)
@@ -27,6 +28,7 @@ internal JsArrayBuffer(
ExceptionHelper.ThrowRangeError(engine.Realm, "arrayBufferMaxByteLength cannot be larger than int32.MaxValue");
}
+ _arrayBufferData = data;
_arrayBufferMaxByteLength = (int?) arrayBufferMaxByteLength;
}
@@ -104,6 +106,11 @@ internal TypedArrayValue GetValueFromBuffer(
///
internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian)
{
+ if (type is TypedArrayElementType.Uint8 or TypedArrayElementType.Uint8C)
+ {
+ return new TypedArrayValue(Types.Number, _arrayBufferData![byteIndex], default);
+ }
+
var elementSize = type.GetElementSize();
var rawBytes = _arrayBufferData!;
@@ -176,8 +183,6 @@ internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteI
TypedArrayValue? arrayValue = type switch
{
TypedArrayElementType.Int8 => (sbyte) rawBytes[byteIndex],
- TypedArrayElementType.Uint8 => rawBytes[byteIndex],
- TypedArrayElementType.Uint8C =>rawBytes[byteIndex],
TypedArrayElementType.Int16 => isLittleEndian
? (short) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
: (short) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)),
@@ -212,10 +217,21 @@ internal void SetValueInBuffer(
ArrayBufferOrder order,
bool? isLittleEndian = null)
{
+ if (type is TypedArrayElementType.Uint8)
+ {
+ var doubleValue = value.DoubleValue;
+ var intValue = double.IsNaN(doubleValue) || doubleValue == 0 || double.IsInfinity(doubleValue)
+ ? 0
+ : (long) doubleValue;
+
+ _arrayBufferData![byteIndex] = (byte) intValue;
+ return;
+ }
+
var block = _arrayBufferData!;
// If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
var rawBytes = NumericToRawBytes(type, value, isLittleEndian ?? BitConverter.IsLittleEndian);
- System.Array.Copy(rawBytes, 0, block, byteIndex, type.GetElementSize());
+ System.Array.Copy(rawBytes, 0, block, byteIndex, type.GetElementSize());
}
private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue value, bool isLittleEndian)
@@ -266,7 +282,7 @@ private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue val
rawBytes[0] = (byte) intValue;
break;
case TypedArrayElementType.Uint8C:
- rawBytes[0] = (byte) TypeConverter.ToUint8Clamp(value.DoubleValue);
+ rawBytes[0] = TypeConverter.ToUint8Clamp(value.DoubleValue);
break;
case TypedArrayElementType.Int16:
#if !NETSTANDARD2_1
diff --git a/Jint/Native/JsSharedArrayBuffer.cs b/Jint/Native/JsSharedArrayBuffer.cs
index 418aa26512..787557a578 100644
--- a/Jint/Native/JsSharedArrayBuffer.cs
+++ b/Jint/Native/JsSharedArrayBuffer.cs
@@ -11,8 +11,9 @@ internal sealed class JsSharedArrayBuffer : JsArrayBuffer
internal JsSharedArrayBuffer(
Engine engine,
+ byte[] data,
uint? arrayBufferMaxByteLength,
- uint arrayBufferByteLengthData) : base(engine, arrayBufferMaxByteLength)
+ uint arrayBufferByteLengthData) : base(engine, data, arrayBufferMaxByteLength)
{
if (arrayBufferByteLengthData > int.MaxValue)
{
diff --git a/Jint/Native/JsTypedArray.cs b/Jint/Native/JsTypedArray.cs
index 4dabe73d1d..ceb46ff93b 100644
--- a/Jint/Native/JsTypedArray.cs
+++ b/Jint/Native/JsTypedArray.cs
@@ -28,10 +28,7 @@ internal JsTypedArray(
uint length) : base(engine)
{
_intrinsics = intrinsics;
- _viewedArrayBuffer = new JsArrayBuffer(engine)
- {
- _arrayBufferData = System.Array.Empty()
- };
+ _viewedArrayBuffer = new JsArrayBuffer(engine, []);
_arrayElementType = type;
_contentType = type != TypedArrayElementType.BigInt64 && type != TypedArrayElementType.BigUint64
diff --git a/Jint/Native/SharedArrayBuffer/SharedArrayBufferConstructor.cs b/Jint/Native/SharedArrayBuffer/SharedArrayBufferConstructor.cs
index b075809481..9885ce4182 100644
--- a/Jint/Native/SharedArrayBuffer/SharedArrayBufferConstructor.cs
+++ b/Jint/Native/SharedArrayBuffer/SharedArrayBufferConstructor.cs
@@ -94,16 +94,23 @@ private JsSharedArrayBuffer AllocateSharedArrayBuffer(JsValue constructor, uint
ExceptionHelper.ThrowRangeError(_realm);
}
+ var allocLength = maxByteLength.GetValueOrDefault(byteLength);
+
var obj = OrdinaryCreateFromConstructor(
constructor,
static intrinsics => intrinsics.SharedArrayBuffer.PrototypeObject,
- static (engine, _, state) => new JsSharedArrayBuffer(engine, state!.Item1, state.Item2),
- new Tuple(maxByteLength, byteLength));
-
- var allocLength = maxByteLength.GetValueOrDefault(byteLength);
- var block = JsSharedArrayBuffer.CreateSharedByteDataBlock(_realm, allocLength);
- obj._arrayBufferData = block;
+ static (engine, _, state) =>
+ {
+ var buffer = new JsSharedArrayBuffer(engine, [], state.MaxByteLength, state.ArrayBufferByteLengthData)
+ {
+ _arrayBufferData = state.Block ?? (state.ByteLength > 0 ? JsSharedArrayBuffer.CreateSharedByteDataBlock(engine.Realm, state.ByteLength) : []),
+ };
+ return buffer;
+ },
+ new ConstructState(Block: null, allocLength, maxByteLength, byteLength));
return obj;
}
+
+ private readonly record struct ConstructState(byte[]? Block, uint ByteLength, uint? MaxByteLength, uint ArrayBufferByteLengthData);
}
diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs
index c41eb6ebd2..04dfd71860 100644
--- a/Jint/Native/TypedArray/TypedArrayConstructor.cs
+++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs
@@ -42,6 +42,13 @@ protected override void Initialize()
SetProperties(properties);
}
+ public JsTypedArray Construct(JsArrayBuffer buffer, int? byteOffset = null, int? length = null)
+ {
+ var o = AllocateTypedArray(this);
+ InitializeTypedArrayFromArrayBuffer(o, buffer, byteOffset, length);
+ return o;
+ }
+
public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
{
if (newTarget.IsUndefined())
@@ -49,41 +56,25 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
ExceptionHelper.ThrowTypeError(_realm);
}
- Func proto = _arrayElementType switch
- {
- TypedArrayElementType.Float16 => static intrinsics => intrinsics.Float16Array.PrototypeObject,
- TypedArrayElementType.Float32 => static intrinsics => intrinsics.Float32Array.PrototypeObject,
- TypedArrayElementType.Float64 => static intrinsics => intrinsics.Float64Array.PrototypeObject,
- TypedArrayElementType.Int8 => static intrinsics => intrinsics.Int8Array.PrototypeObject,
- TypedArrayElementType.Int16 => static intrinsics => intrinsics.Int16Array.PrototypeObject,
- TypedArrayElementType.Int32 => static intrinsics => intrinsics.Int32Array.PrototypeObject,
- TypedArrayElementType.BigInt64 => static intrinsics => intrinsics.BigInt64Array.PrototypeObject,
- TypedArrayElementType.Uint8 => static intrinsics => intrinsics.Uint8Array.PrototypeObject,
- TypedArrayElementType.Uint8C => static intrinsics => intrinsics.Uint8ClampedArray.PrototypeObject,
- TypedArrayElementType.Uint16 => static intrinsics => intrinsics.Uint16Array.PrototypeObject,
- TypedArrayElementType.Uint32 => static intrinsics => intrinsics.Uint32Array.PrototypeObject,
- TypedArrayElementType.BigUint64 => static intrinsics => intrinsics.BigUint64Array.PrototypeObject,
- _ => null!
- };
var numberOfArgs = arguments.Length;
if (numberOfArgs == 0)
{
- return AllocateTypedArray(newTarget, proto, 0);
+ return AllocateTypedArray(newTarget, 0);
}
var firstArgument = arguments[0];
if (firstArgument.IsObject())
{
- var o = AllocateTypedArray(newTarget, proto);
+ var o = AllocateTypedArray(newTarget);
if (firstArgument is JsTypedArray typedArrayInstance)
{
InitializeTypedArrayFromTypedArray(o, typedArrayInstance);
}
else if (firstArgument is JsArrayBuffer arrayBuffer)
{
- var byteOffset = numberOfArgs > 1 ? arguments[1] : Undefined;
- var length = numberOfArgs > 2 ? arguments[2] : Undefined;
+ int? byteOffset = !arguments.At(1).IsUndefined() ? (int) TypeConverter.ToIndex(_realm, arguments[1]) : null;
+ int? length = !arguments.At(2).IsUndefined() ? (int) TypeConverter.ToIndex(_realm, arguments[2]) : null;
InitializeTypedArrayFromArrayBuffer(o, arrayBuffer, byteOffset, length);
}
else
@@ -104,7 +95,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
}
var elementLength = TypeConverter.ToIndex(_realm, firstArgument);
- return AllocateTypedArray(newTarget, proto, elementLength);
+ return AllocateTypedArray(newTarget, elementLength);
}
///
@@ -185,29 +176,25 @@ private void InitializeTypedArrayFromTypedArray(JsTypedArray o, JsTypedArray src
private void InitializeTypedArrayFromArrayBuffer(
JsTypedArray o,
JsArrayBuffer buffer,
- JsValue byteOffset,
- JsValue length)
+ int? byteOffset,
+ int? length)
{
var elementSize = o._arrayElementType.GetElementSize();
- var offset = (int) TypeConverter.ToIndex(_realm, byteOffset);
+ var offset = byteOffset ?? 0;
if (offset % elementSize != 0)
{
ExceptionHelper.ThrowRangeError(_realm, "Invalid offset");
}
int newByteLength;
- var newLength = 0;
- if (!length.IsUndefined())
- {
- newLength = (int) TypeConverter.ToIndex(_realm, length);
- }
+ var newLength = length ?? 0;
var bufferIsFixedLength = buffer.IsFixedLengthArrayBuffer;
buffer.AssertNotDetached();
var bufferByteLength = IntrinsicTypedArrayPrototype.ArrayBufferByteLength(buffer, ArrayBufferOrder.SeqCst);
- if (length.IsUndefined() && !bufferIsFixedLength)
+ if (length == null && !bufferIsFixedLength)
{
if (offset > bufferByteLength)
{
@@ -219,7 +206,7 @@ private void InitializeTypedArrayFromArrayBuffer(
}
else
{
- if (length.IsUndefined())
+ if (length == null)
{
if (bufferByteLength % elementSize != 0)
{
@@ -276,8 +263,25 @@ private static void InitializeTypedArrayFromArrayLike(JsTypedArray o, ObjectInst
///
/// https://tc39.es/ecma262/#sec-allocatetypedarray
///
- private JsTypedArray AllocateTypedArray(JsValue newTarget, Func defaultProto, uint length = 0)
+ private JsTypedArray AllocateTypedArray(JsValue newTarget, uint length = 0)
{
+ Func defaultProto = _arrayElementType switch
+ {
+ TypedArrayElementType.Float16 => static intrinsics => intrinsics.Float16Array.PrototypeObject,
+ TypedArrayElementType.Float32 => static intrinsics => intrinsics.Float32Array.PrototypeObject,
+ TypedArrayElementType.Float64 => static intrinsics => intrinsics.Float64Array.PrototypeObject,
+ TypedArrayElementType.Int8 => static intrinsics => intrinsics.Int8Array.PrototypeObject,
+ TypedArrayElementType.Int16 => static intrinsics => intrinsics.Int16Array.PrototypeObject,
+ TypedArrayElementType.Int32 => static intrinsics => intrinsics.Int32Array.PrototypeObject,
+ TypedArrayElementType.BigInt64 => static intrinsics => intrinsics.BigInt64Array.PrototypeObject,
+ TypedArrayElementType.Uint8 => static intrinsics => intrinsics.Uint8Array.PrototypeObject,
+ TypedArrayElementType.Uint8C => static intrinsics => intrinsics.Uint8ClampedArray.PrototypeObject,
+ TypedArrayElementType.Uint16 => static intrinsics => intrinsics.Uint16Array.PrototypeObject,
+ TypedArrayElementType.Uint32 => static intrinsics => intrinsics.Uint32Array.PrototypeObject,
+ TypedArrayElementType.BigUint64 => static intrinsics => intrinsics.BigUint64Array.PrototypeObject,
+ _ => null!
+ };
+
var proto = GetPrototypeFromConstructor(newTarget, defaultProto);
var realm = GetFunctionRealm(newTarget);
var obj = new JsTypedArray(_engine, realm.Intrinsics, _arrayElementType, length)
diff --git a/Jint/Runtime/Intrinsics.cs b/Jint/Runtime/Intrinsics.cs
index f4f39c317d..84e8d36d54 100644
--- a/Jint/Runtime/Intrinsics.cs
+++ b/Jint/Runtime/Intrinsics.cs
@@ -141,7 +141,7 @@ internal Intrinsics(Engine engine, Realm realm)
internal DataViewConstructor DataView =>
_dataView ??= new DataViewConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
- internal ArrayBufferConstructor ArrayBuffer =>
+ public ArrayBufferConstructor ArrayBuffer =>
_arrayBufferConstructor ??= new ArrayBufferConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
internal SharedArrayBufferConstructor SharedArrayBuffer =>