Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 741cd3c

Browse files
committed
backport of REGEX_DEFAULT_MATCH_TIMEOUT (#23666)
* backport of REGEX_DEFAULT_MATCH_TIMEOUT * Add more exception tests && improve exception logging
1 parent 0d659f4 commit 741cd3c

File tree

5 files changed

+91
-4
lines changed

5 files changed

+91
-4
lines changed

src/System.Text.RegularExpressions/src/Resources/Strings.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
<data name="IllegalCondition" xml:space="preserve">
8989
<value>Illegal conditional (?(...)) expression.</value>
9090
</data>
91+
<data name="IllegalDefaultRegexMatchTimeoutInAppDomain" xml:space="preserve">
92+
<value>AppDomain data '{0}' contains the invalid value or object '{1}' for specifying a default matching timeout for System.Text.RegularExpressions.Regex.</value>
93+
</data>
9194
<data name="IllegalEndEscape" xml:space="preserve">
9295
<value>Illegal \\ at end of pattern.</value>
9396
</data>
@@ -193,4 +196,4 @@
193196
<data name="UnterminatedComment" xml:space="preserve">
194197
<value>Unterminated (?#...) comment.</value>
195198
</data>
196-
</root>
199+
</root>

src/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@ public class Regex : ISerializable
4242

4343
protected internal TimeSpan internalMatchTimeout; // timeout for the execution of this regex
4444

45+
// During static initialisation of Regex we check
46+
private const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
47+
4548
// DefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
4649
// by one means or another. Typically, it is set to InfiniteMatchTimeout.
47-
internal static readonly TimeSpan DefaultMatchTimeout = InfiniteMatchTimeout;
50+
internal static readonly TimeSpan DefaultMatchTimeout = InitDefaultMatchTimeout();
4851

4952
// *********** } match timeout fields ***********
5053

@@ -236,6 +239,45 @@ protected internal static void ValidateMatchTimeout(TimeSpan matchTimeout)
236239
throw new ArgumentOutOfRangeException(nameof(matchTimeout));
237240
}
238241

242+
/// <summary>
243+
/// Specifies the default RegEx matching timeout value (i.e. the timeout that will be used if no
244+
/// explicit timeout is specified).
245+
/// The default is queried from the current <code>AppDomain</code>.
246+
/// If the AddDomain's data value for that key is not a <code>TimeSpan</code> value or if it is outside the
247+
/// valid range, an exception is thrown.
248+
/// If the AddDomain's data value for that key is <code>null</code>, a fallback value is returned.
249+
/// </summary>
250+
/// <returns>The default RegEx matching timeout for this AppDomain</returns>
251+
private static TimeSpan InitDefaultMatchTimeout()
252+
{
253+
// Query AppDomain
254+
AppDomain ad = AppDomain.CurrentDomain;
255+
object defaultMatchTimeoutObj = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
256+
257+
// If no default is specified, use fallback
258+
if (defaultMatchTimeoutObj == null)
259+
{
260+
return InfiniteMatchTimeout;
261+
}
262+
263+
if (defaultMatchTimeoutObj is TimeSpan defaultMatchTimeOut)
264+
{
265+
// If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
266+
try
267+
{
268+
ValidateMatchTimeout(defaultMatchTimeOut);
269+
}
270+
catch (ArgumentOutOfRangeException)
271+
{
272+
throw new ArgumentOutOfRangeException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeOut));
273+
}
274+
275+
return defaultMatchTimeOut;
276+
}
277+
278+
throw new InvalidCastException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeoutObj));
279+
}
280+
239281
/// <summary>
240282
/// Escapes a minimal set of metacharacters (\, *, +, ?, |, {, [, (, ), ^, $, ., #, and
241283
/// whitespace) by replacing them with their \ codes. This converts a string so that

src/System.Text.RegularExpressions/tests/Regex.Ctor.Tests.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Runtime.InteropServices;
78
using System.Threading;
89
using Xunit;
910

1011
namespace System.Text.RegularExpressions.Tests
1112
{
12-
public class RegexConstructorTests
13+
public class RegexConstructorTests : RemoteExecutorTestBase
1314
{
1415
public static IEnumerable<object[]> Ctor_TestData()
1516
{
@@ -75,6 +76,30 @@ public static void Ctor_Invalid()
7576
AssertExtensions.Throws<ArgumentOutOfRangeException>("matchTimeout", () => new Regex("foo", RegexOptions.None, TimeSpan.FromMilliseconds(int.MaxValue)));
7677
}
7778

79+
[Fact]
80+
public static void StaticCtor_InvalidTimeoutObject_ExceptionThrown()
81+
{
82+
RemoteInvoke(() =>
83+
{
84+
AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, true);
85+
Assert.Throws<TypeInitializationException>(() => Regex.InfiniteMatchTimeout);
86+
87+
return SuccessExitCode;
88+
});
89+
}
90+
91+
[Fact]
92+
public static void StaticCtor_InvalidTimeoutRange_ExceptionThrown()
93+
{
94+
RemoteInvoke(() =>
95+
{
96+
AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, TimeSpan.Zero);
97+
Assert.Throws<TypeInitializationException>(() => Regex.InfiniteMatchTimeout);
98+
99+
return SuccessExitCode;
100+
});
101+
}
102+
78103
[Fact]
79104
public void CacheSize_Get()
80105
{

src/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,21 @@ public void Match_Timeout()
328328
Assert.Equal("a", match.Value);
329329
}
330330

331+
[Fact]
332+
public void Match_Timeout_Throws()
333+
{
334+
RemoteInvoke(() =>
335+
{
336+
const string Pattern = @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\w]*[0-9a-zA-Z])*\.)+[a-zA-Z]{2,9})$";
337+
string input = new string('a', 50) + "@a.a";
338+
339+
AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, TimeSpan.FromMilliseconds(100));
340+
Assert.Throws<RegexMatchTimeoutException>(() => new Regex(Pattern).Match(input));
341+
342+
return SuccessExitCode;
343+
});
344+
}
345+
331346
public static IEnumerable<object[]> Match_Advanced_TestData()
332347
{
333348
// \B special character escape: ".*\\B(SUCCESS)\\B.*"
@@ -596,7 +611,7 @@ public void Match(string pattern, string input, RegexOptions options, int beginn
596611
bool isDefaultCount = RegexHelpers.IsDefaultStart(input, options, length);
597612
if (options == RegexOptions.None)
598613
{
599-
if (isDefaultStart && isDefaultCount)
614+
if (isDefaultStart && isDefaultCount)
600615
{
601616
// Use Match(string) or Match(string, string)
602617
VerifyMatch(new Regex(pattern).Match(input), true, expected);

src/System.Text.RegularExpressions/tests/Regex.Tests.Common.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace System.Text.RegularExpressions.Tests
66
{
77
public static class RegexHelpers
88
{
9+
public const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
10+
911
public static bool IsDefaultCount(string input, RegexOptions options, int count)
1012
{
1113
if ((options & RegexOptions.RightToLeft) != 0)

0 commit comments

Comments
 (0)