Skip to content

Commit 7db092a

Browse files
Add a comment in source-generated regexes explaining why they cannot have optimized code. (#66114)
* Add a comment in source-generated regexes explaining why they cannot have optimized code. * Apply suggestions from code review Co-authored-by: Stephen Toub <[email protected]> Co-authored-by: Stephen Toub <[email protected]>
1 parent b410984 commit 7db092a

File tree

3 files changed

+24
-11
lines changed

3 files changed

+24
-11
lines changed

src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ static uint ComputeStringHash(string s)
103103
}
104104

105105
/// <summary>Gets whether a given regular expression method is supported by the code generator.</summary>
106-
private static bool SupportsCodeGeneration(RegexMethod rm)
106+
private static bool SupportsCodeGeneration(RegexMethod rm, out string? reason)
107107
{
108108
RegexNode root = rm.Tree.Root;
109109

110-
if (!root.SupportsCompilation())
110+
if (!root.SupportsCompilation(out reason))
111111
{
112112
return false;
113113
}
@@ -119,6 +119,7 @@ private static bool SupportsCodeGeneration(RegexMethod rm)
119119
// Place an artificial limit on max tree depth in order to mitigate such issues.
120120
// The allowed depth can be tweaked as needed;its exceedingly rare to find
121121
// expressions with such deep trees.
122+
reason = "the regex will result in code that may exceed C# compiler limits";
122123
return false;
123124
}
124125

@@ -163,8 +164,10 @@ private static ImmutableArray<Diagnostic> EmitRegexMethod(IndentedTextWriter wri
163164
writer.Write(" public static global::System.Text.RegularExpressions.Regex Instance { get; } = ");
164165

165166
// If we can't support custom generation for this regex, spit out a Regex constructor call.
166-
if (!SupportsCodeGeneration(rm))
167+
if (!SupportsCodeGeneration(rm, out string? reason))
167168
{
169+
writer.WriteLine();
170+
writer.WriteLine($"// Cannot generate Regex-derived implementation because {reason}.");
168171
writer.WriteLine($"new global::System.Text.RegularExpressions.Regex({patternExpression}, {optionsExpression}, {timeoutExpression});");
169172
writer.WriteLine("}");
170173
return ImmutableArray.Create(Diagnostic.Create(DiagnosticDescriptors.LimitedSourceGeneration, rm.MethodSyntax.GetLocation()));

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal sealed class RegexLWCGCompiler : RegexCompiler
3232
/// <summary>The top-level driver. Initializes everything then calls the Generate* methods.</summary>
3333
public RegexRunnerFactory? FactoryInstanceFromCode(string pattern, RegexTree regexTree, RegexOptions options, bool hasTimeout)
3434
{
35-
if (!regexTree.Root.SupportsCompilation())
35+
if (!regexTree.Root.SupportsCompilation(out _))
3636
{
3737
return null;
3838
}

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,7 @@ static RegexNode ExtractCommonPrefixText(RegexNode alternation)
12231223

12241224
// Now compare the rest of the branches against it.
12251225
int endingIndex = startingIndex + 1;
1226-
for ( ; endingIndex < children.Count; endingIndex++)
1226+
for (; endingIndex < children.Count; endingIndex++)
12271227
{
12281228
// Get the starting node of the next branch.
12291229
startingNode = children[endingIndex].FindBranchOneOrMultiStart();
@@ -2566,32 +2566,42 @@ public int ChildCount()
25662566
}
25672567

25682568
// Determines whether the node supports a compilation / code generation strategy based on walking the node tree.
2569-
internal bool SupportsCompilation()
2569+
// Also returns a human-readable string to explain the reason (it will be emitted by the source generator, hence
2570+
// there's no need to localize).
2571+
internal bool SupportsCompilation([NotNullWhen(false)] out string? reason)
25702572
{
25712573
if (!StackHelper.TryEnsureSufficientExecutionStack())
25722574
{
2573-
// If we can't recur further, code generation isn't supported as the tree is too deep.
2575+
reason = "run-time limits were exceeded";
25742576
return false;
25752577
}
25762578

2577-
if ((Options & (RegexOptions.RightToLeft | RegexOptions.NonBacktracking)) != 0)
2579+
// NonBacktracking isn't supported, nor RightToLeft. The latter applies to both the top-level
2580+
// options as well as when used to specify positive and negative lookbehinds.
2581+
if ((Options & RegexOptions.NonBacktracking) != 0)
2582+
{
2583+
reason = "RegexOptions.NonBacktracking was specified";
2584+
return false;
2585+
}
2586+
2587+
if ((Options & RegexOptions.RightToLeft) != 0)
25782588
{
2579-
// NonBacktracking isn't supported, nor RightToLeft. The latter applies to both the top-level
2580-
// options as well as when used to specify positive and negative lookbehinds.
2589+
reason = "RegexOptions.RightToLeft or a positive/negative lookbehind was used";
25812590
return false;
25822591
}
25832592

25842593
int childCount = ChildCount();
25852594
for (int i = 0; i < childCount; i++)
25862595
{
25872596
// The node isn't supported if any of its children aren't supported.
2588-
if (!Child(i).SupportsCompilation())
2597+
if (!Child(i).SupportsCompilation(out reason))
25892598
{
25902599
return false;
25912600
}
25922601
}
25932602

25942603
// Supported.
2604+
reason = null;
25952605
return true;
25962606
}
25972607

0 commit comments

Comments
 (0)