Skip to content

Commit

Permalink
Fix issues found with latest test262 suite (#1912)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Jul 13, 2024
1 parent 94bd26f commit 0ce19d8
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 30 deletions.
30 changes: 17 additions & 13 deletions Jint/Extensions/WebEncoders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,16 @@ private static int GetArraySizeRequiredToDecode(int count)
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <param name="omitPadding"></param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(byte[] input)
public static string Base64UrlEncode(byte[] input, bool omitPadding)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}

return Base64UrlEncode(input, offset: 0, count: input.Length);
return Base64UrlEncode(input, offset: 0, count: input.Length, omitPadding);
}

/// <summary>
Expand All @@ -123,16 +124,17 @@ public static string Base64UrlEncode(byte[] input)
/// <param name="input">The binary input to encode.</param>
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
/// <param name="count">The number of bytes from <paramref name="input"/> to encode.</param>
/// <param name="omitPadding"></param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(byte[] input, int offset, int count)
public static string Base64UrlEncode(byte[] input, int offset, int count, bool omitPadding)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}

#if NETCOREAPP
return Base64UrlEncode(input.AsSpan(offset, count));
return Base64UrlEncode(input.AsSpan(offset, count), omitPadding);
#else
// Special-case empty input
if (count == 0)
Expand All @@ -141,7 +143,7 @@ public static string Base64UrlEncode(byte[] input, int offset, int count)
}

var buffer = new char[GetArraySizeRequiredToEncode(count)];
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count);
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count, omitPadding);

return new string(buffer, startIndex: 0, length: numBase64Chars);
#endif
Expand All @@ -162,10 +164,11 @@ public static string Base64UrlEncode(byte[] input, int offset, int count)
/// <paramref name="input"/>.
/// </param>
/// <param name="count">The number of <c>byte</c>s from <paramref name="input"/> to encode.</param>
/// <param name="omitPadding"></param>
/// <returns>
/// The number of characters written to <paramref name="output"/>, less any padding characters.
/// </returns>
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count)
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count, bool omitPadding)
{
if (input == null)
{
Expand All @@ -188,7 +191,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o
}

#if NETCOREAPP
return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset));
return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset), omitPadding);
#else
// Special-case empty input.
if (count == 0)
Expand All @@ -213,7 +216,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o
{
output[i] = '_';
}
else if (ch == '=')
else if (omitPadding && ch == '=')
{
// We've reached a padding character; truncate the remainder.
return i - outputOffset;
Expand All @@ -226,7 +229,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o

/// <summary>
/// Get the minimum output <c>char[]</c> size required for encoding <paramref name="count"/>
/// <see cref="byte"/>s with the <see cref="Base64UrlEncode(byte[], int, char[], int, int)"/> method.
/// <see cref="byte"/>s with the <see cref="Base64UrlEncode(byte[], int, char[], int, int, bool)"/> method.
/// </summary>
/// <param name="count">The number of characters to encode.</param>
/// <returns>
Expand All @@ -243,8 +246,9 @@ public static int GetArraySizeRequiredToEncode(int count)
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <param name="omitPadding"></param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(ReadOnlySpan<byte> input)
public static string Base64UrlEncode(ReadOnlySpan<byte> input, bool omitPadding)
{
if (input.IsEmpty)
{
Expand All @@ -258,7 +262,7 @@ public static string Base64UrlEncode(ReadOnlySpan<byte> input)
? stackalloc char[bufferSize]
: bufferToReturnToPool = ArrayPool<char>.Shared.Rent(bufferSize);

var numBase64Chars = Base64UrlEncode(input, buffer);
var numBase64Chars = Base64UrlEncode(input, buffer, omitPadding);
var base64Url = new string(buffer.Slice(0, numBase64Chars));

if (bufferToReturnToPool != null)
Expand All @@ -269,7 +273,7 @@ public static string Base64UrlEncode(ReadOnlySpan<byte> input)
return base64Url;
}

private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output)
private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output, bool omitPadding)
{
Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length));

Expand All @@ -294,7 +298,7 @@ private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output)
{
output[i] = '_';
}
else if (ch == '=')
else if (omitPadding && ch == '=')
{
// We've reached a padding character; truncate the remainder.
return i;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Array/ArrayPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ static string StringFromJsValue(JsValue value)
return s;
}

using var sb = new ValueStringBuilder(stackalloc char[256]);
using var sb = new ValueStringBuilder();
sb.Append(s);
for (uint k = 1; k < len; k++)
{
Expand Down
6 changes: 5 additions & 1 deletion Jint/Native/JsTypedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ public JsValue this[uint index]

public uint Length => GetLength();

internal override uint GetLength() => IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered).TypedArrayLength;
internal override uint GetLength()
{
var record = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered);
return record.IsTypedArrayOutOfBounds ? 0 : record.TypedArrayLength;
}

internal override bool IsArrayLike => true;

Expand Down
20 changes: 10 additions & 10 deletions Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin
{
if (chunkLength == 1)
{
return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length."), read);
return new FromEncodingResult(bytes.ToArray(), 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(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length in strict mode."), read);
}
}

Expand All @@ -150,7 +150,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin
{
if (chunkLength < 2)
{
return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid '=' placement in base64 string."), read);
return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid '=' placement in base64 string."), read);
}

index = SkipAsciiWhitespace(input, index);
Expand All @@ -163,21 +163,21 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin
return new FromEncodingResult(bytes.ToArray(), Error: null, read);
}

return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 string termination."), read);
return new FromEncodingResult(bytes.ToArray(), 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);
return new FromEncodingResult(bytes.ToArray(), 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);
return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Extra characters after base64 string."), read);
}

DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits);
Expand All @@ -188,7 +188,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin
{
if (currentChar is '+' or '/')
{
return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid character in base64url string."), read);
return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid character in base64url string."), read);
}

if (currentChar == '-')
Expand All @@ -204,7 +204,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin

if (!Base64Alphabet.Contains(currentChar))
{
return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read);
return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read);
}

ulong remaining = maxLength - (ulong) bytes.Count;
Expand Down Expand Up @@ -318,7 +318,7 @@ internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLeng

if (length % 2 != 0)
{
return new FromEncodingResult(bytes, ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex string"), read);
return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex string"), read);
}

var byteIndex = 0;
Expand All @@ -327,7 +327,7 @@ internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLeng
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);
return new FromEncodingResult(bytes.AsSpan(0, byteIndex).ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex value"), read);
}

#if SUPPORTS_SPAN_PARSE
Expand Down
14 changes: 9 additions & 5 deletions Jint/Native/TypedArray/Uint8ArrayPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ private JsObject SetFromBase64(JsValue thisObject, JsValue[] arguments)

var byteLength = taRecord.TypedArrayLength;
var result = Uint8ArrayConstructor.FromBase64(_engine, s.ToString(), alphabet.ToString(), lastChunkHandling.ToString(), byteLength);
SetUint8ArrayBytes(into, result.Bytes);

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);
Expand Down Expand Up @@ -105,13 +105,13 @@ private JsObject SetFromHex(JsValue thisObject, JsValue[] arguments)

var byteLength = taRecord.TypedArrayLength;
var result = Uint8ArrayConstructor.FromHex(_engine, s.ToString(), byteLength);
SetUint8ArrayBytes(into, result.Bytes);

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);
Expand All @@ -136,10 +136,14 @@ private JsValue ToBase64(JsValue thisObject, JsValue[] arguments)
if (alphabet == "base64")
{
outAscii = Convert.ToBase64String(toEncode);
if (omitPadding)
{
outAscii = outAscii.TrimEnd('=');
}
}
else
{
outAscii = WebEncoders.Base64UrlEncode(toEncode);
outAscii = WebEncoders.Base64UrlEncode(toEncode, omitPadding);
}

return outAscii;
Expand Down

0 comments on commit 0ce19d8

Please sign in to comment.