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
38 changes: 23 additions & 15 deletions Jint/Native/RegExp/RegExpPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,6 @@ private JsValue Replace(JsValue thisObject, JsValue stringArg, JsValue replaceVa
string Evaluator(Match match)
{
var actualGroupCount = GetActualRegexGroupCount(rei, match);
var replacerArgs = new List<JsValue>(actualGroupCount + 2);
replacerArgs.Add(match.Value);

ObjectInstance? groups = null;
// Pre-initialize groups with unique names in order
Expand All @@ -277,10 +275,16 @@ string Evaluator(Match match)
}
}

// matched + captures + position + string + optional groups object; the array is freshly
// allocated and exactly JsValue[], so writes can skip the covariant store type check.
var replacerArgs = new JsValue[actualGroupCount + 2 + (groups is not null ? 1 : 0)];
var argIndex = 0;
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, match.Value);

for (var i = 1; i < actualGroupCount; i++)
{
var capture = match.Groups[i];
replacerArgs.Add(capture.Success ? capture.Value : Undefined);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, capture.Success ? capture.Value : Undefined);

var groupName = GetRegexGroupName(rei, i);
if (!string.IsNullOrWhiteSpace(groupName) && capture.Success)
Expand All @@ -289,11 +293,11 @@ string Evaluator(Match match)
}
}

replacerArgs.Add(match.Index);
replacerArgs.Add(s);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, match.Index);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, s);
if (groups is not null)
{
replacerArgs.Add(groups);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex, groups);
}

return CallFunctionalReplace(replaceValue, replacerArgs);
Expand Down Expand Up @@ -374,18 +378,22 @@ string Evaluator(Match match)
string replacement;
if (functionalReplace)
{
var replacerArgs = new List<JsValue>();
replacerArgs.Add(matched);
// matched + captures + position + string + optional named captures; the array is freshly
// allocated and exactly JsValue[], so writes can skip the covariant store type check.
var hasNamedCaptures = !namedCaptures.IsUndefined();
var replacerArgs = new JsValue[captures.Count + 3 + (hasNamedCaptures ? 1 : 0)];
var argIndex = 0;
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, matched);
foreach (var capture in captures)
{
replacerArgs.Add(capture);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, capture);
}

replacerArgs.Add(position);
replacerArgs.Add(s);
if (!namedCaptures.IsUndefined())
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, position);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex++, s);
if (hasNamedCaptures)
{
replacerArgs.Add(namedCaptures);
Arguments.WriteNoTypeCheck(replacerArgs, argIndex, namedCaptures);
}

replacement = CallFunctionalReplace(replaceValue, replacerArgs);
Expand Down Expand Up @@ -422,9 +430,9 @@ string Evaluator(Match match)
#pragma warning restore CA1845
}

private static string CallFunctionalReplace(JsValue replacer, List<JsValue> replacerArgs)
private static string CallFunctionalReplace(JsValue replacer, JsCallArguments replacerArgs)
{
var result = ((ICallable) replacer).Call(Undefined, replacerArgs.ToArray());
var result = ((ICallable) replacer).Call(Undefined, replacerArgs);
return TypeConverter.ToString(result);
}

Expand Down
34 changes: 34 additions & 0 deletions Jint/Runtime/Arguments.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
#if NET8_0_OR_GREATER
using System.Runtime.InteropServices;
#endif
using Jint.Native;

namespace Jint.Runtime;
Expand Down Expand Up @@ -31,6 +35,36 @@ public static JsValue At(this JsValue[] args, int index)
return At(args, index, JsValue.Undefined);
}

/// <summary>
/// Writes <paramref name="value"/> to <paramref name="array"/> without the covariant array store
/// type check (stelemref). Because <see cref="JsValue"/> is not sealed, a plain <c>array[index] = value</c>
/// must verify at runtime that the array's actual element type accepts the value; that check is pure
/// overhead when the array is known to be exactly <see cref="JsValue"/>[]. Bounds are checked explicitly.
/// </summary>
/// <remarks>
/// Callers MUST guarantee that <paramref name="array"/> is exactly of type <see cref="JsValue"/>[] —
/// freshly allocated via <c>new JsValue[n]</c> or rented from <see cref="Jint.Pooling.JsValueArrayPool"/>
/// (whose factories only ever create such arrays). Writing through this helper into an array whose
/// element type is a subtype of <see cref="JsValue"/> would break array type safety. In particular,
/// never use it on caller-supplied <see cref="JsCallArguments"/> whose provenance is unknown.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WriteNoTypeCheck(JsValue[] array, int index, JsValue value)
{
Debug.Assert(array.GetType() == typeof(JsValue[]), "array must be exactly JsValue[]");

if ((uint) index >= (uint) array.Length)
{
Throw.ArgumentOutOfRangeException(nameof(index), "Index was outside the bounds of the array.");
}

#if NET8_0_OR_GREATER
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index) = value;
#else
array[index] = value;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsCallArguments Skip(this JsValue[] args, int count)
{
Expand Down
7 changes: 6 additions & 1 deletion Jint/Runtime/Interpreter/Expressions/ExpressionCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,18 @@ public JsValue[] ArgumentListEvaluation(EvaluationContext context, object key, o
return arguments;
}

/// <summary>
/// Evaluates argument expressions into <paramref name="targetArray"/>, which callers must have
/// allocated via <c>new JsValue[n]</c> or rented from the engine's <see cref="Pooling.JsValueArrayPool"/>
/// so that it is exactly <see cref="JsValue"/>[] (see <see cref="Arguments.WriteNoTypeCheck"/>).
/// </summary>
internal int BuildArguments(EvaluationContext context, JsValue[] targetArray, int startIndex = 0)
{
var expressions = _expressions;
int i = startIndex;
for (; (uint) i < (uint) expressions.Length; i++)
{
targetArray[i] = GetValue(context, expressions[i])!;
Arguments.WriteNoTypeCheck(targetArray, i, GetValue(context, expressions[i])!);

// Check for generator suspension after each expression evaluation
// This is needed because yield expressions return normally instead of throwing
Expand Down