using System.Reflection;
using Jint;
using Jint.Native;
using XFESystem.Collections;
namespace XFESystem.JintCleanupPatch.Tests;
public sealed class RegexSearchTests
{
private static long Hits(JsValue value) => (long)typeof(JsRegExp).GetField("_xfeNoMatchHits", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(value)!;
private static Engine Create() => new(options => { options.DisableStringCompilation(); options.Constraints.RegexTimeout = TimeSpan.FromMilliseconds(50); });
[Fact]
public void RepeatedFailedSuffixSkipsSearchButEarlierStartStillFindsMatches()
{
var engine = Create();
try
{
engine.Execute("const input='baaa',r=/b/gu;r.lastIndex=1;r.exec(input);r.lastIndex=2;r.exec(input)");
Assert.Equal(1, Hits(engine.Evaluate("r")));
Assert.Equal("b,0,1", engine.Evaluate("r.lastIndex=0;const m=r.exec(input);[m[0],m.index,r.lastIndex].join(',')").AsString());
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Theory]
[InlineData("const r=/a/uy;r.exec('ba');r.lastIndex=1;const m=r.exec('ba');[m[0],m.index,r.lastIndex].join(',')", "a,1,2")]
[InlineData("const r=/z/gu;r.exec('aaa');r.lastIndex=2;const a=r.exec('aaaz');[a[0],a.index,r.lastIndex].join(',')", "z,3,4")]
[InlineData("const r=/z/gu;r.exec('aaa');r.compile('a','gu');const m=r.exec('aaa');[m[0],m.index,r.lastIndex].join(',')", "a,0,1")]
[InlineData("const r=/z/gu;r.exec('aaa');r.exec=function(){return ['custom']};r.exec('aaa')[0]", "custom")]
[InlineData("const r=/(?<x>a)|(b)/dgu;r.exec('cc');r.lastIndex=0;const a=r.exec('ab');a[0]='changed';r.lastIndex=1;const b=r.exec('ab');JSON.stringify([b[0],b[1],b[2],b.groups.x,b.indices[0],r.lastIndex])", "[\"b\",null,\"b\",null,[1,2],2]")]
[InlineData("const r=/(?<=😀)x/gu;r.exec('😀y');r.lastIndex=1;r.exec('😀y');r.lastIndex=1;const m=r.exec('😀x');[m[0],m.index,r.lastIndex].join(',')", "x,2,3")]
[InlineData("const r=/z/gu;r.exec('aaa');let reads=0;r.lastIndex={valueOf(){reads++;return 1}};r.exec('aaa');[reads,r.lastIndex].join(',')", "1,0")]
public void StickyInputCompileCapturesUnicodeAndObservableLastIndexKeepOriginalSemantics(string source, string expected)
{
var engine = Create();
try { Assert.Equal(expected, engine.Evaluate(source).AsString()); }
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Fact]
public void RetainedInputIsBoundedAndRegexInstancesDoNotShareSearchFacts()
{
var engine = Create();
try
{
engine.Execute("const input='a'.repeat(131073),r=/z/u;r.exec(input);r.exec(input);const other=/z/u;other.exec('aaa')");
Assert.Equal(0, Hits(engine.Evaluate("r")));
Assert.Null(typeof(JsRegExp).GetField("_xfeNoMatchInput", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(engine.Evaluate("r")));
Assert.Equal(0, Hits(engine.Evaluate("other")));
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Fact]
public void TimedOutSearchesRemainFailuresAndAreNeverMemoized()
{
var engine = Create();
try
{
engine.Execute("const r=/(a+)+$/u,input='a'.repeat(32)+'!'");
for (int attempt = 0; attempt < 2; attempt++)
Assert.Throws<System.Text.RegularExpressions.RegexMatchTimeoutException>(() => engine.Evaluate("r.exec(input)"));
Assert.Equal(0, Hits(engine.Evaluate("r")));
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
}
using System.Reflection;
using Jint;
using Jint.Native;
using XFESystem.Collections;
namespace XFESystem.JintCleanupPatch.Tests;
public sealed class RegexSearchTests
{
private static long Hits(JsValue value) => (long)typeof(JsRegExp).GetField("_xfeNoMatchHits", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(value)!;
private static Engine Create() => new(options => { options.DisableStringCompilation(); options.Constraints.RegexTimeout = TimeSpan.FromMilliseconds(50); });
[Fact]
public void RepeatedFailedSuffixSkipsSearchButEarlierStartStillFindsMatches()
{
var engine = Create();
try
{
engine.Execute("const input='baaa',r=/b/gu;r.lastIndex=1;r.exec(input);r.lastIndex=2;r.exec(input)");
Assert.Equal(1, Hits(engine.Evaluate("r")));
Assert.Equal("b,0,1", engine.Evaluate("r.lastIndex=0;const m=r.exec(input);[m[0],m.index,r.lastIndex].join(',')").AsString());
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Theory]
[InlineData("const r=/a/uy;r.exec('ba');r.lastIndex=1;const m=r.exec('ba');[m[0],m.index,r.lastIndex].join(',')", "a,1,2")]
[InlineData("const r=/z/gu;r.exec('aaa');r.lastIndex=2;const a=r.exec('aaaz');[a[0],a.index,r.lastIndex].join(',')", "z,3,4")]
[InlineData("const r=/z/gu;r.exec('aaa');r.compile('a','gu');const m=r.exec('aaa');[m[0],m.index,r.lastIndex].join(',')", "a,0,1")]
[InlineData("const r=/z/gu;r.exec('aaa');r.exec=function(){return ['custom']};r.exec('aaa')[0]", "custom")]
[InlineData("const r=/(?<x>a)|(b)/dgu;r.exec('cc');r.lastIndex=0;const a=r.exec('ab');a[0]='changed';r.lastIndex=1;const b=r.exec('ab');JSON.stringify([b[0],b[1],b[2],b.groups.x,b.indices[0],r.lastIndex])", "[\"b\",null,\"b\",null,[1,2],2]")]
[InlineData("const r=/(?<=😀)x/gu;r.exec('😀y');r.lastIndex=1;r.exec('😀y');r.lastIndex=1;const m=r.exec('😀x');[m[0],m.index,r.lastIndex].join(',')", "x,2,3")]
[InlineData("const r=/z/gu;r.exec('aaa');let reads=0;r.lastIndex={valueOf(){reads++;return 1}};r.exec('aaa');[reads,r.lastIndex].join(',')", "1,0")]
public void StickyInputCompileCapturesUnicodeAndObservableLastIndexKeepOriginalSemantics(string source, string expected)
{
var engine = Create();
try { Assert.Equal(expected, engine.Evaluate(source).AsString()); }
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Fact]
public void RetainedInputIsBoundedAndRegexInstancesDoNotShareSearchFacts()
{
var engine = Create();
try
{
engine.Execute("const input='a'.repeat(131073),r=/z/u;r.exec(input);r.exec(input);const other=/z/u;other.exec('aaa')");
Assert.Equal(0, Hits(engine.Evaluate("r")));
Assert.Null(typeof(JsRegExp).GetField("_xfeNoMatchInput", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(engine.Evaluate("r")));
Assert.Equal(0, Hits(engine.Evaluate("other")));
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
[Fact]
public void TimedOutSearchesRemainFailuresAndAreNeverMemoized()
{
var engine = Create();
try
{
engine.Execute("const r=/(a+)+$/u,input='a'.repeat(32)+'!'");
for (int attempt = 0; attempt < 2; attempt++)
Assert.Throws<System.Text.RegularExpressions.RegexMatchTimeoutException>(() => engine.Evaluate("r.exec(input)"));
Assert.Equal(0, Hits(engine.Evaluate("r")));
}
finally { ((IWeakCollectionOwner)(object)engine).WeakCollections.Release(); }
}
}