<data name="IllegalCondition" xml:space="preserve">
<value>Illegal conditional (?(...)) expression.</value>
</data>
+ <data name="IllegalDefaultRegexMatchTimeoutInAppDomain" xml:space="preserve">
+ <value>AppDomain data '{0}' contains the invalid value or object '{1}' for specifying a default matching timeout for System.Text.RegularExpressions.Regex.</value>
+ </data>
<data name="IllegalEndEscape" xml:space="preserve">
<value>Illegal \\ at end of pattern.</value>
</data>
<data name="UnterminatedComment" xml:space="preserve">
<value>Unterminated (?#...) comment.</value>
</data>
-</root>
\ No newline at end of file
+</root>
protected internal TimeSpan internalMatchTimeout; // timeout for the execution of this regex
+ // During static initialisation of Regex we check
+ private const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
+
// DefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
// by one means or another. Typically, it is set to InfiniteMatchTimeout.
- internal static readonly TimeSpan DefaultMatchTimeout = InfiniteMatchTimeout;
+ internal static readonly TimeSpan DefaultMatchTimeout = InitDefaultMatchTimeout();
// *********** } match timeout fields ***********
}
/// <summary>
+ /// Specifies the default RegEx matching timeout value (i.e. the timeout that will be used if no
+ /// explicit timeout is specified).
+ /// The default is queried from the current <code>AppDomain</code>.
+ /// If the AddDomain's data value for that key is not a <code>TimeSpan</code> value or if it is outside the
+ /// valid range, an exception is thrown.
+ /// If the AddDomain's data value for that key is <code>null</code>, a fallback value is returned.
+ /// </summary>
+ /// <returns>The default RegEx matching timeout for this AppDomain</returns>
+ private static TimeSpan InitDefaultMatchTimeout()
+ {
+ // Query AppDomain
+ AppDomain ad = AppDomain.CurrentDomain;
+ object defaultMatchTimeoutObj = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
+
+ // If no default is specified, use fallback
+ if (defaultMatchTimeoutObj == null)
+ {
+ return InfiniteMatchTimeout;
+ }
+
+ if (defaultMatchTimeoutObj is TimeSpan defaultMatchTimeOut)
+ {
+ // If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
+ try
+ {
+ ValidateMatchTimeout(defaultMatchTimeOut);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ throw new ArgumentOutOfRangeException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeOut));
+ }
+
+ return defaultMatchTimeOut;
+ }
+
+ throw new InvalidCastException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeoutObj));
+ }
+
+ /// <summary>
/// Escapes a minimal set of metacharacters (\, *, +, ?, |, {, [, (, ), ^, $, ., #, and
/// whitespace) by replacing them with their \ codes. This converts a string so that
/// it can be used as a constant within a regular expression safely. (Note that the
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Xunit;
namespace System.Text.RegularExpressions.Tests
{
- public class RegexConstructorTests
+ public class RegexConstructorTests : RemoteExecutorTestBase
{
public static IEnumerable<object[]> Ctor_TestData()
{
}
[Fact]
+ public static void StaticCtor_InvalidTimeoutObject_ExceptionThrown()
+ {
+ RemoteInvoke(() =>
+ {
+ AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, true);
+ Assert.Throws<TypeInitializationException>(() => Regex.InfiniteMatchTimeout);
+
+ return SuccessExitCode;
+ });
+ }
+
+ [Fact]
+ public static void StaticCtor_InvalidTimeoutRange_ExceptionThrown()
+ {
+ RemoteInvoke(() =>
+ {
+ AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, TimeSpan.Zero);
+ Assert.Throws<TypeInitializationException>(() => Regex.InfiniteMatchTimeout);
+
+ return SuccessExitCode;
+ });
+ }
+
+ [Fact]
public void CacheSize_Get()
{
Assert.Equal(15, Regex.CacheSize);
Assert.Equal("a", match.Value);
}
+ [Fact]
+ public void Match_Timeout_Throws()
+ {
+ RemoteInvoke(() =>
+ {
+ 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})$";
+ string input = new string('a', 50) + "@a.a";
+
+ AppDomain.CurrentDomain.SetData(RegexHelpers.DefaultMatchTimeout_ConfigKeyName, TimeSpan.FromMilliseconds(100));
+ Assert.Throws<RegexMatchTimeoutException>(() => new Regex(Pattern).Match(input));
+
+ return SuccessExitCode;
+ });
+ }
+
public static IEnumerable<object[]> Match_Advanced_TestData()
{
// \B special character escape: ".*\\B(SUCCESS)\\B.*"
bool isDefaultCount = RegexHelpers.IsDefaultStart(input, options, length);
if (options == RegexOptions.None)
{
- if (isDefaultStart && isDefaultCount)
+ if (isDefaultStart && isDefaultCount)
{
// Use Match(string) or Match(string, string)
VerifyMatch(new Regex(pattern).Match(input), true, expected);
{
public static class RegexHelpers
{
+ public const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
+
public static bool IsDefaultCount(string input, RegexOptions options, int count)
{
if ((options & RegexOptions.RightToLeft) != 0)