diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexRunner.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexRunner.cs
index 2e699c8e85a8d4..4ba1ebb976edb9 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexRunner.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexRunner.cs
@@ -573,6 +573,13 @@ protected void TransferCapture(int capnum, int uncapnum, int start, int end)
else if (end <= start2)
{
start = start2;
+
+ // Ensure we don't create a capture with negative length
+ // When the balancing capture precedes the balanced group, end might be less than the new start
+ if (end < start)
+ {
+ end = start;
+ }
}
else
{
diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs
index f51604442bcfaf..41c1283f690901 100644
--- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs
+++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs
@@ -2881,5 +2881,196 @@ public async Task MatchNonBacktrackingOver255Minterms()
Assert.Equal(272, ms[0].Length);
}
}
+
+ ///
+ /// Tests for balancing groups where the balancing group's captured content
+ /// precedes the position of the group being balanced.
+ /// This tests the fix for https://github.com/dotnet/runtime/issues/111161
+ ///
+ [Theory]
+ [MemberData(nameof(BalancingGroup_WithConditional_MemberData))]
+ public void BalancingGroup_WithConditional_ConsistentBehavior(Regex regex, string input, bool expectedGroup2Matched, string expectedMatch)
+ {
+ Match m = regex.Match(input);
+
+ Assert.True(m.Success, $"Match should succeed for input '{input}'");
+ Assert.Equal(expectedMatch, m.Value);
+
+#if !NETFRAMEWORK // This bug was fixed in .NET Core and doesn't exist in .NET Framework
+ // Check that the group 2 state is consistent
+ bool group2Success = m.Groups[2].Success;
+ int group2CapturesCount = m.Groups[2].Captures.Count;
+
+ // The key test: Group.Success and Captures.Count should be consistent with the conditional behavior
+ Assert.Equal(expectedGroup2Matched, group2Success);
+ if (expectedGroup2Matched)
+ {
+ Assert.True(group2CapturesCount > 0, "If group 2 matched, it should have at least one capture");
+ }
+ else
+ {
+ Assert.Equal(0, group2CapturesCount);
+ }
+#else
+ // On .NET Framework, just use the parameters to avoid xUnit warning
+ _ = expectedGroup2Matched;
+#endif
+ }
+
+ public static IEnumerable