Skip to content

Commit 9ba1b85

Browse files
More stack overflow avoidance in regex NonBacktracking (dotnet#60786)
* Add stackoverflow avoidance to SymbolicRegexNode Remove unused Restrict methods instead of adding avoidance. Co-authored-by: Stephen Toub <stoub@microsoft.com> * Add more stack overflow avoidance in regex This follows a full audit of the NonBacktracking code. * Apply suggestions from code review Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 3e94696 commit 9ba1b85

7 files changed

Lines changed: 139 additions & 78 deletions

File tree

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/Algebras/MintermGenerator.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Collections.Generic;
55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
using System.Threading;
79

810
namespace System.Text.RegularExpressions.Symbolic
911
{
@@ -114,6 +116,12 @@ private PartitionTree(IBooleanAlgebra<TPredicate> solver, TPredicate pred, Parti
114116

115117
internal void Refine(TPredicate other)
116118
{
119+
if (!StackHelper.TryEnsureSufficientExecutionStack())
120+
{
121+
StackHelper.CallOnEmptyStack(Refine, other);
122+
return;
123+
}
124+
117125
if (_left is null && _right is null)
118126
{
119127
// If this is a leaf node create left and/or right children for the new predicate
@@ -187,6 +195,12 @@ internal void Refine(TPredicate other)
187195
/// </summary>
188196
private void ExtendLeft()
189197
{
198+
if (!StackHelper.TryEnsureSufficientExecutionStack())
199+
{
200+
StackHelper.CallOnEmptyStack(ExtendLeft);
201+
return;
202+
}
203+
190204
if (_left is null && _right is null)
191205
{
192206
_left = new PartitionTree(_solver, _pred, null, null);
@@ -204,6 +218,12 @@ private void ExtendLeft()
204218
/// </summary>
205219
private void ExtendRight()
206220
{
221+
if (!StackHelper.TryEnsureSufficientExecutionStack())
222+
{
223+
StackHelper.CallOnEmptyStack(ExtendRight);
224+
return;
225+
}
226+
207227
if (_left is null && _right is null)
208228
{
209229
_right = new PartitionTree(_solver, _pred, null, null);

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/RegexNodeToSymbolicConverter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public RegexNodeToSymbolicConverter(Unicode.UnicodeCategoryTheory<BDD> categoriz
3131

3232
private BDD CreateConditionFromSet(bool ignoreCase, string set)
3333
{
34+
if (!StackHelper.TryEnsureSufficientExecutionStack())
35+
{
36+
return StackHelper.CallOnEmptyStack(CreateConditionFromSet, ignoreCase, set);
37+
}
38+
3439
(bool ignoreCase, string set) key = (ignoreCase, set);
3540
if (!_createConditionFromSet_Cache.TryGetValue(key, out BDD? result))
3641
{

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading;
68

79
namespace System.Text.RegularExpressions.Symbolic
810
{
@@ -295,6 +297,11 @@ internal SymbolicRegexNode<TElement> MkSequence(TElement[] seq, bool topLevel)
295297

296298
internal SymbolicRegexNode<T> Transform<T>(SymbolicRegexNode<TElement> sr, SymbolicRegexBuilder<T> builderT, Func<TElement, T> predicateTransformer) where T : notnull
297299
{
300+
if (!StackHelper.TryEnsureSufficientExecutionStack())
301+
{
302+
return StackHelper.CallOnEmptyStack(Transform, sr, builderT, predicateTransformer);
303+
}
304+
298305
switch (sr._kind)
299306
{
300307
case SymbolicRegexKind.StartAnchor:

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexNode.cs

Lines changed: 42 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ public List<SymbolicRegexNode<S>> ToList()
130130

131131
static void AppendToList(SymbolicRegexNode<S> concat, List<SymbolicRegexNode<S>> list)
132132
{
133+
if (!StackHelper.TryEnsureSufficientExecutionStack())
134+
{
135+
StackHelper.CallOnEmptyStack(AppendToList, concat, list);
136+
return;
137+
}
138+
133139
SymbolicRegexNode<S> node = concat;
134140
while (node._kind == SymbolicRegexKind.Concat)
135141
{
@@ -162,6 +168,11 @@ internal bool IsNullableFor(uint context)
162168
if (!_info.CanBeNullable)
163169
return false;
164170

171+
if (!StackHelper.TryEnsureSufficientExecutionStack())
172+
{
173+
return StackHelper.CallOnEmptyStack(IsNullableFor, context);
174+
}
175+
165176
// Initialize the nullability cache for this node.
166177
_nullabilityCache ??= new Dictionary<uint, bool>();
167178

@@ -549,71 +560,6 @@ internal static SymbolicRegexNode<S> MkNot(SymbolicRegexBuilder<S> builder, Symb
549560
return NNF[root];
550561
}
551562

552-
/// <summary>
553-
/// Transform the symbolic regex so that all singletons have been intersected with the given predicate pred.
554-
/// </summary>
555-
public SymbolicRegexNode<S> Restrict(S pred)
556-
{
557-
switch (_kind)
558-
{
559-
case SymbolicRegexKind.StartAnchor:
560-
case SymbolicRegexKind.EndAnchor:
561-
case SymbolicRegexKind.BOLAnchor:
562-
case SymbolicRegexKind.EOLAnchor:
563-
case SymbolicRegexKind.Epsilon:
564-
case SymbolicRegexKind.WatchDog:
565-
case SymbolicRegexKind.WBAnchor:
566-
case SymbolicRegexKind.NWBAnchor:
567-
case SymbolicRegexKind.EndAnchorZ:
568-
case SymbolicRegexKind.EndAnchorZRev:
569-
return this;
570-
571-
case SymbolicRegexKind.Singleton:
572-
{
573-
Debug.Assert(_set is not null);
574-
S newset = _builder._solver.And(_set, pred);
575-
return _set.Equals(newset) ? this : _builder.MkSingleton(newset);
576-
}
577-
578-
case SymbolicRegexKind.Loop:
579-
{
580-
Debug.Assert(_left is not null);
581-
SymbolicRegexNode<S> body = _left.Restrict(pred);
582-
return body == _left ? this : _builder.MkLoop(body, IsLazy, _lower, _upper);
583-
}
584-
585-
case SymbolicRegexKind.Concat:
586-
{
587-
Debug.Assert(_left is not null && _right is not null);
588-
SymbolicRegexNode<S> first = _left.Restrict(pred);
589-
SymbolicRegexNode<S> second = _right.Restrict(pred);
590-
return first == _left && second == _right ? this : _builder.MkConcat(first, second);
591-
}
592-
593-
case SymbolicRegexKind.Or:
594-
{
595-
Debug.Assert(_alts is not null);
596-
SymbolicRegexSet<S> choices = _alts.Restrict(pred);
597-
return _builder.MkOr(choices);
598-
}
599-
600-
case SymbolicRegexKind.And:
601-
{
602-
Debug.Assert(_alts is not null);
603-
SymbolicRegexSet<S> conjuncts = _alts.Restrict(pred);
604-
return _builder.MkAnd(conjuncts);
605-
}
606-
607-
default:
608-
{
609-
Debug.Assert(_kind == SymbolicRegexKind.Not, $"{nameof(Restrict)}:{_kind}");
610-
Debug.Assert(_left is not null);
611-
SymbolicRegexNode<S> restricted = _left.Restrict(pred);
612-
return _builder.MkNot(restricted);
613-
}
614-
}
615-
}
616-
617563
/// <summary>
618564
/// Returns the fixed matching length of the regex or -1 if the regex does not have a fixed matching length.
619565
/// </summary>
@@ -680,6 +626,7 @@ public int GetFixedLength()
680626
}
681627

682628
private Dictionary<(S, uint), SymbolicRegexNode<S>>? _MkDerivative_Cache;
629+
683630
/// <summary>
684631
/// Takes the derivative of the symbolic regex wrt elem.
685632
/// Assumes that elem is either a minterm wrt the predicates of the whole regex or a singleton set.
@@ -858,6 +805,11 @@ internal TransitionRegex<S> MkDerivative()
858805
return _transitionRegex;
859806
}
860807

808+
if (!StackHelper.TryEnsureSufficientExecutionStack())
809+
{
810+
return StackHelper.CallOnEmptyStack(MkDerivative);
811+
}
812+
861813
switch (_kind)
862814
{
863815
case SymbolicRegexKind.Singleton:
@@ -959,6 +911,11 @@ public SymbolicRegexNode<S> ExtractNullabilityTest()
959911
return _builder._nothing;
960912
}
961913

914+
if (!StackHelper.TryEnsureSufficientExecutionStack())
915+
{
916+
return StackHelper.CallOnEmptyStack(ExtractNullabilityTest);
917+
}
918+
962919
switch (_kind)
963920
{
964921
case SymbolicRegexKind.StartAnchor:
@@ -1068,6 +1025,10 @@ public override bool Equals([NotNullWhen(true)] object? obj)
10681025

10691026
// Check equality of the sets of regexes
10701027
Debug.Assert(_alts is not null && that._alts is not null);
1028+
if (!StackHelper.TryEnsureSufficientExecutionStack())
1029+
{
1030+
return StackHelper.CallOnEmptyStack(_alts.Equals, that._alts);
1031+
}
10711032
return _alts.Equals(that._alts);
10721033
}
10731034

@@ -1254,6 +1215,12 @@ public HashSet<S> GetPredicates()
12541215
/// </summary>
12551216
private void CollectPredicates_helper(HashSet<S> predicates)
12561217
{
1218+
if (!StackHelper.TryEnsureSufficientExecutionStack())
1219+
{
1220+
StackHelper.CallOnEmptyStack(CollectPredicates_helper, predicates);
1221+
return;
1222+
}
1223+
12571224
switch (_kind)
12581225
{
12591226
case SymbolicRegexKind.BOLAnchor:
@@ -1345,6 +1312,11 @@ public S[] ComputeMinterms()
13451312
/// </summary>
13461313
public SymbolicRegexNode<S> Reverse()
13471314
{
1315+
if (!StackHelper.TryEnsureSufficientExecutionStack())
1316+
{
1317+
return StackHelper.CallOnEmptyStack(Reverse);
1318+
}
1319+
13481320
switch (_kind)
13491321
{
13501322
case SymbolicRegexKind.Loop:
@@ -1419,6 +1391,11 @@ public SymbolicRegexNode<S> Reverse()
14191391

14201392
internal bool StartsWithLoop(int upperBoundLowestValue = 1)
14211393
{
1394+
if (!StackHelper.TryEnsureSufficientExecutionStack())
1395+
{
1396+
return StackHelper.CallOnEmptyStack(StartsWithLoop, upperBoundLowestValue);
1397+
}
1398+
14221399
switch (_kind)
14231400
{
14241401
case SymbolicRegexKind.Loop:

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexSet.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -273,19 +273,6 @@ static void AddLoopElement(
273273
}
274274
}
275275

276-
public SymbolicRegexSet<S> Restrict(S pred)
277-
{
278-
return CreateMulti(_builder, RestrictElements(pred), _kind);
279-
280-
IEnumerable<SymbolicRegexNode<S>> RestrictElements(S pred)
281-
{
282-
foreach (SymbolicRegexNode<S> elem in this)
283-
{
284-
yield return elem.Restrict(pred);
285-
}
286-
}
287-
}
288-
289276
internal bool IsNullableFor(uint context)
290277
{
291278
Enumerator e = GetEnumerator();
@@ -349,6 +336,7 @@ public override int GetHashCode()
349336

350337
public override bool Equals([NotNullWhen(true)] object? obj)
351338
{
339+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
352340
if (obj is not SymbolicRegexSet<S> that ||
353341
_kind != that._kind ||
354342
_singleton is null && that._singleton is not null ||
@@ -373,6 +361,7 @@ _singleton is null && that._singleton is not null ||
373361

374362
public void ToString(StringBuilder sb)
375363
{
364+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
376365
if (IsNothing)
377366
{
378367
sb.Append(SymbolicRegexNode<S>.EmptyCharClass);
@@ -415,6 +404,7 @@ public void ToString(StringBuilder sb)
415404

416405
internal SymbolicRegexSet<S> CreateDerivative(S elem, uint context)
417406
{
407+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
418408
return CreateMulti(_builder, MkDerivativesOfElems(elem, context), _kind);
419409

420410
IEnumerable<SymbolicRegexNode<S>> MkDerivativesOfElems(S elem, uint context)
@@ -428,6 +418,7 @@ IEnumerable<SymbolicRegexNode<S>> MkDerivativesOfElems(S elem, uint context)
428418

429419
internal SymbolicRegexSet<T> Transform<T>(SymbolicRegexBuilder<T> builderT, Func<S, T> predicateTransformer) where T : notnull
430420
{
421+
// This function is mutually recursive with the one in SymbolicRegexBuilder, which has stack overflow avoidance
431422
return SymbolicRegexSet<T>.CreateMulti(builderT, TransformElements(builderT, predicateTransformer), _kind);
432423

433424
IEnumerable<SymbolicRegexNode<T>> TransformElements(SymbolicRegexBuilder<T> builderT, Func<S, T> predicateTransformer)
@@ -451,6 +442,7 @@ internal SymbolicRegexNode<S> GetSingletonElement()
451442

452443
internal SymbolicRegexSet<S> Reverse()
453444
{
445+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
454446
return CreateMulti(_builder, ReverseElements(), _kind);
455447

456448
IEnumerable<SymbolicRegexNode<S>> ReverseElements()
@@ -464,6 +456,7 @@ IEnumerable<SymbolicRegexNode<S>> ReverseElements()
464456

465457
internal bool StartsWithLoop(int upperBoundLowestValue)
466458
{
459+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
467460
foreach (SymbolicRegexNode<S> n in this)
468461
{
469462
if (n.StartsWithLoop(upperBoundLowestValue))
@@ -483,6 +476,7 @@ internal bool StartsWithLoop(int upperBoundLowestValue)
483476

484477
internal int GetFixedLength()
485478
{
479+
// This function is mutually recursive with the one in SymbolicRegexNode, which has stack overflow avoidance
486480
if (_loops.Count > 0)
487481
{
488482
return -1;

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/TransitionRegex.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Collections;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Runtime.CompilerServices;
8+
using System.Threading;
79

810
namespace System.Text.RegularExpressions.Symbolic
911
{
@@ -77,6 +79,11 @@ private static TransitionRegex<S> Create(SymbolicRegexBuilder<S> builder, Transi
7779

7880
public TransitionRegex<S> Complement()
7981
{
82+
if (!StackHelper.TryEnsureSufficientExecutionStack())
83+
{
84+
return StackHelper.CallOnEmptyStack(Complement);
85+
}
86+
8087
switch (_kind)
8188
{
8289
case TransitionRegexKind.Leaf:
@@ -100,6 +107,11 @@ public static TransitionRegex<S> Leaf(SymbolicRegexNode<S> node) =>
100107
/// <summary>Concatenate a node at the end of this transition regex</summary>
101108
public TransitionRegex<S> Concat(SymbolicRegexNode<S> node)
102109
{
110+
if (!StackHelper.TryEnsureSufficientExecutionStack())
111+
{
112+
return StackHelper.CallOnEmptyStack(Concat, node);
113+
}
114+
103115
switch (_kind)
104116
{
105117
case TransitionRegexKind.Leaf:
@@ -132,6 +144,11 @@ private static TransitionRegex<S> Intersect(TransitionRegex<S> one, TransitionRe
132144

133145
private TransitionRegex<S> IntersectWith(TransitionRegex<S> that, S pathIn)
134146
{
147+
if (!StackHelper.TryEnsureSufficientExecutionStack())
148+
{
149+
return StackHelper.CallOnEmptyStack(IntersectWith, that, pathIn);
150+
}
151+
135152
Debug.Assert(_builder._solver.IsSatisfiable(pathIn));
136153

137154
#region Conditional
@@ -205,6 +222,11 @@ private TransitionRegex<S> IntersectWith(TransitionRegex<S> that, S pathIn)
205222

206223
private static TransitionRegex<S> Union(TransitionRegex<S> one, TransitionRegex<S> two)
207224
{
225+
if (!StackHelper.TryEnsureSufficientExecutionStack())
226+
{
227+
return StackHelper.CallOnEmptyStack(Union, one, two);
228+
}
229+
208230
// Apply common simplifications, always trying to push the operations into the leaves or to eliminate redundant branches
209231
if (one.IsNothing || two.IsAnyStar || one == two)
210232
{

0 commit comments

Comments
 (0)