diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs index 70390343c3405..89650f3e27a69 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs @@ -448,13 +448,13 @@ private int FindEndPosition inputForInnerLoop = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? - input.Slice(0, pos + CharsPerTimeoutCheck) : - input; + int innerLoopLength = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? + pos + CharsPerTimeoutCheck : + input.Length; bool done = currentState.NfaState is not null ? - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); // If the inner loop indicates that the search finished (for example due to reaching a deadend state) or // there is no more input available, then the whole search is done. @@ -466,7 +466,7 @@ private int FindEndPosition - private bool FindEndPositionDeltas(ReadOnlySpan input, RegexRunnerMode mode, + private bool FindEndPositionDeltas(ReadOnlySpan input, int length, RegexRunnerMode mode, ref int posRef, ref CurrentState state, ref int endPosRef, ref int endStateIdRef, ref int initialStatePosRef, ref int initialStatePosCandidateRef) where TStateHandler : struct, IStateHandler where TInputReader : struct, IInputReader @@ -561,7 +561,7 @@ private bool FindEndPositionDeltas= length || !TStateHandler.TryTakeTransition(this, ref state, positionId)) { return false; } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs index 9f73f9f1c30e8..b5b27236a56a7 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs @@ -1278,6 +1278,22 @@ public void Match_CachedPattern_NewTimeoutApplies(RegexOptions options) Assert.InRange(sw.Elapsed.TotalSeconds, 0, 10); // arbitrary upper bound that should be well above what's needed with a 1ms timeout } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + public void NonBacktracking_NoEndAnchorMatchAtTimeoutCheck() + { + // This constant must be at least as large as the one in the implementation that sets the maximum number + // of innermost loop iterations between timeout checks. + const int CharsToTriggerTimeoutCheck = 10000; + // Check that it is indeed large enough to trigger timeouts. If this fails the constant above needs to be larger. + Assert.Throws(() => new Regex("a*", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromTicks(1)) + .Match(new string('a', CharsToTriggerTimeoutCheck))); + + // The actual test: ^a*$ shouldn't match in a string ending in 'b' + Regex testPattern = new Regex("^a*$", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromHours(1)); + string input = string.Concat(new string('a', CharsToTriggerTimeoutCheck), 'b'); + Assert.False(testPattern.IsMatch(input)); + } + public static IEnumerable Match_Advanced_TestData() { foreach (RegexEngine engine in RegexHelpers.AvailableEngines)