File: System\Text\RegularExpressions\Regex.Timeout.cs
Web Access
Project: src\src\libraries\System.Text.RegularExpressions\src\System.Text.RegularExpressions.csproj (System.Text.RegularExpressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Threading;
 
namespace System.Text.RegularExpressions
{
    public partial class Regex
    {
        /// <summary>The maximum allowed timeout duration.</summary>
        /// <remarks>
        /// Previously the timeout was based on Environment.TickCount, which can overflow. The implementation now uses Environment.TickCount64,
        /// such that this constraint could be relaxed in the future if desired.
        /// </remarks>
        private const ulong MaximumMatchTimeoutTicks = 10_000UL * (int.MaxValue - 1); // TimeSpan.FromMilliseconds(int.MaxValue - 1).Ticks;
 
        /// <summary>Name of the AppContext slot that may be set to a default timeout override.</summary>
        /// <remarks>
        /// Setting this to a valid TimeSpan will cause that TimeSpan to be used as the timeout for <see cref="Regex"/> instances created
        /// without a timeout. If a timeout is explicitly provided, even if it's infinite, this value will be ignored.
        /// </remarks>
        private const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
 
        /// <summary>Number of ticks represented by <see cref="InfiniteMatchTimeout"/>.</summary>
        private const long InfiniteMatchTimeoutTicks = -10_000; // InfiniteMatchTimeout.Ticks
 
        // Historical note:
        // Regex.InfiniteMatchTimeout was created instead of Timeout.InfiniteTimeSpan because of:
        // 1) Concerns about a connection between Regex and multi-threading.
        // 2) Concerns around requiring an extra contract assembly reference to access Timeout.
        // Neither of these would motivate such an addition now, but the API exists.
 
        /// <summary>Specifies that a pattern-matching operation should not time out.</summary>
        /// <remarks>
        /// <para>
        /// The <see cref="Regex(string, RegexOptions, TimeSpan)"/> constructor and a number of static matching
        /// methods use the <see cref="InfiniteMatchTimeout"/> constant to indicate that the attempt to find a
        /// pattern match should not time out.
        /// </para>
        /// <para>
        /// Setting the regular expression engine's time-out value to <see cref="InfiniteMatchTimeout"/> can
        /// cause regular expressions that rely on excessive backtracking to appear to stop responding when
        /// processing text that nearly matches the regular expression pattern. If you disable time-outs, you
        /// should ensure that your regular expression does not rely on excessive backtracking and that it
        /// handles text that nearly matches the regular expression pattern. For more information about handling
        /// backtracking, see
        /// <see href="https://learn.microsoft.com/dotnet/standard/base-types/backtracking-in-regular-expressions">Backtracking</see>.
        /// </para>
        /// </remarks>
        public static readonly TimeSpan InfiniteMatchTimeout = Timeout.InfiniteTimeSpan;
 
        /// <summary>
        /// The default timeout value to use if one isn't explicitly specified when creating the <see cref="Regex"/>
        /// or using its static methods (which implicitly create one if one can't be found in the cache).
        /// </summary>
        /// <remarks>
        /// The default defaults to <see cref="InfiniteMatchTimeout"/> but can be overridden by setting
        /// the <see cref="DefaultMatchTimeout_ConfigKeyName"/> <see cref="AppContext"/> slot.
        /// </remarks>
        internal static readonly TimeSpan s_defaultMatchTimeout = InitDefaultMatchTimeout();
 
        /// <summary>
        /// The maximum amount of time that can elapse in a pattern-matching operation before the operation
        /// times out.
        /// </summary>
        protected internal TimeSpan internalMatchTimeout;
 
        /// <summary>Gets the time-out interval of the current instance.</summary>
        /// <value>
        /// The maximum time interval that can elapse in a pattern-matching operation before a
        /// <see cref="RegexMatchTimeoutException"/> is thrown, or <see cref="InfiniteMatchTimeout"/> if
        /// time-outs are disabled.
        /// </value>
        /// <remarks>
        /// <para>
        /// The <see cref="MatchTimeout"/> property defines the approximate maximum time interval for a
        /// <see cref="Regex"/> instance to execute a single matching operation before the operation times out.
        /// The regular expression engine throws a <see cref="RegexMatchTimeoutException"/> exception during
        /// its next timing check after the time-out interval has elapsed. This prevents the regular expression
        /// engine from processing input strings that require excessive backtracking. For more information, see
        /// <see href="https://learn.microsoft.com/dotnet/standard/base-types/backtracking-in-regular-expressions">Backtracking</see>
        /// and
        /// <see href="https://learn.microsoft.com/dotnet/standard/base-types/best-practices">Best Practices for Regular Expressions</see>.
        /// </para>
        /// <para>
        /// This property is read-only. You can set its value explicitly for an individual <see cref="Regex"/>
        /// object by calling the <see cref="Regex(string, RegexOptions, TimeSpan)"/> constructor; and you can
        /// set its value for all <see cref="Regex"/> matching operations in an application domain by calling
        /// the <see cref="AppDomain.SetData(string, object?)"/> method and providing a <see cref="TimeSpan"/>
        /// value for the "REGEX_DEFAULT_MATCH_TIMEOUT" property.
        /// </para>
        /// <para>
        /// If you do not explicitly set a time-out interval, the default value
        /// <see cref="InfiniteMatchTimeout"/> is used, and matching operations do not time out.
        /// </para>
        /// </remarks>
        public TimeSpan MatchTimeout => internalMatchTimeout;
 
        /// <summary>Gets the default matching timeout value.</summary>
        /// <remarks>
        /// The default is queried from <code>AppContext</code>. If the AppContext'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 AppContext's data value for that key is <code>null</code>, an infinite timeout is returned.
        /// </remarks>
        private static TimeSpan InitDefaultMatchTimeout()
        {
            object? defaultTimeout = AppContext.GetData(DefaultMatchTimeout_ConfigKeyName);
 
            if (defaultTimeout is not TimeSpan defaultMatchTimeOut)
            {
                // If not default was specified in AppContext, default to infinite.
                if (defaultTimeout is null)
                {
                    return InfiniteMatchTimeout;
                }
 
                // If a default was specified that's not a TimeSpan, throw.
                throw new InvalidCastException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultTimeout));
            }
 
            // If default timeout is outside the valid range, throw. As this is used in the static cctor, it will result in a TypeInitializationException
            // for all subsequent Regex use.
            try
            {
                ValidateMatchTimeout(defaultMatchTimeOut);
            }
            catch (ArgumentOutOfRangeException)
            {
                throw new ArgumentOutOfRangeException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeOut));
            }
 
            return defaultMatchTimeOut;
        }
    }
}