From 94bd26f530b4e9cd9ce440fe652ee0aed8674400 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 13 Jul 2024 12:21:00 +0300 Subject: [PATCH] Implement Uint8Array to/from base64 (#1911) --- Directory.Packages.props | 2 +- .../Test262Harness.settings.json | 1 - Jint/Extensions/WebEncoders.cs | 338 ++++++++++++++++ .../ArrayBuffer/ArrayBufferConstructor.cs | 2 +- Jint/Native/Global/GlobalObject.cs | 12 +- Jint/Native/JsArrayBuffer.cs | 1 + Jint/Native/JsString.cs | 18 +- .../TypedArray/TypedArrayConstructor.Types.cs | 327 ++++++++-------- .../TypedArrayConstructor.Uint8Array.cs | 360 ++++++++++++++++++ .../TypedArray/TypedArrayConstructor.cs | 18 +- Jint/Native/TypedArray/TypedArrayPrototype.cs | 2 +- Jint/Native/TypedArray/Uint8ArrayPrototype.cs | 188 +++++++++ Jint/Runtime/ExceptionHelper.cs | 11 +- README.md | 1 + 14 files changed, 1079 insertions(+), 202 deletions(-) create mode 100644 Jint/Extensions/WebEncoders.cs create mode 100644 Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs create mode 100644 Jint/Native/TypedArray/Uint8ArrayPrototype.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index e1e6832a9e..5c5592c274 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,7 +23,7 @@ - + diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 9bcf95bcb5..a3d00b76d9 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -18,7 +18,6 @@ "regexp-v-flag", "source-phase-imports", "tail-call-optimization", - "uint8array-base64", "Temporal", "u180e" ], diff --git a/Jint/Extensions/WebEncoders.cs b/Jint/Extensions/WebEncoders.cs new file mode 100644 index 0000000000..80bb40b429 --- /dev/null +++ b/Jint/Extensions/WebEncoders.cs @@ -0,0 +1,338 @@ +// modified from +// https://github.com/dotnet/aspnetcore/blob/fd060ce8c36ffe195b9e9a69a1bbd8fb53cc6d7c/src/Shared/WebEncoders/WebEncoders.cs + +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if NETCOREAPP +using System.Buffers; +#endif +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Jint.Extensions; + +/// +/// Contains utility APIs to assist with common encoding and decoding operations. +/// +[SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper")] +[SuppressMessage("Maintainability", "CA1512:Use ArgumentOutOfRangeException throw helper")] +internal static class WebEncoders +{ + private static readonly byte[] EmptyBytes = []; + + /// + /// Decodes a base64url-encoded string. + /// + /// The base64url-encoded input to decode. + /// The base64url-decoded form of the input. + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(ReadOnlySpan input) + { + // Special-case empty input + if (input.Length == 0) + { + return EmptyBytes; + } + + // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. + var buffer = new char[GetArraySizeRequiredToDecode(input.Length)]; + + return Base64UrlDecode(input, buffer); + } + + /// + /// Decodes a base64url-encoded into a byte[]. + /// + public static byte[] Base64UrlDecode(ReadOnlySpan input, char[] buffer) + { + if (input.Length == 0) + { + return EmptyBytes; + } + + // Assumption: input is base64url encoded without padding and contains no whitespace. + + var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(input.Length); + var arraySizeRequired = checked(input.Length + paddingCharsToAdd); + Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4."); + + // Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'. + var i = 0; + for (var j = 0; i < input.Length; i++, j++) + { + var ch = input[j]; + if (ch == '-') + { + buffer[i] = '+'; + } + else if (ch == '_') + { + buffer[i] = '/'; + } + else + { + buffer[i] = ch; + } + } + + // Add the padding characters back. + for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--) + { + buffer[i] = '='; + } + + // Decode. + // If the caller provided invalid base64 chars, they'll be caught here. + return Convert.FromBase64CharArray(buffer, 0, arraySizeRequired); + } + + private static int GetArraySizeRequiredToDecode(int count) + { + if (count == 0) + { + return 0; + } + + var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); + + return checked(count + numPaddingCharsToAdd); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + return Base64UrlEncode(input, offset: 0, count: input.Length); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// The number of bytes from to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input, int offset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + +#if NETCOREAPP + return Base64UrlEncode(input.AsSpan(offset, count)); +#else + // Special-case empty input + if (count == 0) + { + return string.Empty; + } + + var buffer = new char[GetArraySizeRequiredToEncode(count)]; + var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); + + return new string(buffer, startIndex: 0, length: numBase64Chars); +#endif + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// + /// Buffer to receive the base64url-encoded form of . Array must be large enough to + /// hold characters and the full base64-encoded form of + /// , including padding characters. + /// + /// + /// The offset into at which to begin writing the base64url-encoded form of + /// . + /// + /// The number of bytes from to encode. + /// + /// The number of characters written to , less any padding characters. + /// + public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (outputOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(outputOffset)); + } + + var arraySizeRequired = GetArraySizeRequiredToEncode(count); + if (output.Length - outputOffset < arraySizeRequired) + { + throw new ArgumentException("invalid", nameof(count)); + } + +#if NETCOREAPP + return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset)); +#else + // Special-case empty input. + if (count == 0) + { + return 0; + } + + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + // Start with default Base64 encoding. + var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = outputOffset; i - outputOffset < numBase64Chars; i++) + { + var ch = output[i]; + if (ch == '+') + { + output[i] = '-'; + } + else if (ch == '/') + { + output[i] = '_'; + } + else if (ch == '=') + { + // We've reached a padding character; truncate the remainder. + return i - outputOffset; + } + } + + return numBase64Chars; +#endif + } + + /// + /// Get the minimum output char[] size required for encoding + /// s with the method. + /// + /// The number of characters to encode. + /// + /// The minimum output char[] size required for encoding s. + /// + public static int GetArraySizeRequiredToEncode(int count) + { + var numWholeOrPartialInputBlocks = checked(count + 2) / 3; + return checked(numWholeOrPartialInputBlocks * 4); + } + +#if NETCOREAPP + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(ReadOnlySpan input) + { + if (input.IsEmpty) + { + return string.Empty; + } + + int bufferSize = GetArraySizeRequiredToEncode(input.Length); + + char[]? bufferToReturnToPool = null; + Span buffer = bufferSize <= 128 + ? stackalloc char[bufferSize] + : bufferToReturnToPool = ArrayPool.Shared.Rent(bufferSize); + + var numBase64Chars = Base64UrlEncode(input, buffer); + var base64Url = new string(buffer.Slice(0, numBase64Chars)); + + if (bufferToReturnToPool != null) + { + ArrayPool.Shared.Return(bufferToReturnToPool); + } + + return base64Url; + } + + private static int Base64UrlEncode(ReadOnlySpan input, Span output) + { + Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length)); + + if (input.IsEmpty) + { + return 0; + } + + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + Convert.TryToBase64Chars(input, output, out int charsWritten); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = 0; i < charsWritten; i++) + { + var ch = output[i]; + if (ch == '+') + { + output[i] = '-'; + } + else if (ch == '/') + { + output[i] = '_'; + } + else if (ch == '=') + { + // We've reached a padding character; truncate the remainder. + return i; + } + } + + return charsWritten; + } +#endif + + private static int GetNumBase64PaddingCharsInString(string str) + { + // Assumption: input contains a well-formed base64 string with no whitespace. + + // base64 guaranteed have 0 - 2 padding characters. + if (str[str.Length - 1] == '=') + { + if (str[str.Length - 2] == '=') + { + return 2; + } + return 1; + } + return 0; + } + + private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) + { + switch (inputLength % 4) + { + case 0: + return 0; + case 2: + return 2; + case 3: + return 1; + default: + throw new FormatException("invalid length"); + } + } +} diff --git a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs index 439cb9af9c..e24bf8e5f4 100644 --- a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs +++ b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs @@ -28,7 +28,7 @@ internal ArrayBufferConstructor( _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } - private ArrayBufferPrototype PrototypeObject { get; } + internal ArrayBufferPrototype PrototypeObject { get; } protected override void Initialize() { diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index c5327f624f..5b50d8b0c3 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -283,25 +283,17 @@ private static JsValue IsFinite(JsValue thisObject, JsValue[] arguments) private static bool IsValidHexaChar(char c) => Uri.IsHexDigit(c); /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.2 + /// https://tc39.es/ecma262/#sec-encodeuri-uri /// - /// - /// - /// private JsValue EncodeUri(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); - return Encode(uriString, UnescapedUriSet); } - /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 + /// https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent /// - /// - /// - /// private JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); diff --git a/Jint/Native/JsArrayBuffer.cs b/Jint/Native/JsArrayBuffer.cs index 00cc93ee20..6f6db98c9c 100644 --- a/Jint/Native/JsArrayBuffer.cs +++ b/Jint/Native/JsArrayBuffer.cs @@ -28,6 +28,7 @@ internal JsArrayBuffer( ExceptionHelper.ThrowRangeError(engine.Realm, "arrayBufferMaxByteLength cannot be larger than int32.MaxValue"); } + _prototype = engine.Intrinsics.ArrayBuffer.PrototypeObject; _arrayBufferData = data; _arrayBufferMaxByteLength = (int?) arrayBufferMaxByteLength; } diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index 3f4134d58c..326efb99d3 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -128,15 +128,25 @@ public JsString(char value) : base(Types.String) return a.Equals(b); } - if (a is null) + return b is null; + } + + public static bool operator !=(JsString a, JsValue b) + { + return !(a == b); + } + + public static bool operator ==(JsString? a, string? b) + { + if (a is not null) { - return b is null; + return a.Equals(b); } - return b is not null && a.Equals(b); + return b is null; } - public static bool operator !=(JsString a, JsValue b) + public static bool operator !=(JsString? a, string? b) { return !(a == b); } diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs b/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs index fee7edafcb..13f2dab6a8 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs @@ -1,225 +1,206 @@ using Jint.Runtime; -namespace Jint.Native.TypedArray +namespace Jint.Native.TypedArray; + +public sealed class Int8ArrayConstructor : TypedArrayConstructor { - public sealed class Int8ArrayConstructor : TypedArrayConstructor + internal Int8ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int8) { - internal Int8ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int8) - { - } - - public JsTypedArray Construct(sbyte[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } } - public sealed class Uint8ArrayConstructor : TypedArrayConstructor + public JsTypedArray Construct(ReadOnlySpan values) { - internal Uint8ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8) - { - } - - public JsTypedArray Construct(byte[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Uint8ClampedArrayConstructor : TypedArrayConstructor +public sealed class Uint8ClampedArrayConstructor : TypedArrayConstructor +{ + internal Uint8ClampedArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8C) { - internal Uint8ClampedArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8C) - { - } + } - public JsTypedArray Construct(byte[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Int16ArrayConstructor : TypedArrayConstructor +public sealed class Int16ArrayConstructor : TypedArrayConstructor +{ + internal Int16ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int16) { - internal Int16ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int16) - { - } + } - public JsTypedArray Construct(short[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Uint16ArrayConstructor : TypedArrayConstructor +public sealed class Uint16ArrayConstructor : TypedArrayConstructor +{ + internal Uint16ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint16) { - internal Uint16ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint16) - { - } + } - public JsTypedArray Construct(ushort[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Int32ArrayConstructor : TypedArrayConstructor +public sealed class Int32ArrayConstructor : TypedArrayConstructor +{ + internal Int32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int32) { - internal Int32ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int32) - { - } + } - public JsTypedArray Construct(int[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Uint32ArrayConstructor : TypedArrayConstructor +public sealed class Uint32ArrayConstructor : TypedArrayConstructor +{ + internal Uint32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint32) { - internal Uint32ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint32) - { - } + } - public JsTypedArray Construct(uint[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Float16ArrayConstructor : TypedArrayConstructor +public sealed class Float16ArrayConstructor : TypedArrayConstructor +{ + internal Float16ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float16) { - internal Float16ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float16) - { - } + } #if SUPPORTS_HALF - public JsTypedArray Construct(Half[] values) + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + for (var i = 0; i < values.Length; ++i) { - var array = (JsTypedArray) base.Construct([values.Length], this); - for (var i = 0; i < values.Length; ++i) - { - array.DoIntegerIndexedElementSet(i, values[i]); - } - return array; + array.DoIntegerIndexedElementSet(i, values[i]); } -#endif + return array; } +#endif +} - public sealed class Float32ArrayConstructor : TypedArrayConstructor +public sealed class Float32ArrayConstructor : TypedArrayConstructor +{ + internal Float32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float32) { - internal Float32ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float32) - { - } + } - public JsTypedArray Construct(float[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class Float64ArrayConstructor : TypedArrayConstructor +public sealed class Float64ArrayConstructor : TypedArrayConstructor +{ + internal Float64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float64) { - internal Float64ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float64) - { - } + } - public JsTypedArray Construct(double[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class BigInt64ArrayConstructor : TypedArrayConstructor +public sealed class BigInt64ArrayConstructor : TypedArrayConstructor +{ + internal BigInt64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigInt64) { - internal BigInt64ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigInt64) - { - } + } - public JsTypedArray Construct(long[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } +} - public sealed class BigUint64ArrayConstructor : TypedArrayConstructor +public sealed class BigUint64ArrayConstructor : TypedArrayConstructor +{ + internal BigUint64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigUint64) { - internal BigUint64ArrayConstructor( - Engine engine, - Realm realm, - IntrinsicTypedArrayConstructor functionPrototype, - IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigUint64) - { - } + } - public JsTypedArray Construct(ulong[] values) - { - var array = (JsTypedArray) base.Construct([values.Length], this); - FillTypedArrayInstance(array, values); - return array; - } + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; } } diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs new file mode 100644 index 0000000000..6f4b0e68ea --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs @@ -0,0 +1,360 @@ +using System.Buffers; +using System.Globalization; +using Jint.Collections; +using Jint.Extensions; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native.TypedArray; + +public sealed class Uint8ArrayConstructor : TypedArrayConstructor +{ + internal Uint8ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8) + { + } + + protected override void Initialize() + { + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + var properties = new PropertyDictionary(1, checkExistingKeys: false) { ["BYTES_PER_ELEMENT"] = new(new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.AllForbidden)), ["fromBase64"] = new(new ClrFunction(Engine, "fromBase64", FromBase64, 1, PropertyFlag.Configurable), PropertyFlags), ["fromHex"] = new(new ClrFunction(Engine, "fromHex", FromHex, 1, PropertyFlag.Configurable), PropertyFlags), }; + SetProperties(properties); + } + + public JsTypedArray Construct(ReadOnlySpan values) + { + var array = (JsTypedArray) base.Construct([values.Length], this); + FillTypedArrayInstance(array, values); + return array; + } + + private JsTypedArray FromBase64(JsValue thisObject, JsValue[] arguments) + { + var s = arguments.At(0); + + if (!s.IsString()) + { + ExceptionHelper.ThrowTypeError(_realm, "fromBase64 must be called with a string"); + } + + var opts = GetOptionsObject(_engine, arguments.At(1)); + var alphabet = GetAndValidateAlphabetOption(_engine, opts); + var lastChunkHandling = GetAndValidateLastChunkHandling(_engine, opts); + + var result = FromBase64(_engine, s.ToString(), alphabet.ToString(), lastChunkHandling.ToString()); + if (result.Error is not null) + { + throw result.Error; + } + + var ta = _realm.Intrinsics.Uint8Array.Construct(new JsArrayBuffer(_engine, result.Bytes)); + return ta; + } + + internal static JsString GetAndValidateLastChunkHandling(Engine engine, ObjectInstance opts) + { + var lastChunkHandling = opts.Get("lastChunkHandling"); + if (lastChunkHandling.IsUndefined()) + { + lastChunkHandling = "loose"; + } + + if (lastChunkHandling is not JsString s || (s != "loose" && s != "strict" && s != "stop-before-partial")) + { + ExceptionHelper.ThrowTypeError(engine.Realm, "lastChunkHandling must be either 'loose', 'strict' or 'stop-before-partial'"); + return default; + } + + return s; + } + + internal static JsString GetAndValidateAlphabetOption(Engine engine, ObjectInstance opts) + { + var alphabet = opts.Get("alphabet"); + if (alphabet.IsUndefined()) + { + alphabet = "base64"; + } + + if (alphabet is not JsString s || (s != "base64" && s != "base64url")) + { + ExceptionHelper.ThrowTypeError(engine.Realm, "alphabet must be either 'base64' or 'base64url'"); + return default; + } + + return s; + } + + internal readonly record struct FromEncodingResult(byte[] Bytes, JavaScriptException? Error, int Read); + + private static readonly SearchValues Base64Alphabet = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + + internal static FromEncodingResult FromBase64(Engine engine, string input, string alphabet, string lastChunkHandling, uint maxLength = uint.MaxValue) + { + if (maxLength == 0) + { + return new FromEncodingResult([], Error: null, 0); + } + + var read = 0; + var bytes = new List(); + var chunk = new char[4]; + var chunkLength = 0; + var index = 0; + var length = input.Length; + + var stopBeforePartial = string.Equals(lastChunkHandling, "stop-before-partial", StringComparison.Ordinal); + var loose = string.Equals(lastChunkHandling, "loose", StringComparison.Ordinal); + var base64Url = string.Equals(alphabet, "base64url", StringComparison.Ordinal); + var throwOnExtraBits = string.Equals(lastChunkHandling, "strict", StringComparison.Ordinal); + + while (true) + { + index = SkipAsciiWhitespace(input, index); + if (index == length) + { + if (chunkLength > 0) + { + if (stopBeforePartial) + { + return new FromEncodingResult(bytes.ToArray(), Error: null, read); + } + + if (loose) + { + if (chunkLength == 1) + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length."), read); + } + + DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits: false); + } + else // strict + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length in strict mode."), read); + } + } + + return new FromEncodingResult(bytes.ToArray(), Error: null, length); + } + + char currentChar = input[index]; + index++; + + if (currentChar == '=') + { + if (chunkLength < 2) + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid '=' placement in base64 string."), read); + } + + index = SkipAsciiWhitespace(input, index); + if (chunkLength == 2) + { + if (index == length) + { + if (stopBeforePartial) + { + return new FromEncodingResult(bytes.ToArray(), Error: null, read); + } + + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 string termination."), read); + } + + currentChar = input[index]; + if (currentChar != '=') + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Expected '=' in base64 string."), read); + } + + index = SkipAsciiWhitespace(input, index + 1); + } + + if (index < length) + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Extra characters after base64 string."), read); + } + + DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits); + return new FromEncodingResult(bytes.ToArray(), Error: null, length); + } + + if (base64Url) + { + if (currentChar is '+' or '/') + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid character in base64url string."), read); + } + + if (currentChar == '-') + { + currentChar = '+'; + } + + if (currentChar == '_') + { + currentChar = '/'; + } + } + + if (!Base64Alphabet.Contains(currentChar)) + { + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read); + } + + ulong remaining = maxLength - (ulong) bytes.Count; + if ((remaining == 1 && chunkLength == 2) || (remaining == 2 && chunkLength == 3)) + { + return new FromEncodingResult(bytes.ToArray(), Error: null, read); + } + + chunk[chunkLength] = currentChar; + chunkLength++; + + if (chunkLength == 4) + { + DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits: false); + chunkLength = 0; + read = index; + if (bytes.Count == maxLength) + { + return new FromEncodingResult(bytes.ToArray(), Error: null, read); + } + } + } + } + + private static int SkipAsciiWhitespace(string input, int index) + { + while (index < input.Length) + { + var c = input[index]; + if (c != 0x0009 && c != 0x000A && c != 0x000C && c != 0x000D && c != 0x0020) + { + return index; + } + + index++; + } + + return index; + } + + private static void DecodeBase64Chunk( + Engine engine, + List into, + char[] chunk, + int chunkLength, + bool throwOnExtraBits = false) + { + if (chunkLength == 2) + { + chunk[2] = 'A'; + chunk[3] = 'A'; + } + else if (chunkLength == 3) + { + chunk[3] = 'A'; + } + + var bytes = WebEncoders.Base64UrlDecode(chunk); + + if (chunkLength == 2) + { + if (throwOnExtraBits && bytes[1] != 0) + { + ExceptionHelper.ThrowSyntaxError(engine.Realm, "Invalid padding in base64 chunk."); + } + into.Add(bytes[0]); + return; + } + + if (chunkLength == 3) + { + if (throwOnExtraBits && bytes[2] != 0) + { + ExceptionHelper.ThrowSyntaxError(engine.Realm, "Invalid padding in base64 chunk."); + } + into.Add(bytes[0]); + into.Add(bytes[1]); + return; + } + + into.AddRange(bytes); + } + + private JsTypedArray FromHex(JsValue thisObject, JsValue[] arguments) + { + var s = arguments.At(0); + + if (!s.IsString()) + { + ExceptionHelper.ThrowTypeError(_realm, "fromHex must be called with a string"); + } + + var result = FromHex(_engine, s.ToString()); + if (result.Error is not null) + { + throw result.Error; + } + + var ta = _realm.Intrinsics.Uint8Array.Construct(new JsArrayBuffer(_engine, result.Bytes)); + ta._viewedArrayBuffer._arrayBufferData = result.Bytes; + return ta; + } + + private static readonly SearchValues HexAlphabet = SearchValues.Create("0123456789abcdefABCDEF"); + + internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLength = int.MaxValue) + { + var length = s.Length; + var bytes = new byte[System.Math.Min(maxLength, length / 2)]; + var read = 0; + + if (length % 2 != 0) + { + return new FromEncodingResult(bytes, ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex string"), read); + } + + var byteIndex = 0; + while (read < length && byteIndex < maxLength) + { + var hexits = s.AsSpan(read, 2); + if (!HexAlphabet.Contains(hexits[0]) || !HexAlphabet.Contains(hexits[1])) + { + return new FromEncodingResult(bytes, ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex value"), read); + } + +#if SUPPORTS_SPAN_PARSE + var b = byte.Parse(hexits, NumberStyles.HexNumber, CultureInfo.InvariantCulture); +#else + var b = byte.Parse(hexits.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture); +#endif + bytes[byteIndex++] = b; + read += 2; + } + + return new FromEncodingResult(bytes, Error: null, read); + } + + internal static ObjectInstance GetOptionsObject(Engine engine, JsValue options) + { + if (options.IsUndefined()) + { + return new JsObject(engine); + } + + if (options.IsObject()) + { + return options.AsObject(); + } + + ExceptionHelper.ThrowTypeError(engine.Realm, "Invalid options"); + return default; + } +} diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index 04dfd71860..ae60a63436 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -24,14 +24,17 @@ internal TypedArrayConstructor( TypedArrayElementType type) : base(engine, realm, new JsString(type.GetTypedArrayName())) { _arrayElementType = type; - _prototype = functionPrototype; - PrototypeObject = new TypedArrayPrototype(engine, objectPrototype, this, type); + + PrototypeObject = type == TypedArrayElementType.Uint8 + ? new Uint8ArrayPrototype(engine, objectPrototype, this) + : new TypedArrayPrototype(engine, objectPrototype, this, type); + _length = new PropertyDescriptor(JsNumber.PositiveThree, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } - private TypedArrayPrototype PrototypeObject { get; } + private Prototype PrototypeObject { get; } protected override void Initialize() { @@ -56,7 +59,6 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) ExceptionHelper.ThrowTypeError(_realm); } - var numberOfArgs = arguments.Length; if (numberOfArgs == 0) { @@ -263,7 +265,7 @@ private static void InitializeTypedArrayFromArrayLike(JsTypedArray o, ObjectInst /// /// https://tc39.es/ecma262/#sec-allocatetypedarray /// - private JsTypedArray AllocateTypedArray(JsValue newTarget, uint length = 0) + internal JsTypedArray AllocateTypedArray(JsValue newTarget, uint length = 0) { Func defaultProto = _arrayElementType switch { @@ -296,7 +298,7 @@ private JsTypedArray AllocateTypedArray(JsValue newTarget, uint length = 0) return obj; } - internal static void FillTypedArrayInstance(JsTypedArray target, T[] values) + internal static void FillTypedArrayInstance(JsTypedArray target, ReadOnlySpanvalues) { for (var i = 0; i < values.Length; ++i) { @@ -304,7 +306,7 @@ internal static void FillTypedArrayInstance(JsTypedArray target, T[] values) } } - internal static void FillTypedArrayInstance(JsTypedArray target, ulong[] values) + internal static void FillTypedArrayInstance(JsTypedArray target, ReadOnlySpan values) { for (var i = 0; i < values.Length; ++i) { @@ -312,7 +314,7 @@ internal static void FillTypedArrayInstance(JsTypedArray target, ulong[] values) } } - internal static void FillTypedArrayInstance(JsTypedArray target, long[] values) + internal static void FillTypedArrayInstance(JsTypedArray target, ReadOnlySpan values) { for (var i = 0; i < values.Length; ++i) { diff --git a/Jint/Native/TypedArray/TypedArrayPrototype.cs b/Jint/Native/TypedArray/TypedArrayPrototype.cs index 47d6c08ccf..a91b23aca5 100644 --- a/Jint/Native/TypedArray/TypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/TypedArrayPrototype.cs @@ -24,7 +24,7 @@ internal TypedArrayPrototype( protected override void Initialize() { - var properties = new PropertyDictionary(2, false) + var properties = new PropertyDictionary(2, checkExistingKeys: false) { ["BYTES_PER_ELEMENT"] = new(JsNumber.Create(_arrayElementType.GetElementSize()), PropertyFlag.AllForbidden), ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), diff --git a/Jint/Native/TypedArray/Uint8ArrayPrototype.cs b/Jint/Native/TypedArray/Uint8ArrayPrototype.cs new file mode 100644 index 0000000000..295f48d7da --- /dev/null +++ b/Jint/Native/TypedArray/Uint8ArrayPrototype.cs @@ -0,0 +1,188 @@ +using System.Text; +using Jint.Collections; +using Jint.Extensions; +using Jint.Native.ArrayBuffer; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native.TypedArray; + +internal sealed class Uint8ArrayPrototype : Prototype +{ + private readonly TypedArrayConstructor _constructor; + + public Uint8ArrayPrototype( + Engine engine, + IntrinsicTypedArrayPrototype objectPrototype, + TypedArrayConstructor constructor) + : base(engine, engine.Realm) + { + _prototype = objectPrototype; + _constructor = constructor; + + } + + protected override void Initialize() + { + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + var properties = new PropertyDictionary(6, checkExistingKeys: false) + { + ["BYTES_PER_ELEMENT"] = new(JsNumber.PositiveOne, PropertyFlag.AllForbidden), + ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), + ["setFromBase64"] = new(new ClrFunction(Engine, "setFromBase64", SetFromBase64, 1, PropertyFlag.Configurable), PropertyFlags), + ["setFromHex"] = new(new ClrFunction(Engine, "setFromHex", SetFromHex, 1, PropertyFlag.Configurable), PropertyFlags), + ["toBase64"] = new(new ClrFunction(Engine, "toBase64", ToBase64, 0, PropertyFlag.Configurable), PropertyFlags), + ["toHex"] = new(new ClrFunction(Engine, "toHex", ToHex, 0, PropertyFlag.Configurable), PropertyFlags), + }; + SetProperties(properties); + } + + private JsObject SetFromBase64(JsValue thisObject, JsValue[] arguments) + { + var into = ValidateUint8Array(thisObject); + var s = arguments.At(0); + + if (!s.IsString()) + { + ExceptionHelper.ThrowTypeError(_realm, "setFromBase64 must be called with a string"); + } + + var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(1)); + var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts); + var lastChunkHandling = Uint8ArrayConstructor.GetAndValidateLastChunkHandling(_engine, opts); + + var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst); + if (taRecord.IsTypedArrayOutOfBounds) + { + ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds"); + } + + var byteLength = taRecord.TypedArrayLength; + var result = Uint8ArrayConstructor.FromBase64(_engine, s.ToString(), alphabet.ToString(), lastChunkHandling.ToString(), byteLength); + if (result.Error is not null) + { + throw result.Error; + } + + SetUint8ArrayBytes(into, result.Bytes); + + var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); + resultObject.CreateDataPropertyOrThrow("read", result.Read); + resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); + return resultObject; + } + + private static void SetUint8ArrayBytes(JsTypedArray into, byte[] bytes) + { + var offset = into._byteOffset; + var len = bytes.Length; + var index = 0; + while (index < len) + { + var b = bytes[index]; + var byteIndexInBuffer = index + offset; + into._viewedArrayBuffer.SetValueInBuffer(byteIndexInBuffer, TypedArrayElementType.Uint8, b, isTypedArray: true, ArrayBufferOrder.Unordered); + index++; + } + } + + private JsObject SetFromHex(JsValue thisObject, JsValue[] arguments) + { + var into = ValidateUint8Array(thisObject); + var s = arguments.At(0); + + if (!s.IsString()) + { + ExceptionHelper.ThrowTypeError(_realm, "setFromHex must be called with a string"); + } + + var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst); + if (taRecord.IsTypedArrayOutOfBounds) + { + ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds"); + } + + var byteLength = taRecord.TypedArrayLength; + var result = Uint8ArrayConstructor.FromHex(_engine, s.ToString(), byteLength); + if (result.Error is not null) + { + throw result.Error; + } + + SetUint8ArrayBytes(into, result.Bytes); + + var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); + resultObject.CreateDataPropertyOrThrow("read", result.Read); + resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); + return resultObject; + } + + private JsValue ToBase64(JsValue thisObject, JsValue[] arguments) + { + var o = ValidateUint8Array(thisObject); + + var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(0)); + var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts); + + var omitPadding = TypeConverter.ToBoolean(opts.Get("omitPadding")); +#if NETCOREAPP + var toEncode = GetUint8ArrayBytes(o); +#else + var toEncode = GetUint8ArrayBytes(o).ToArray(); +#endif + + string outAscii; + if (alphabet == "base64") + { + outAscii = Convert.ToBase64String(toEncode); + } + else + { + outAscii = WebEncoders.Base64UrlEncode(toEncode); + } + + return outAscii; + } + + private JsValue ToHex(JsValue thisObject, JsValue[] arguments) + { + var o = ValidateUint8Array(thisObject); + var toEncode = GetUint8ArrayBytes(o); + + using var outString = new ValueStringBuilder(); + foreach (var b in toEncode) + { + var b1 = (byte)(b >> 4); + outString.Append((char)(b1 > 9 ? b1 - 10 + 'a' : b1 + '0')); + + var b2 = (byte)(b & 0x0F); + outString.Append((char)(b2 > 9 ? b2 - 10 + 'a' : b2 + '0')); + } + + return outString.ToString(); + } + + private ReadOnlySpan GetUint8ArrayBytes(JsTypedArray ta) + { + var buffer = ta._viewedArrayBuffer; + var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(ta, ArrayBufferOrder.SeqCst); + if (taRecord.IsTypedArrayOutOfBounds) + { + ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds"); + } + + return buffer._arrayBufferData!.AsSpan(0, (int) taRecord.TypedArrayLength); + } + + private JsTypedArray ValidateUint8Array(JsValue ta) + { + if (ta is not JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 } typedArray) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, "Not a Uint8Array"); + return default; + } + + return typedArray; + } +} diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index 2e6671451e..86f8a24c5a 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -18,19 +18,24 @@ internal static class ExceptionHelper [DoesNotReturn] public static void ThrowSyntaxError(Realm realm, string? message = null) { - throw new JavaScriptException(realm.Intrinsics.SyntaxError, message); + throw CreateSyntaxError(realm, message); } [DoesNotReturn] public static void ThrowSyntaxError(Realm realm, string message, in SourceLocation location) { - throw new JavaScriptException(realm.Intrinsics.SyntaxError, message).SetJavaScriptLocation(location); + throw CreateSyntaxError(realm, message).SetJavaScriptLocation(location); + } + + public static JavaScriptException CreateSyntaxError(Realm realm, string? message) + { + return new JavaScriptException(realm.Intrinsics.SyntaxError, message); } [DoesNotReturn] public static void ThrowArgumentException(string? message = null) { - ThrowArgumentException(message, null); + ThrowArgumentException(message, paramName: null); } [DoesNotReturn] diff --git a/README.md b/README.md index e2f66f7ec7..f7b1d03324 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ and many more. - ✔ Resizable and growable ArrayBuffers - ✔ Set methods (`intersection`, `union`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) - ✔ ShadowRealm +- ✔ Uint8Array to/from base64 #### Other