Regex: negated character class loop followed by \b incorrectly made atomic

Đang mở
#136,601 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
54/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
csharp
Lĩnh vực
testing-qa

Hướng nghiên cứu

Start by reading RegexCharClass.IsKnownWordClassSubset in src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs and the fix in PR 125636. Add regression coverage for the listed negated character class patterns, including the source generator if the existing test structure supports it. Confirm the tests fail on .NET 10 and pass on the fixed code; the expected matches are given in the issue.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

area-System.Text.RegularExpressions untriaged
Description

On .NET 10, a + loop over a negated character class followed by \b is incorrectly made atomic,
so the backtracking engines and the source generator fail to find matches that exist. The
NonBacktracking engine is correct. .NET 9 was correct, and .NET 11 is already correct.

The pattern ^[^a-z]+\b(?:\W+|$) matches nothing at all on .NET 10.

PR 125636 fixed this in .net 11 but doesn't have a dedicated test.

This is not the same as https://github.com/dotnet/runtime/issues/135108

Reproduction Steps
using System.Text.RegularExpressions;
Console.WriteLine(new Regex(@"^[^a-z]+\b\W").IsMatch("A ghi"));  // False on .NET 10; True on 9 and 11

Full three-runtime Docker repro (one image per SDK, identical source):
docker build --build-arg SDK_TAG=10.0 --build-arg TFM=net10.0 -t r . && docker run --rm r

Pattern Input .NET 9.0.19 .NET 10.0.11 .NET 11.0.0-p7
^[^a-z]+\b A ghi "A " "A " "A "
^[^a-z]+\b\W A ghi "A " no match "A "
^[^a-z]+\b(?:\W+|$) A ghi "A " no match "A "
^[^a-z]*\b(?:\W+|$) A ghi "A " "A " "A "
^[^a-z]{1,}\b\W A ghi "A " no match "A "
^[^a-z]{1,9}\b\W A ghi "A " no match "A "
^[A-Z ]+\b(?:\W+|$) A ghi "A " "A " "A "
^[^0-9]+\b\W A ghi "A " no match "A "
^\W+\b\W -- ghi no match no match no match

Trigger requires both a loop minimum ≥ 1 (the node.M > 0 guard) and a negated set whose stated
characters are all word characters. RegexOptions.None, Compiled and [GeneratedRegex] are all
affected; NonBacktracking is not.

Expected behavior

Expected true

Actual behavior

.NET 10 returns false.

Regression?

Yes, .NET 9 → .NET 10. Introduced by #117892; incidentally fixed on main by #125636, which replaced
IsKnownWordClassSubset with IsSubsetOf guarded by !IsNegated(subset). Root-cause detail and the
proposed one-line patch for release/10.0 are above.

Known Workarounds
  • RegexOptions.NonBacktracking — correct on all versions, no pattern change.
  • Move the boundary into the alternation: ^[^a-z]+(?:\b\W+|$). Note ^[^a-z]+(?:\b\W+|\b$) still reproduces, so it only helps when not every branch begins with the boundary.
  • Avoid the negated class, or use * where semantics permit.
Configuration

.NET 10.0.14 - incorrect
.NET 10.0.11 (SDK 10.0.400) - incorrect
.NET 9.0.19 (SDK 9.0.317) - correct
.NET 11.0.0-preview.7.26381.103 - correct
Reproduced in the official mcr.microsoft.com/dotnet/sdk images on both linux/arm64 and linux/amd64, and on macOS 15 arm64 directly.

Other information

One guard, mirroring what the category path above it already does:

--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs
@@ public static bool IsKnownWordClassSubset(string set)
         // If we can enumerate every character in the set quickly, do so, checking to see whether they're all in \w.
-        if (CanEasilyEnumerateSetContents(set))
+        // Note: for a negated set this would enumerate the characters that are EXCLUDED, so bail.
+        if (!IsNegated(set) && CanEasilyEnumerateSetContents(set))
         {

Plus a regression test, which is missing everywhere today:

// [^a-z] is not a subset of \w - it contains spaces, punctuation, digits and uppercase -
// so [^a-z]+ must not be made atomic ahead of the \b.
yield return (@"^[^a-z]+\b(?:\W+|$)", "CA Actress", RegexOptions.None, 0, 10, true, "CA ");
yield return (@"^[^a-z]+\b\W",        "A ghi",      RegexOptions.None, 0, 5,  true, "A ");
yield return (@"^[^0-9]+\b\W",        "A ghi",      RegexOptions.None, 0, 5,  true, "A ");

Worth landing that test on main too, so .NET 11's currently-incidental correctness is actually pinned.

Ngôn ngữ chính
C#
Star
18.4k
Fork
5.8k
Merge trung bình
2 ngày 12 giờ
Pull request đã merge (30 ngày)
579

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của dotnet/runtime

Tất cả issue của dotnet/runtime

Issue tương tự

Thêm issue về C#

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.