using System.Globalization; using System.Runtime.CompilerServices; using Jint.Native.ArrayBuffer; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.TypedArray; using Jint.Runtime; using Jint.Runtime.Descriptors; namespace Jint.Native; public sealed class JsTypedArray : ObjectInstance { internal const uint LengthAuto = uint.MaxValue; internal readonly TypedArrayContentType _contentType; internal readonly TypedArrayElementType _arrayElementType; internal JsArrayBuffer _viewedArrayBuffer; internal uint _byteLength; internal int _byteOffset; private readonly Intrinsics _intrinsics; internal uint _arrayLength; internal JsTypedArray( Engine engine, Intrinsics intrinsics, TypedArrayElementType type, uint length) : base(engine) { _intrinsics = intrinsics; _viewedArrayBuffer = new JsArrayBuffer(engine, []); _arrayElementType = type; _contentType = type != TypedArrayElementType.BigInt64 && type != TypedArrayElementType.BigUint64 ? TypedArrayContentType.Number : TypedArrayContentType.BigInt; _arrayLength = length; } public JsValue this[int index] { get => IntegerIndexedElementGet(index); set => IntegerIndexedElementSet(index, value); } public JsValue this[uint index] { get => IntegerIndexedElementGet(index); set => IntegerIndexedElementSet(index, value); } public uint Length => GetLength(); internal override uint GetLength() { var record = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered); return record.IsTypedArrayOutOfBounds ? 0 : record.TypedArrayLength; } public override bool PreventExtensions() { if (!IsTypedArrayFixedLength) { return false; } return base.PreventExtensions(); } /// /// https://tc39.es/ecma262/#sec-istypedarrayfixedlength /// private bool IsTypedArrayFixedLength { get { if (_arrayLength == LengthAuto) { return false; } var buffer = _viewedArrayBuffer; if (!buffer.IsFixedLengthArrayBuffer && !buffer.IsSharedArrayBuffer) { return false; } return true; } } internal override bool IsArrayLike => true; internal override bool IsIntegerIndexedArray => true; /// /// https://tc39.es/ecma262/#sec-allocatetypedarraybuffer /// internal void AllocateTypedArrayBuffer(ulong len) { var elementSize = _arrayElementType.GetElementSize(); var byteLength = elementSize * len; var data = _intrinsics.ArrayBuffer.AllocateArrayBuffer(_intrinsics.ArrayBuffer, byteLength); _byteLength = (uint) byteLength; _arrayLength = (uint) len; _viewedArrayBuffer = data; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool HasProperty(long numericIndex) { return IsValidIntegerIndex(numericIndex); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p /// public override bool HasProperty(JsValue property) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { return IsValidIntegerIndex(numericIndex.Value); } return base.HasProperty(property); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p /// public override PropertyDescriptor GetOwnProperty(JsValue property) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { var value = IntegerIndexedElementGet(numericIndex.Value); if (value.IsUndefined()) { return PropertyDescriptor.Undefined; } return new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } return base.GetOwnProperty(property); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver /// public override JsValue Get(JsValue property, JsValue receiver) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { return IntegerIndexedElementGet(numericIndex.Value); } return base.Get(property, receiver); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver /// public override bool Set(JsValue property, JsValue value, JsValue receiver) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { if (ReferenceEquals(this, receiver)) { IntegerIndexedElementSet(numericIndex.Value, value); return true; } if (!IsValidIntegerIndex(numericIndex.Value)) { return true; } } return base.Set(property, value, receiver); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc /// public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { if (!IsValidIntegerIndex(numericIndex.Value)) { return false; } if (desc is { ConfigurableSet: true, Configurable: false }) { return false; } if (desc is { EnumerableSet: true, Enumerable: false }) { return false; } if (desc.IsAccessorDescriptor()) { return false; } if (desc is { WritableSet: true, Writable: false }) { return false; } IntegerIndexedElementSet(numericIndex.Value, desc.Value); return true; } return base.DefineOwnProperty(property, desc); } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys /// public override List GetOwnPropertyKeys(Types types = Types.Empty | Types.String | Types.Symbol) { var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.SeqCst); var keys = new List(); if (!taRecord.IsTypedArrayOutOfBounds) { var length = GetLength(); for (uint i = 0; i < length; ++i) { keys.Add(JsString.Create(i)); } } if (_properties is not null) { foreach (var pair in _properties) { keys.Add(pair.Key.Name); } } if (_symbols is not null) { foreach (var pair in _symbols) { keys.Add(pair.Key); } } return keys; } /// /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p /// public override bool Delete(JsValue property) { var numericIndex = TypeConverter.CanonicalNumericIndexString(property); if (numericIndex is not null) { return !IsValidIntegerIndex(numericIndex.Value); } return base.Delete(property); } // helper to prevent floating points [MethodImpl(MethodImplOptions.AggressiveInlining)] private JsValue IntegerIndexedElementGet(int index) { if (!IsValidIntegerIndex(index)) { return Undefined; } return DoIntegerIndexedElementGet(index); } /// /// https://tc39.es/ecma262/#sec-integerindexedelementget /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private JsValue IntegerIndexedElementGet(double index) { if (!IsValidIntegerIndex(index)) { return Undefined; } return DoIntegerIndexedElementGet((int) index); } private JsValue DoIntegerIndexedElementGet(int index) { var offset = _byteOffset; var elementType = _arrayElementType; var elementSize = elementType.GetElementSize(); var indexedPosition = index * elementSize + offset; var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, isTypedArray: true, ArrayBufferOrder.Unordered); if (value.Type == Types.Number) { return _arrayElementType.FitsInt32() ? JsNumber.Create((int) value.DoubleValue) : JsNumber.Create(value.DoubleValue); } return JsBigInt.Create(value.BigInteger); } // helper tot prevent floating point [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void IntegerIndexedElementSet(int index, JsValue value) { TypedArrayValue numValue = _contentType != TypedArrayContentType.BigInt ? TypeConverter.ToNumber(value) : value.ToBigInteger(_engine); if (IsValidIntegerIndex(index)) { DoIntegerIndexedElementSet(index, numValue); } } /// /// https://tc39.es/ecma262/#sec-integerindexedelementset /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void IntegerIndexedElementSet(double index, JsValue value) { if (_contentType != TypedArrayContentType.BigInt) { var numValue = TypeConverter.ToNumber(value); if (IsValidIntegerIndex(index)) { DoIntegerIndexedElementSet((int) index, numValue); } } else { try { var numValue = TypeConverter.ToBigInt(value); if (IsValidIntegerIndex(index)) { DoIntegerIndexedElementSet((int) index, numValue); } } catch (ParseErrorException ex) { Throw.SyntaxError(_engine.Realm, ex.Message); } } } internal void DoIntegerIndexedElementSet(int index, TypedArrayValue numValue) { var offset = _byteOffset; var elementType = _arrayElementType; var elementSize = elementType.GetElementSize(); var indexedPosition = index * elementSize + offset; _viewedArrayBuffer.SetValueInBuffer(indexedPosition, elementType, numValue, true, ArrayBufferOrder.Unordered); } /// /// https://tc39.es/ecma262/#sec-isvalidintegerindex /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsValidIntegerIndex(double index) { if (_viewedArrayBuffer.IsDetachedBuffer) { return false; } if (!TypeConverter.IsIntegralNumber(index)) { return false; } if (NumberInstance.IsNegativeZero(index)) { return false; } return IsValidIntegerIndex((int) index); } /// /// https://tc39.es/ecma262/#sec-isvalidintegerindex /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsValidIntegerIndex(int index) { if (_viewedArrayBuffer.IsDetachedBuffer) { return false; } var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered); if (taRecord.IsTypedArrayOutOfBounds) { return false; } var length = taRecord.TypedArrayLength; if (index < 0 || index >= length) { return false; } return true; } internal T[] ToNativeArray() { var conversionType = typeof(T); var elementSize = _arrayElementType.GetElementSize(); var byteOffset = _byteOffset; var buffer = _viewedArrayBuffer; var array = new T[GetLength()]; for (var i = 0; i < array.Length; ++i) { var indexedPosition = i * elementSize + byteOffset; var value = buffer.RawBytesToNumeric(_arrayElementType, indexedPosition, BitConverter.IsLittleEndian); array[i] = (T) Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture); } return array; } }