Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"async-iteration",
"Atomics",
"decorators",
"immutable-arraybuffer",
"import-defer",
"joint-iteration",
"json-parse-with-source",
Expand Down
137 changes: 135 additions & 2 deletions Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ protected override void Initialize()
["byteLength"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
[KnownKeys.Constructor] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
["detached"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get detached", Detached, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["immutable"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get immutable", Immutable, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["maxByteLength"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get maxByteLength", MaxByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["resizable"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get resizable", Resizable, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["resize"] = new PropertyDescriptor(new ClrFunction(_engine, "resize", Resize, 1, lengthFlags), PropertyFlag.NonEnumerable),
["slice"] = new PropertyDescriptor(new ClrFunction(_engine, "slice", Slice, 2, lengthFlags), PropertyFlag.NonEnumerable),
["sliceToImmutable"] = new PropertyDescriptor(new ClrFunction(_engine, "sliceToImmutable", SliceToImmutable, 2, lengthFlags), PropertyFlag.NonEnumerable),
["transfer"] = new PropertyDescriptor(new ClrFunction(_engine, "transfer", Transfer, 0, lengthFlags), PropertyFlag.NonEnumerable),
["transferToFixedLength"] = new PropertyDescriptor(new ClrFunction(_engine, "transferToFixedLength", TransferToFixedLength, 0, lengthFlags), PropertyFlag.NonEnumerable),
["transferToImmutable"] = new PropertyDescriptor(new ClrFunction(_engine, "transferToImmutable", TransferToImmutable, 0, lengthFlags), PropertyFlag.NonEnumerable),
};
SetProperties(properties);

Expand All @@ -56,6 +59,20 @@ private JsValue Detached(JsValue thisObject, JsCallArguments arguments)
return o.IsDetachedBuffer;
}

/// <summary>
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-get-arraybuffer.prototype.immutable
/// </summary>
private JsValue Immutable(JsValue thisObject, JsCallArguments arguments)
{
var o = thisObject as JsArrayBuffer;
if (o is null || o.IsSharedArrayBuffer)
{
Throw.TypeError(_realm, "Method ArrayBuffer.prototype.immutable called on incompatible receiver " + thisObject);
}

return o.IsImmutableBuffer;
}

/// <summary>
/// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
/// </summary>
Expand Down Expand Up @@ -95,6 +112,7 @@ private JsValue Resizable(JsValue thisObject, JsCallArguments arguments)

/// <summary>
/// https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.resize
/// </summary>
private JsValue Resize(JsValue thisObject, JsCallArguments arguments)
{
Expand All @@ -104,6 +122,13 @@ private JsValue Resize(JsValue thisObject, JsCallArguments arguments)
Throw.TypeError(_realm, "Method ArrayBuffer.prototype.resize called on incompatible receiver " + thisObject);
}

// Step 2: Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]).
// This check must happen before reading newLength
if (o.IsFixedLengthArrayBuffer)
{
Throw.TypeError(_realm, "Cannot resize a fixed-length ArrayBuffer");
}

var newLength = arguments.At(0);
var newByteLength = TypeConverter.ToIndex(_realm, newLength);

Expand Down Expand Up @@ -204,9 +229,16 @@ private JsValue Slice(JsValue thisObject, JsCallArguments arguments)
Throw.TypeError(_realm);
}

// https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.slice
// If IsImmutableBuffer(new) is true, throw a TypeError exception.
if (bufferInstance.IsImmutableBuffer)
{
Throw.TypeError(_realm, "Cannot use an immutable ArrayBuffer as species constructor result");
}

// NOTE: Side-effects of the above steps may have detached O.

if (bufferInstance.IsDetachedBuffer)
if (o.IsDetachedBuffer)
{
Throw.TypeError(_realm);
}
Expand All @@ -233,6 +265,96 @@ private JsValue TransferToFixedLength(JsValue thisObject, JsCallArguments argume
return ArrayBufferCopyAndDetach(thisObject, arguments.At(0), PreserveResizability.FixedLength);
}

/// <summary>
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.transfertoimmutable
/// </summary>
private JsValue TransferToImmutable(JsValue thisObject, JsCallArguments arguments)
{
// 1. Let O be the this value.
// 2. Return ? ArrayBufferCopyAndDetach(O, newLength, immutable).
return ArrayBufferCopyAndDetach(thisObject, arguments.At(0), PreserveResizability.Immutable);
}

/// <summary>
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.slicetoimmutable
/// </summary>
private JsValue SliceToImmutable(JsValue thisObject, JsCallArguments arguments)
{
// 1. Let O be the this value.
var o = thisObject as JsArrayBuffer;
if (o is null || o.IsSharedArrayBuffer)
{
Throw.TypeError(_realm, "Method ArrayBuffer.prototype.sliceToImmutable called on incompatible receiver " + thisObject);
}

// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
// (already checked above)

// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
o.AssertNotDetached();

var start = arguments.At(0);
var end = arguments.At(1);

// 5. Let len be O.[[ArrayBufferByteLength]].
var len = o.ArrayBufferByteLength;

// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
var relativeStart = TypeConverter.ToIntegerOrInfinity(start);

// 7-8. Set first based on relativeStart
var first = relativeStart switch
{
double.NegativeInfinity => 0,
< 0 => (int) System.Math.Max(len + relativeStart, 0),
_ => (int) System.Math.Min(relativeStart, len)
};

// 9-10. Set relativeEnd based on end
double relativeEnd;
if (end.IsUndefined())
{
relativeEnd = len;
}
else
{
relativeEnd = TypeConverter.ToIntegerOrInfinity(end);
}

// 11-12. Set final based on relativeEnd
var final = relativeEnd switch
{
double.NegativeInfinity => 0,
< 0 => (int) System.Math.Max(len + relativeEnd, 0),
_ => (int) System.Math.Min(relativeEnd, len)
};

// 13. Let newLen be max(final - first, 0).
var newLen = (uint) System.Math.Max(final - first, 0);

// 14. Let new be ? AllocateArrayBuffer(%ArrayBuffer%, newLen).
var newBuffer = _engine.Realm.Intrinsics.ArrayBuffer.AllocateArrayBuffer(_engine.Realm.Intrinsics.ArrayBuffer, newLen);

// 15. If IsDetachedBuffer(O) is true, throw a TypeError exception.
o.AssertNotDetached();

// 16. Let fromBuf be O.[[ArrayBufferData]].
var fromBuf = o.ArrayBufferData!;

// 17. Let toBuf be new.[[ArrayBufferData]].
var toBuf = newBuffer.ArrayBufferData!;

// 18. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
System.Array.Copy(fromBuf, first, toBuf, 0, newLen);

// 19. Set new.[[ArrayBufferImmutable]] to true.
newBuffer._isImmutable = true;

// 20. Return new.
return newBuffer;
}

private JsValue ArrayBufferCopyAndDetach(JsValue o, JsValue newLength, PreserveResizability preserveResizability)
{
if (o is not JsArrayBuffer arrayBuffer || arrayBuffer.IsSharedArrayBuffer)
Expand All @@ -253,6 +375,10 @@ private JsValue ArrayBufferCopyAndDetach(JsValue o, JsValue newLength, PreserveR

arrayBuffer.AssertNotDetached();

// https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffercopyanddetach
// If IsImmutableBuffer(arrayBuffer) is true, throw a TypeError exception.
arrayBuffer.AssertNotImmutable();

uint? newMaxByteLength = null;
if (preserveResizability == PreserveResizability.PreserveResizability && arrayBuffer._arrayBufferMaxByteLength != null)
{
Expand All @@ -273,6 +399,12 @@ private JsValue ArrayBufferCopyAndDetach(JsValue o, JsValue newLength, PreserveR

// NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as a zero-copy move or a realloc.

// If preserveResizability is immutable, set new buffer's immutable flag
if (preserveResizability == PreserveResizability.Immutable)
{
newBuffer._isImmutable = true;
}

arrayBuffer.DetachArrayBuffer();

return newBuffer;
Expand All @@ -281,6 +413,7 @@ private JsValue ArrayBufferCopyAndDetach(JsValue o, JsValue newLength, PreserveR
private enum PreserveResizability
{
PreserveResizability,
FixedLength
FixedLength,
Immutable
}
}
11 changes: 10 additions & 1 deletion Jint/Native/DataView/DataViewPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ private static DataViewWithBufferWitnessRecord MakeDataViewWithBufferWitnessReco

/// <summary>
/// https://tc39.es/ecma262/#sec-setviewvalue
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-setviewvalue
/// </summary>
private JsValue SetViewValue(
JsValue view,
Expand All @@ -365,6 +366,15 @@ private JsValue SetViewValue(
Throw.TypeError(_realm, "Method called on incompatible receiver " + view);
}

var buffer = dataView._viewedArrayBuffer!;

// https://tc39.es/proposal-immutable-arraybuffer/#sec-setviewvalue
// Check immutability BEFORE processing arguments
if (buffer.IsImmutableBuffer)
{
Throw.TypeError(_realm, "Cannot modify an immutable ArrayBuffer");
}

var getIndex = TypeConverter.ToIndex(_realm, requestIndex);

TypedArrayValue numberValue;
Expand All @@ -378,7 +388,6 @@ private JsValue SetViewValue(
}

var isLittleEndianBoolean = TypeConverter.ToBoolean(isLittleEndian);
var buffer = dataView._viewedArrayBuffer!;
buffer.AssertNotDetached();

var viewOffset = dataView._byteOffset;
Expand Down
17 changes: 17 additions & 0 deletions Jint/Native/JsArrayBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class JsArrayBuffer : ObjectInstance

internal byte[]? _arrayBufferData;
internal readonly int? _arrayBufferMaxByteLength;
internal bool _isImmutable;

internal readonly JsValue _arrayBufferDetachKey = Undefined;

Expand Down Expand Up @@ -52,6 +53,11 @@ internal static byte[] CreateByteDataBlock(Realm realm, ulong byteLength)

internal virtual bool IsSharedArrayBuffer => false;

/// <summary>
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-isimmutablebuffer
/// </summary>
internal bool IsImmutableBuffer => _isImmutable;

/// <summary>
/// https://tc39.es/ecma262/#sec-detacharraybuffer
/// </summary>
Expand Down Expand Up @@ -355,4 +361,15 @@ internal void AssertNotDetached()
Throw.TypeError(_engine.Realm, "ArrayBuffer has been detached");
}
}

/// <summary>
/// https://tc39.es/proposal-immutable-arraybuffer/#sec-isimmutablebuffer
/// </summary>
internal void AssertNotImmutable()
{
if (IsImmutableBuffer)
{
Throw.TypeError(_engine.Realm, "Cannot modify an immutable ArrayBuffer");
}
}
}
4 changes: 4 additions & 0 deletions Jint/Native/JsTypedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ internal void DoIntegerIndexedElementSet(int index, TypedArrayValue numValue)
var elementType = _arrayElementType;
var elementSize = elementType.GetElementSize();
var indexedPosition = index * elementSize + offset;

// https://tc39.es/proposal-immutable-arraybuffer/#sec-integerindexedelementset
_viewedArrayBuffer.AssertNotImmutable();

_viewedArrayBuffer.SetValueInBuffer(indexedPosition, elementType, numValue, true, ArrayBufferOrder.Unordered);
}

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ and many more.

- ✔ `Error.isError`
- ✔ Explicit Resource Management (`using` and `await using`)
- ✔ Immutable Arraybuffers
- ✔ Iterator Sequencing
- ✔ `Math.sumPrecise`
- ✔ `ShadowRealm`
Expand Down