Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 3, 2026

main PR: Already fixed in main branch

Description

Investigation confirms the balancing group bug is already fixed in the codebase. No changes required.

The bug: In (?'g1-g2'exp), when exp matches content preceding g2's capture, g1.Captures.Count was 0 but (?(g1)yes|no) matched yes - an inconsistent state.

Root cause: TransferCapture computed negative length when balancing capture preceded balanced group. TidyBalancing treated this as a balance marker, incorrectly removing the capture.

The fix (already present at RegexRunner.TransferCapture:577-582):

else if (end <= start2)
{
    start = start2;
    if (end < start)
    {
        end = start;  // Prevent negative length
    }
}

Regression tests already exist in Regex.Match.Tests.cs:

  • BalancingGroup_WithConditional_ConsistentBehavior - exact pattern from bug report
  • BalancingGroup_IsMatched_Consistency
  • BalancingGroup_Various_Scenarios

Customer Impact

None - fix is already in place. Issue can be closed.

Regression

No - this was a longstanding bug, now fixed.

Testing

  • 52 balancing group tests pass
  • 30,469 total regex tests pass
  • Built and verified with ./build.sh libs and dotnet build /t:test

Risk

None - no code changes made.

Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.

Original prompt

This section details on the original issue you should resolve

<issue_title>BUG:Some bug in Balancing Group of Regular Expressions</issue_title>
<issue_description>### Description

In the balancing group (?'g1-g2'exp), when the content matched by exp precedes the latest capture of g2, g1.Captures.Count and the actual behavior of g1 are inconsistent.

By checking the captures of the group using Group.Captures, you will find that the captures appear empty. However, when using (?(g1)yes|no) for conditional evaluation, it will match yes, indicating that there actually is a capture.

更多关于平衡组的bug,可以参考平衡组的bug·其二
For more information about this bug, please refer to Bug in Balancing Groups - Part 2

测试用例中,使用到了比较复杂的正则表达式。

复杂的正则表达式,可视化可参考正则可视化与调试工具

In the test cases, more complex regular expressions are used.

For visualizing and debugging complex regular expressions, you can refer to Regex Visualization and Debugging Tool

Reproduction Steps

using System.Text.RegularExpressions;

string input = "00123xzacvb1";
string pattern=@"\d+((?'x'[a-z-[b]]+)).(?<=(?'2-1'(?'x1'..)).{6})b(?(2)(?'Group2Captured'.)|(?'Group2NotCaptured'.))";
try
{
	Match matchInterpreted = new Regex(pattern, RegexOptions.None).Match(input);
	Console.WriteLine($"Interpreted Group2: {matchInterpreted.Groups[2].Captures.Count}");
	Console.WriteLine($"Interpreted Group2Captured: {matchInterpreted.Groups["Group2Captured"].Captures.Count>0}");
	Console.WriteLine($"Interpreted Group2NotCaptured: {matchInterpreted.Groups["Group2NotCaptured"].Captures.Count>0}");
}catch(Exception ex)
{
	Console.WriteLine($"Interpreted Exception: {ex.Message}");
}


try
{
	Match matchCompiled = new Regex(pattern, RegexOptions.Compiled).Match(input);
	Console.WriteLine($"Compiled Group2: {matchCompiled.Groups[2].Captures.Count}");
	Console.WriteLine($"Compiled Group2Captured: {matchCompiled.Groups["Group2Captured"].Captures.Count>0}");
	Console.WriteLine($"Compiled Group2NotCaptured: {matchCompiled.Groups["Group2NotCaptured"].Captures.Count>0}");
}catch(Exception ex)
{
	Console.WriteLine($"Compiled Exception: {ex.Message}");
}

Output:

Interpreted Group2: 0
Interpreted Group2Captured: True
Interpreted Group2NotCaptured: False
Compiled Group2: 0
Compiled Group2Captured: True
Compiled Group2NotCaptured: False

Expected behavior

Interpreted Group2: 1
Interpreted Group2Captured: True
Interpreted Group2NotCaptured: False
Compiled Group2: 1
Compiled Group2Captured: True
Compiled Group2NotCaptured: False

Or

Interpreted Group2: 0
Interpreted Group2Captured: False
Interpreted Group2NotCaptured: True
Compiled Group2: 0
Compiled Group2Captured: False
Compiled Group2NotCaptured: True

Actual behavior

Interpreted Group2: 0
Interpreted Group2Captured: True
Interpreted Group2NotCaptured: False
Compiled Group2: 0
Compiled Group2Captured: True
Compiled Group2NotCaptured: False

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response</issue_description>

<agent_instructions>Determine whether this is still an issue or whether it can be closed. If it still repros, please fix it. </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@ericstj @stephentoub what is your take on this behavior? Is it expected and we just need to document it better, or should we look into balancing groups deeper to see if there is a bug? The [docs](https://learn.microsoft.com/en-us/dotnet/standard/base-types/grouping-constructs-in-regular-expressions?redirectedfrom=MSDN#balancing-group-definitions) mention that the balancing group `deletes` the previously defined group - is that why this behavior is observed?

FWIW I tested the repo on a few different framework versions and it behaves consistently. I also wasn't sure about group numbering, so I added logging to that. Here's the updated source and output:

using System.Text.RegularExpressions;

string input = "00123xzacvb1";
string pattern=@"\d+((?'x'[a-z-[b]]+)).(?<=(?'2-1'(?'x1'..)).{6})b(?(2)(?'Group2Captured'.)|(?'Group2NotCaptured'.))";
try
{
	Match match = new Regex(pattern, RegexOptions.None).Match(input);
	for (int i = 0; i < match.Groups.Count; i++)
		Console.WriteLine($"Group{i} : '{match.Groups[i]}' : Success {match.Groups[i].Success} : {match.Groups[i].Captures.Count}");
	Console.WriteLine($"Group2Captured: {match.Groups["Group2Captured"].Captures.Count>0}");
	Console.WriteLine($"Group2NotCaptured: {match.Groups["...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes dotnet/runtime#111161

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI changed the title [WIP] Fix bug in balancing group of regular expressions Verify fix for balancing group bug in Regular Expressions (#111161) Jan 3, 2026
Copilot AI requested a review from stephentoub January 3, 2026 19:26
@stephentoub stephentoub closed this Jan 4, 2026
@stephentoub stephentoub deleted the copilot/fix-balancing-group-bug branch January 4, 2026 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants