Repository: microsoft/STL
Pull request #5380: `<regex>`: Revise parsing of escape sequences
Author: login: muellerj2
Labels: name: bug
description: Something isn't working
name: regex
description: meow is a substring of homeowner
Created: 2025-03-30T11:33:33Z | Merged: 2025-04-10T07:44:38Z

Fixes #5244. There is still one remaining issue related to escape sequences in basic regular expressions. I created #5379 to track this.

## Changes

* ECMAScript: `[\b]` now matches "backspace" (see [the semantics of the production in the recent ECMAScript standard, unchanged since the 3rd edition](https://262.ecma-international.org/#sec-patterns-static-semantics-character-value)). I repurposed and  renamed one of the `_L_flags` to enable this oddity in the parser for ECMAScript. This flag is only set for ECMAScript and had previously been used to represent that the backslash can escape itself in ECMAScript -- but this is superfluous because the flag `_L_ident_ECMA` implies the same. 
* ECMAScript: `[\z]` and `[\Z]` no longer match a custom character class "z" provided by a custom regex traits class. Instead, they will match the characters "z" and "Z", as the escapes also do outside of squared character classes. (This change will also make it easier to solve #992 in the future.)
* ECMAScript: The standard states that a decimal escape starting with digit `\0` represents the literal NUL. The standard also demands that such an escape must not be followed by another decimal digit ([which is still specified this way in recent ECMAScript standards](https://262.ecma-international.org/#prod-CharacterEscape)), so we now throw an `error_escape` in this case. As a consequence of this change, a backreference escape can no longer be written with leading zeros (see also [the most recent ECMAScript standard](https://262.ecma-international.org/#sec-regular-expressions)).
* basic, grep: The escape sequence `\|` is no longer interpreted to mean the character `|`. Instead, it is rejected, since its meaning is undefined according to the POSIX standard version referenced by the C++ standard ([Section 9.3.2](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_02)). Users can write `|` instead.
  * The current edition of the POSIX standard makes the meaning of `\|`implementation-defined (in a not particularly good way, I think): It states that `\|` could either be interpreted as the character `|` or an actual pipe ([Section 9.3.2 in the recent standard](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html#tag_09_03_02)). The standard then continues that a future version may require `\|` to denote a pipe.  
  In any case, the interpretation of `\|` as the character `|`, which was implemented before this PR, is not the one recommended by the current version of the standard.
* basic, grep: The regular expression `]` is accepted now, as [the POSIX standard requires because it does not declare `]` to be a special character](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_02)). (I noticed this issue when I added `[\]]` as a test case.)
  * Unfortunately, there is no truly suitable `_L_flags` for this, so I check one of the flags that are set for basic regular expressions but not for any of the other supported grammars. Setting `_L_paren_bal` for basic regular expressions would make the parser accept wrong usages of `\}` and `\)` as well.
* awk: As required by the POSIX standard ([see here](https://pubs.opengroup.org/onlinepubs/009604399/utilities/awk.html#tag_04_06_13_04)), octal escape sequences and all awk-specific character escapes are now recognized within squared character classes (before this PR, only the escapes \a, \b, \f, \n, \r, \t and \v were recognized). On the other hand, unknown escape sequences are now rejected in character classes, because their meaning is ambiguous:
  * The underlying POSIX extended regular expressions don't treat \ as an escape character in character classes, so `[\a]` matches the backslash and a. This was how these escape sequences were interpreted before this PR.
  * The POSIX standard leaves the interpretation of unknown sequences undefined in the awk specification.
  * Many awk implementations treat such unknown sequences as identity escapes (as `std::regex` does in ECMAScript mode, too), but others stick closer to the underlying extended regular expressions semantics and treat them as backslash + character.

## `\}` and `\]` in extended regular expressions
According to the POSIX standard referenced by the C++ standard, the escape sequence `\}` is undefined in extended regular expressions. However, [more recent POSIX versions do define it](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html#tag_09_04_02) and it also feels unbalanced to reject `\}` while `\{` is accepted, so I opted to keep supporting the escape sequence `\}`. 
* The parser supports the escape sequence `\[` but not `\]`. More recent POSIX versions define `\]` as well in basic and extended regular expressions. Should we add support for `\]`?

Linked issue #5244: `<regex>`: Some escape sequences are mishandled
There are a number of escape sequences that the parser mistakenly accepts or miscompiles.

## ECMAScript
* Backreferences with leading zero digits (e.g., `\01` for capture group 1) should be rejected. [ECMA-262 3rd ed., Section 15.10.2.11 "DecimalEscape"]
* `\00` and more zero digits should be rejected and not be interpreted as an escape for NUL. Only `\0` is a valid escape sequence for NUL. [ECMA-262 3rd ed., Section 15.10.2.11 "DecimalEscape"]
* When a custom traits implementation defines a new character class "z", `[\z]` matches the characters in this class and not the character z. (Meanwhile, `\z` without brackets matches the character z and not the characters in the class "z".) [ECMA-262 3rd ed., Sections 15.10.1 "Patterns" and 15.10.2.12 "CharacterClassEscape"]
* `[\b]` should match U+0008 BACKSPACE, not b. [ECMA-262 3rd ed., Section 15.10.2.19 "ClassEscape"]

## awk

See [Section "Regular expressions"](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html#tag_20_06_13_04) in the awk specification.

* Octal escape sequences are not parsed correctly in square-bracket character class definitions. (E.g., `[\040]` should match U+0020 SPACE.)
* Similarly, `[\"]` and `[\/]` match backslashes as well even though they shouldn't.
* While the awk specification says that using unspecified escape sequences results in undefined behavior, I think we should reject them. (I believe we should handle this differently from ECMAScript mode, where unrecognized escape sequences just yield the escaped character.)
Linked issue comments: user: login: muellerj2
created_at: 2025-03-29T19:30:16Z
body: While writing test coverage for this, I noticed two more mistakenly accepted escape sequences:

* In basic regexes, `\|` is accepted even though `|` is not special.
* In extended regexes, `\}` is accepted even though `}` is not special.
author_association: CONTRIBUTOR

Diff:
diff --git a/stl/inc/regex b/stl/inc/regex
index 07835e51318..1434e738cd9 100644
--- a/stl/inc/regex
+++ b/stl/inc/regex
@@ -1729,21 +1729,21 @@ private:
     void _Expect(_Meta_type, regex_constants::error_type);
 
     // parsing
-    int _Do_digits(int _Base, int _Count, regex_constants::error_type _Error_type);
-    bool _DecimalDigits2(regex_constants::error_type _Error_type, int _Count = INT_MAX);
+    int _Do_digits(int _Base, int _Initial, int _Count, regex_constants::error_type _Error_type);
+    bool _DecimalDigits3(regex_constants::error_type _Error_type, int _Initial = 0);
     void _HexDigits(int);
     bool _OctalDigits();
     void _Do_ex_class(_Meta_type);
     bool _CharacterClassEscape(bool);
-    _Prs_ret _ClassEscape2();
+    _Prs_ret _ClassEscape3();
     _Prs_ret _ClassAtom(bool);
     void _ClassRanges();
     void _CharacterClass();
-    bool _IdentityEscape();
-    bool _IsIdentityEscape() const;
+    bool _IdentityEscape(bool);
+    bool _IsIdentityEscape(bool) const;
     bool _Do_ffn(_Elem);
     bool _Do_ffnx(_Elem);
-    bool _CharacterEscape();
+    bool _CharacterEscape(bool);
     void _AtomEscape();
     void _Do_capture_group();
     void _Do_noncapture_group();
@@ -1783,7 +1783,7 @@ enum _Lang_flags { // describe language properties
     _L_esc_uni    = 0x00000800, // has Unicode escape sequences
     _L_esc_hex    = 0x00001000, // has hexadecimal escape sequences
     _L_esc_oct    = 0x00002000, // has octal escape sequences
-    _L_esc_bsl    = 0x00004000, // has escape backslash in character classes
+    _L_esc_bsp    = 0x00004000, // has backspace escape in character classes
     _L_esc_ffnx   = 0x00008000, // has extra file escapes (\a and \b)
     _L_esc_ffn    = 0x00010000, // has limited file escapes (\[fnrtv])
     _L_esc_wsd    = 0x00020000, // has w, s, and d character set escapes
@@ -1797,7 +1797,8 @@ enum _Lang_flags { // describe language properties
     _L_anch_rstr  = 0x02000000, // anchor restricted to beginning/end
     _L_star_beg   = 0x04000000, // star okay at beginning of RE/expr (BRE)
     _L_empty_grp  = 0x08000000, // empty group allowed (ERE prohibits "()")
-    _L_paren_bal  = 0x10000000, // ')'/'}'/']' special only after '('/'{'/']'
+    _L_paren_bal  = 0x10000000, // ')'/'}' special only after '('/'{'
+    _L_brk_bal    = 0x20000000, // ']' special only after '[' (ERE, BRE); TRANSITION, ABI: same value as _L_brk_rstr
     _L_brk_rstr   = 0x20000000, // ']' not special when first character in set
     _L_mtch_long  = 0x40000000, // find longest match (ERE, BRE)
 };
@@ -4039,9 +4040,9 @@ void _Parser<_FwdIt, _Elem, _RxTraits>::_Expect(_Meta_type _St, regex_constants:
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 int _Parser<_FwdIt, _Elem, _RxTraits>::_Do_digits(
-    int _Base, int _Count, regex_constants::error_type _Error_type) { // translate digits to numeric value
+    int _Base, int _Initial, int _Count, regex_constants::error_type _Error_type) { // translate digits to numeric value
     int _Chv;
-    _Val = 0;
+    _Val = _Initial;
     while (_Count != 0 && (_Chv = _Traits.value(_Char, _Base)) != -1) { // append next digit
         if (_Val > (INT_MAX - _Chv) / _Base) {
             _Error(_Error_type);
@@ -4055,21 +4056,21 @@ int _Parser<_FwdIt, _Elem, _RxTraits>::_Do_digits(
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-bool _Parser<_FwdIt, _Elem, _RxTraits>::_DecimalDigits2(
-    const regex_constants::error_type _Error_type, const int _Count /* = INT_MAX */) { // check for decimal value
-    return _Do_digits(10, _Count, _Error_type) != _Count;
+bool _Parser<_FwdIt, _Elem, _RxTraits>::_DecimalDigits3(
+    const regex_constants::error_type _Error_type, const int _Initial /* = 0 */) { // check for decimal value
+    return _Do_digits(10, _Initial, INT_MAX, _Error_type) != INT_MAX;
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_HexDigits(int _Count) { // check for _Count hex digits
-    if (_Do_digits(16, _Count, regex_constants::error_escape) != 0) {
+    if (_Do_digits(16, 0, _Count, regex_constants::error_escape) != 0) {
         _Error(regex_constants::error_escape);
     }
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 bool _Parser<_FwdIt, _Elem, _RxTraits>::_OctalDigits() { // check for up to 3 octal digits
-    return _Do_digits(8, 3, regex_constants::error_escape) != 3;
+    return _Do_digits(8, 0, 3, regex_constants::error_escape) != 3;
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
@@ -4139,36 +4140,33 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterClassEscape(bool _Addit) { //
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-_Prs_ret _Parser<_FwdIt, _Elem, _RxTraits>::_ClassEscape2() { // check for class escape
-    if ((_L_flags & _L_esc_bsl) && _Char == _Esc_bsl) { // handle escape backslash if allowed
-        _Val = _Esc_bsl;
+_Prs_ret _Parser<_FwdIt, _Elem, _RxTraits>::_ClassEscape3() { // check for class escape
+    if ((_L_flags & _L_esc_bsp) && _Char == _Esc_ctrl_b) { // handle backspace escape
         _Next();
+        _Val = _Meta_bsp;
         return _Prs_chr;
-    } else if ((_L_flags & _L_esc_wsd) && _CharacterClassEscape(false)) {
-        return _Prs_set;
-    } else if (_DecimalDigits2(regex_constants::error_escape)) { // check for invalid value
-        if (_Val != 0) {
+    } else if ((_L_flags & (_L_bzr_chr | _L_bckr))
+               && (_Val = _Traits.value(_Char, 10)) != -1) { // handle \0 and reject other escaped decimal literals
+        _Next();
+        if (!(_L_flags & _L_bzr_chr) || _Val != 0 || _Traits.value(_Char, 10) != -1) {
             _Error(regex_constants::error_escape);
         }
 
         return _Prs_chr;
+    } else if (_CharacterEscape(true)) {
+        return _Prs_chr;
+    } else if ((_L_flags & _L_esc_wsd) && _CharacterClassEscape(false)) {
+        return _Prs_set;
     }
-    return _CharacterEscape() ? _Prs_chr : _Prs_none;
+
+    _Error(regex_constants::error_escape);
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 _Prs_ret _Parser<_FwdIt, _Elem, _RxTraits>::_ClassAtom(const bool _Initial) { // check for class atom
-    if (_Mchar == _Meta_esc) { // check for valid escape sequence
+    if (_Mchar == _Meta_esc && (_L_flags & (_L_grp_esc | _L_ident_awk))) { // check for valid escape sequence
         _Next();
-        if (_L_flags & _L_grp_esc) {
-            return _ClassEscape2();
-        } else if ((_L_flags & _L_esc_ffn && _Do_ffn(_Char))
-                   || (_L_flags & _L_esc_ffnx && _Do_ffnx(_Char))) { // advance to next character
-            _Next();
-            return _Prs_chr;
-        }
-        _Val = _Meta_esc;
-        return _Prs_chr;
+        return _ClassEscape3();
     } else if (_Mchar == _Meta_lsq) { // check for valid delimited expression
         _Next();
         if (_Mchar == _Meta_colon || _Mchar == _Meta_equal || _Mchar == _Meta_dot) { // handle delimited expression
@@ -4325,7 +4323,9 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_Wrapped_disjunction() { // add disjunct
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape() const { // check for valid identity escape
+bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape(bool _In_character_class) const {
+    // check for valid identity escape
+
     if (_L_flags & _L_ident_ECMA) {
         // ECMAScript identity escape characters
         switch (_Char) {
@@ -4343,26 +4343,28 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape() const { // check for
     }
 
     switch (_Char) {
+    case _Meta_esc:
+        // BRE, ERE, awk identity escape characters (anywhere in awk)
+        return true;
     case _Meta_dot:
     case _Meta_lsq:
-    case _Meta_esc:
     case _Meta_star:
-    case _Meta_bar:
     case _Meta_caret:
     case _Meta_dlr:
-        // BRE, ERE, awk identity escape characters
-        return true;
+        // BRE, ERE, awk identity escape characters (outside character classes only)
+        return !_In_character_class;
     case _Meta_lpar:
     case _Meta_rpar:
+    case _Meta_bar:
     case _Meta_plus:
     case _Meta_query:
     case _Meta_lbr:
     case _Meta_rbr:
-        // additional ERE identity escape characters
-        return (_L_flags & _L_ident_ERE) != 0;
+        // additional ERE identity escape characters (outside character classes only)
+        return (_L_flags & _L_ident_ERE) != 0 && !_In_character_class;
     case '"':
     case '/':
-        // additional awk identity escape characters
+        // additional awk identity escape characters (anywhere)
         return (_L_flags & _L_ident_awk) != 0;
     default:
         return false;
@@ -4370,8 +4372,9 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape() const { // check for
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-bool _Parser<_FwdIt, _Elem, _RxTraits>::_IdentityEscape() { // check whether an escape is valid, and process it if so
-    if (_IsIdentityEscape()) {
+bool _Parser<_FwdIt, _Elem, _RxTraits>::_IdentityEscape(bool _In_character_class) {
+    // check whether an escape is valid, and process it if so
+    if (_IsIdentityEscape(_In_character_class)) {
         _Val = _Char;
         _Next();
         return true;
@@ -4413,7 +4416,7 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_Do_ffnx(_Elem _Ch) { // check for the r
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid character escape
+bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape(bool _In_character_class) { // check for valid character escape
     if (_Mchar == _Meta_eos) {
         _Error(regex_constants::error_escape);
     }
@@ -4439,7 +4442,7 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
             _Error(regex_constants::error_escape);
         }
     } else {
-        return _IdentityEscape();
+        return _IdentityEscape(_In_character_class);
     }
 
     if (_STD _Max_limit<typename _RxTraits::_Uelem>() < static_cast<unsigned int>(_Val)) {
@@ -4452,23 +4455,28 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_AtomEscape() { // check for valid atom escape
-    constexpr int _Bre_max_backref_digits = 1;
-    if ((_L_flags & _L_bckr)
-        && _DecimalDigits2(regex_constants::error_backref,
-            (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
-        if (_Val == 0) { // handle \0
-            if (!(_L_flags & _L_bzr_chr)) {
+    if ((_L_flags & (_L_bzr_chr | _L_bckr)) && (_Val = _Traits.value(_Char, 10)) != -1) { // escaped decimal sequence
+        _Next();
+        if ((_L_flags & _L_bzr_chr) && _Val == 0) { // handle \0
+            if (_Traits.value(_Char, 10) != -1) {
+                _Error(regex_constants::error_escape);
+            }
+            _Nfa._Add_char2(_Elem{});
+        } else if (_L_flags & _L_bckr) { // check for valid backreference
+            if (!(_L_flags & _L_lim_bckr)) {
+                (void) _DecimalDigits3(regex_constants::error_backref, _Val);
+            }
+
+            if (_Val == 0) {
                 _Error(regex_constants::error_escape);
+            } else if (_Grp_idx < static_cast<size_t>(_Val) || _Finished_grps.size() <= static_cast<size_t>(_Val)
+                       || !_Finished_grps[static_cast<size_t>(_Val)]) {
+                _Error(regex_constants::error_backref);
             } else {
-                _Nfa._Add_char2(static_cast<_Elem>(_Val));
+                _Nfa._Add_backreference(static_cast<size_t>(_Val));
             }
-        } else if (_Grp_idx < static_cast<size_t>(_Val) || _Finished_grps.size() <= static_cast<size_t>(_Val)
-                   || !_Finished_grps[static_cast<size_t>(_Val)]) {
-            _Error(regex_constants::error_backref);
-        } else {
-            _Nfa._Add_backreference(static_cast<size_t>(_Val));
         }
-    } else if (_CharacterEscape()) {
+    } else if (_CharacterEscape(false)) {
         _Nfa._Add_char2(static_cast<_Elem>(_Val));
     } else if (!(_L_flags & _L_esc_wsd) || !_CharacterClassEscape(true)) {
         _Error(regex_constants::error_escape);
@@ -4486,7 +4494,7 @@ void _Parser<_FwdIt, _Elem, _RxTraits>::_Quantifier() { // check for quantifier
             _Max = 1;
         } else if (_Mchar == _Meta_lbr) { // check for valid bracketed value
             _Next();
-            if (!_DecimalDigits2(regex_constants::error_badbrace)) {
+            if (!_DecimalDigits3(regex_constants::error_badbrace)) {
                 _Error(regex_constants::error_badbrace);
             }
 
@@ -4496,7 +4504,7 @@ void _Parser<_FwdIt, _Elem, _RxTraits>::_Quantifier() { // check for quantifier
             } else { // check for decimal constant following comma
                 _Next();
                 if (_Mchar != _Meta_rbr) {
-                    if (!_DecimalDigits2(regex_constants::error_badbrace)) {
+                    if (!_DecimalDigits3(regex_constants::error_badbrace)) {
                         _Error(regex_constants::error_badbrace);
                     }
 
@@ -4573,7 +4581,7 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_Alternative() { // check for valid alte
             _Error(regex_constants::error_badrepeat);
         } else if (_Mchar == _Meta_rbr && !(_L_flags & _L_paren_bal)) {
             _Error(regex_constants::error_brace);
-        } else if (_Mchar == _Meta_rsq && !(_L_flags & _L_paren_bal)) {
+        } else if (_Mchar == _Meta_rsq && !(_L_flags & _L_brk_bal)) {
             _Error(regex_constants::error_brack);
         } else { // add character
             _Nfa._Add_char2(_Char);
@@ -4702,17 +4710,17 @@ _Parser<_FwdIt, _Elem, _RxTraits>::_Parser(
     : _Pat(_Pfirst), _Begin(_Pfirst), _End(_Plast), _Nfa(_Tr, _Fx), _Traits(_Tr), _Flags(_Fx) {
 
     constexpr unsigned int _ECMA_flags = _L_ext_rep | _L_alt_pipe | _L_nex_grp | _L_nex_rep | _L_nc_grp | _L_asrt_gen
-                                       | _L_asrt_wrd | _L_bckr | _L_ngr_rep | _L_esc_uni | _L_esc_hex | _L_esc_bsl
+                                       | _L_asrt_wrd | _L_bckr | _L_ngr_rep | _L_esc_uni | _L_esc_hex | _L_esc_bsp
                                        | _L_esc_ffn | _L_esc_wsd | _L_esc_ctrl | _L_bzr_chr | _L_grp_esc | _L_ident_ECMA
                                        | _L_empty_grp;
 
     constexpr unsigned int _Basic_flags =
-        _L_bckr | _L_lim_bckr | _L_anch_rstr | _L_star_beg | _L_empty_grp | _L_brk_rstr | _L_mtch_long;
+        _L_bckr | _L_lim_bckr | _L_anch_rstr | _L_star_beg | _L_empty_grp | _L_brk_bal | _L_brk_rstr | _L_mtch_long;
 
     constexpr unsigned int _Grep_flags = _Basic_flags | _L_alt_nl | _L_no_nl;
 
-    constexpr unsigned int _Extended_flags =
-        _L_ext_rep | _L_alt_pipe | _L_nex_grp | _L_nex_rep | _L_ident_ERE | _L_paren_bal | _L_brk_rstr | _L_mtch_long;
+    constexpr unsigned int _Extended_flags = _L_ext_rep | _L_alt_pipe | _L_nex_grp | _L_nex_rep | _L_ident_ERE
+                                           | _L_paren_bal | _L_brk_bal | _L_brk_rstr | _L_mtch_long;
 
     constexpr unsigned int _Awk_flags = _Extended_flags | _L_esc_oct | _L_esc_ffn | _L_esc_ffnx | _L_ident_awk;
 
diff --git a/tests/std/test.lst b/tests/std/test.lst
index 079235d8159..292e4255c97 100644
--- a/tests/std/test.lst
+++ b/tests/std/test.lst
@@ -256,6 +256,7 @@ tests\GH_004929_internal_tag_constructors
 tests\GH_004930_char_traits_user_specialization
 tests\GH_005090_stl_hardening
 tests\GH_005204_regex_collating_ranges
+tests\GH_005244_regex_escape_sequences
 tests\GH_005315_destructor_tombstones
 tests\LWG2381_num_get_floating_point
 tests\LWG2597_complex_branch_cut
diff --git a/tests/std/tests/GH_005244_regex_escape_sequences/env.lst b/tests/std/tests/GH_005244_regex_escape_sequences/env.lst
new file mode 100644
index 00000000000..19f025bd0e6
--- /dev/null
+++ b/tests/std/tests/GH_005244_regex_escape_sequences/env.lst
@@ -0,0 +1,4 @@
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+RUNALL_INCLUDE ..\usual_matrix.lst
diff --git a/tests/std/tests/GH_005244_regex_escape_sequences/test.cpp b/tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
new file mode 100644
index 00000000000..09641ea5dcf
--- /dev/null
+++ b/tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
@@ -0,0 +1,881 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <cstddef>
+#include <cstdio>
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(const string& expected, const string& c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(const string& expected, const string& c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(const string& c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(const string& c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(const string& c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    g_regexTester.should_match("\x00"s + "0", R"(\x000)", ECMAScript);
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    g_regexTester.should_match("\u0000"s + "0", R"(\u00000)", ECMAScript);
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\D)", ECMAScript);
+
+    g_regexTester.should_match("a", R"(\w)", ECMAScript);
+    g_regexTester.should_match("A", R"(\w)", ECMAScript);
+    g_regexTester.should_match("0", R"(\w)", ECMAScript);
+    g_regexTester.should_match("_", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("A", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\W)", ECMAScript);
+    g_regexTester.should_match("-", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\W)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\W)", ECMAScript);
+
+    g_regexTester.should_match(" ", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\t", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\n", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\t", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\n", R"(\S)", ECMAScript);
+    g_regexTester.should_match("a", R"(\S)", ECMAScript);
+    g_regexTester.should_match("0", R"(\S)", ECMAScript);
+    g_regexTester.should_match("_", R"(\S)", ECMAScript);
+    g_regexTester.should_match("-", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\S)", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("g", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("\\\\", R"(\\)", ECMAScript);
+    check_atomescape_identityescape("a", ECMAScript);
+    check_atomescape_identityescape("-", ECMAScript);
+    check_atomescape_identityescape(" ", ECMAScript);
+    check_atomescape_identityescape("(", ECMAScript);
+    check_atomescape_identityescape(")", ECMAScript);
+    check_atomescape_identityescape("[", ECMAScript);
+    check_atomescape_identityescape("]", ECMAScript);
+    check_atomescape_identityescape("{", ECMAScript);
+    check_atomescape_identityescape("}", ECMAScript);
+    check_atomescape_identityescape("*", ECMAScript);
+    check_atomescape_identityescape("?", ECMAScript);
+    check_atomescape_identityescape("+", ECMAScript);
+    check_atomescape_identityescape("^", ECMAScript);
+    check_atomescape_identityescape("$", ECMAScript);
+    check_atomescape_identityescape(".", ECMAScript);
+
+    {
+        string pattern = R"(\z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"(\Z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_classescape_ecmascript() {
+
+    // ClassEscape :: DecimalEscape
+    // only accepts literal NUL (\0), all other decimal escapes result in error
+    g_regexTester.should_match("\0"s, R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\0])", ECMAScript);
+    g_regexTester.should_throw(R"([\00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\01])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\10])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\01])", error_escape, ECMAScript);
+
+    // ClassEscape :: 'b'
+    check_classescape_controlescape("\b", "b", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: ControlEscape
+    check_classescape_controlescape("\f", "f", ECMAScript);
+    check_classescape_controlescape("\n", "n", ECMAScript);
+    check_classescape_controlescape("\r", "r", ECMAScript);
+    check_classescape_controlescape("\t", "t", ECMAScript);
+    check_classescape_controlescape("\v", "v", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"([\cA])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cZ])", ECMAScript);
+    g_regexTester.should_match("\x1", R"([\ca])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cz])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("c", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("ca", R"([\ca])", ECMAScript);
+    g_regexTester.should_throw(R"([\c0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\c])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"([\x00])", ECMAScript);
+    g_regexTester.should_match("\x00"s, R"([\x000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\x000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\x41])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\x41])", ECMAScript);
+    g_regexTester.should_match("\xff", R"([\xff])", ECMAScript);
+    g_regexTester.should_throw(R"([\x])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\x0])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"([\u0000])", ECMAScript);
+    g_regexTester.should_match("\u0000"s, R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\u0041])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\u0041])", ECMAScript);
+    g_regexTester.should_throw(R"([\uffff])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u2600])", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"([\u])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u000])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"([\d])", ECMAScript);
+    g_regexTester.should_match("5", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\D])", ECMAScript);
+    g_regexTester.should_not_match("5", R"([\D])", ECMAScript);
+    g_regexTester.should_match("a", R"([\D])", ECMAScript);
+    g_regexTester.should_match("_", R"([\D])", ECMAScript);
+    g_regexTester.should_match("-", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\D])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\D])", ECMAScript);
+
+    g_regexTester.should_match("a", R"([\w])", ECMAScript);
+    g_regexTester.should_match("A", R"([\w])", ECMAScript);
+    g_regexTester.should_match("0", R"([\w])", ECMAScript);
+    g_regexTester.should_match("_", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("A", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\W])", ECMAScript);
+    g_regexTester.should_match("-", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\W])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\W])", ECMAScript);
+
+    g_regexTester.should_match(" ", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\t", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\n", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\t", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\n", R"([\S])", ECMAScript);
+    g_regexTester.should_match("a", R"([\S])", ECMAScript);
+    g_regexTester.should_match("0", R"([\S])", ECMAScript);
+    g_regexTester.should_match("_", R"([\S])", ECMAScript);
+    g_regexTester.should_match("-", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\S])", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"([\\])", ECMAScript);
+    g_regexTester.should_not_match("b", R"([\\])", ECMAScript);
+    check_classescape_identityescape("a", ECMAScript);
+    check_classescape_identityescape("-", ECMAScript);
+    check_classescape_identityescape(" ", ECMAScript);
+    check_classescape_identityescape("(", ECMAScript);
+    check_classescape_identityescape(")", ECMAScript);
+    check_classescape_identityescape("[", ECMAScript);
+    check_classescape_identityescape("]", ECMAScript);
+    check_classescape_identityescape("{", ECMAScript);
+    check_classescape_identityescape("}", ECMAScript);
+    check_classescape_identityescape("*", ECMAScript);
+    check_classescape_identityescape("?", ECMAScript);
+    check_classescape_identityescape("+", ECMAScript);
+    check_classescape_identityescape("^", ECMAScript);
+    check_classescape_identityescape("$", ECMAScript);
+    check_classescape_identityescape(".", ECMAScript);
+    check_classescape_identityescape("B", ECMAScript);
+
+    {
+        string pattern = R"([\z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"([\Z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_atomescape_posix_common(syntax_option_type option) {
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // When special character is preceded by backslash, the special character matches itself
+    g_regexTester.should_match("\\", R"(\\)", option);
+    g_regexTester.should_not_match("b", R"(\\)", option);
+    g_regexTester.should_not_match("\\\\", R"(\\)", option);
+    check_atomescape_identityescape(".", option);
+    check_atomescape_identityescape("[", option);
+    check_atomescape_identityescape("*", option);
+    check_atomescape_identityescape("^", option);
+    check_atomescape_identityescape("$", option);
+
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // escaping ordinary characters is undefined -> reject
+    g_regexTester.should_throw(R"(\B)", error_escape, option);
+    g_regexTester.should_throw(R"(\c)", error_escape, option);
+    g_regexTester.should_throw(R"(\ca)", error_escape, option);
+    g_regexTester.should_throw(R"(\x000)", error_escape, option);
+    g_regexTester.should_throw(R"(\u0000)", error_escape, option);
+    g_regexTester.should_throw(R"(\d)", error_escape, option);
+    g_regexTester.should_throw(R"(\D)", error_escape, option);
+    g_regexTester.should_throw(R"(\w)", error_escape, option);
+    g_regexTester.should_throw(R"(\W)", error_escape, option);
+    g_regexTester.should_throw(R"(\s)", error_escape, option);
+    g_regexTester.should_throw(R"(\S)", error_escape, option);
+
+    // while [ is special, ] is not
+    g_regexTester.should_throw(R"(\])", error_escape, option);
+}
+
+void test_gh_5244_atomescape_posix_not_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // reject awk-only escapes
+    g_regexTester.should_throw(R"(\a)", error_escape, option);
+    g_regexTester.should_throw(R"(\b)", error_escape, option);
+    g_regexTester.should_throw(R"(\f)", error_escape, option);
+    g_regexTester.should_throw(R"(\n)", error_escape, option);
+    g_regexTester.should_throw(R"(\r)", error_escape, option);
+    g_regexTester.should_throw(R"(\t)", error_escape, option);
+    g_regexTester.should_throw(R"(\v)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // Section on "BREs Matching Multiple Characters":
+    // \ plus digit is backreference to previously completed subexpression
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_backref, option);
+    g_regexTester.should_match("aa", R"(\(a\)\1)", option);
+    g_regexTester.should_throw(R"(\(a\)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1\(a\))", error_backref, option);
+
+    // check that the parser rejects escaped characters
+    // that are only special in extended regexes or awk
+    g_regexTester.should_throw(R"(\+)", error_escape, option);
+    g_regexTester.should_throw(R"(\?)", error_escape, option);
+    g_regexTester.should_throw(R"(\|)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_extended_egrep_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // check that the parser accepts escaped characters
+    // that are only special in extended regexes
+    check_atomescape_identityescape("+", option);
+    check_atomescape_identityescape("?", option);
+    check_atomescape_identityescape("|", option);
+    check_atomescape_identityescape("(", option);
+    check_atomescape_identityescape(")", option);
+    check_atomescape_identityescape("{", option);
+
+    // Even though { is special, } is not,
+    // so the interpretation of the escape sequence \} is undefined
+    // according to the POSIX standard referenced in the C++ standard.
+    // But we treat \} as an identity escape in line with
+    // more recent versions of the POSIX standard.
+    check_atomescape_identityescape("}", option);
+}
+
+void test_gh_5244_atomescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_atomescape_extended_egrep_awk(option);
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // there are no backreferences in extended regexes
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1(a))", error_escape, option);
+}
+
+void test_gh_5244_atomescape_awk() {
+    test_gh_5244_atomescape_extended_egrep_awk(awk);
+
+    // awk-only escapes
+    check_atomescape_controlescape("\a", "a", awk);
+    check_atomescape_controlescape("\b", "b", awk);
+    check_atomescape_controlescape("\f", "f", awk);
+    check_atomescape_controlescape("\n", "n", awk);
+    check_atomescape_controlescape("\r", "r", awk);
+    check_atomescape_controlescape("\t", "t", awk);
+    check_atomescape_controlescape("\v", "v", awk);
+    check_atomescape_identityescape("\"", awk);
+    check_atomescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_not_match("1", R"(\1)", awk);
+    g_regexTester.should_not_match("\\1", R"(\1)", awk);
+    g_regexTester.should_match("\11", R"(\11)", awk);
+    g_regexTester.should_not_match("1", R"(\11)", awk);
+    g_regexTester.should_not_match("11", R"(\11)", awk);
+    g_regexTester.should_not_match("\\11", R"(\11)", awk);
+    g_regexTester.should_match("\111", R"(\111)", awk);
+    g_regexTester.should_not_match("1", R"(\111)", awk);
+    g_regexTester.should_not_match("11", R"(\111)", awk);
+    g_regexTester.should_not_match("111", R"(\111)", awk);
+    g_regexTester.should_not_match("\\111", R"(\111)", awk);
+    g_regexTester.should_match("\111"s + "1", R"(\1111)", awk);
+    g_regexTester.should_not_match("\111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1", R"(\1111)", awk);
+    g_regexTester.should_not_match("11", R"(\1111)", awk);
+    g_regexTester.should_not_match("111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1111", R"(\1111)", awk);
+    g_regexTester.should_not_match("\\1111", R"(\1111)", awk);
+    g_regexTester.should_match("A", R"(\101)", awk);
+    g_regexTester.should_match("Aa", R"(\101a)", awk);
+    g_regexTester.should_match("\33", R"(\033)", awk);
+    g_regexTester.should_match("\33a", R"(\033a)", awk);
+    g_regexTester.should_match("\33", R"(\33)", awk);
+    g_regexTester.should_match("\33a", R"(\33a)", awk);
+    g_regexTester.should_match("\1", R"(\001)", awk);
+    g_regexTester.should_match("\1a", R"(\001a)", awk);
+    g_regexTester.should_match("\1", R"(\01)", awk);
+    g_regexTester.should_match("\1a", R"(\01a)", awk);
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_match("\1a", R"(\1a)", awk);
+    g_regexTester.should_throw(R"(\8)", error_escape, awk);
+    g_regexTester.should_match("\1"s + "8", R"(\18)", awk);
+    g_regexTester.should_match("\12"s + "9", R"(\129)", awk);
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"(\0)", error_escape, awk);
+    g_regexTester.should_throw(R"(\00)", error_escape, awk);
+    g_regexTester.should_throw(R"(\000)", error_escape, awk);
+}
+
+void test_gh_5244_classescape_posix_not_awk(syntax_option_type option) {
+    // Sections "BRE Bracket Expressions" and "ERE Bracket Expressions":
+    // Backslash shall lose its special meaning, so \c should always match \ + c
+
+    // common special characters outside character classes
+    check_classescape_noescape(".", option);
+    check_classescape_noescape("[", option);
+    check_classescape_noescape("*", option);
+    check_classescape_noescape("^", option);
+    check_classescape_noescape("$", option);
+    check_classescape_noescape("B", option);
+    check_classescape_noescape("c", option);
+
+    // special characters outside character classes in extended regexes
+    check_classescape_noescape("+", option);
+    check_classescape_noescape("?", option);
+    check_classescape_noescape("|", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep)) {
+        check_classescape_noescape("(", option);
+        check_classescape_noescape(")", option);
+        check_classescape_noescape("{", option);
+    }
+
+    // closing characters that are not considered special
+    g_regexTester.should_match("\\]", R"([\]])", option);
+    g_regexTester.should_not_match("]", R"([\]])", option);
+    g_regexTester.should_not_match("\\", R"([\]])", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep)) {
+        check_classescape_noescape("}", option);
+    }
+
+    // awk escape sequences
+    check_classescape_noescape("a", option);
+    check_classescape_noescape("b", option);
+    check_classescape_noescape("f", option);
+    check_classescape_noescape("n", option);
+    check_classescape_noescape("r", option);
+    check_classescape_noescape("t", option);
+    check_classescape_noescape("v", option);
+    check_classescape_noescape("\"", option);
+    check_classescape_noescape("/", option);
+
+    // awk octal sequences
+    check_classescape_noescape("0", option);
+    check_classescape_noescape("1", option);
+    g_regexTester.should_match("1", R"([\101])", option);
+    g_regexTester.should_match("0", R"([\101])", option);
+    g_regexTester.should_match("\\", R"([\101])", option);
+    g_regexTester.should_match("1", R"([\101a])", option);
+    g_regexTester.should_match("0", R"([\101a])", option);
+    g_regexTester.should_match("\\", R"([\101a])", option);
+    g_regexTester.should_match("a", R"([\101a])", option);
+    g_regexTester.should_match("3", R"([\033a])", option);
+    g_regexTester.should_match("0", R"([\033a])", option);
+    g_regexTester.should_match("\\", R"([\033a])", option);
+    g_regexTester.should_match("a", R"([\033a])", option);
+    g_regexTester.should_match("3", R"([\33a])", option);
+    g_regexTester.should_match("\\", R"([\33a])", option);
+    g_regexTester.should_match("a", R"([\33a])", option);
+    g_regexTester.should_match("1", R"([\001a])", option);
+    g_regexTester.should_match("0", R"([\001a])", option);
+    g_regexTester.should_match("\\", R"([\001a])", option);
+    g_regexTester.should_match("a", R"([\001a])", option);
+
+    // ECMAScript escape sequences
+    g_regexTester.should_match("c", R"([\ca])", option);
+    g_regexTester.should_match("a", R"([\ca])", option);
+    g_regexTester.should_match("\\", R"([\ca])", option);
+    g_regexTester.should_match("x", R"([\x000])", option);
+    g_regexTester.should_match("0", R"([\x000])", option);
+    g_regexTester.should_match("\\", R"([\x000])", option);
+    g_regexTester.should_match("u", R"([\u0000])", option);
+    g_regexTester.should_match("0", R"([\u0000])", option);
+    g_regexTester.should_match("\\", R"([\u0000])", option);
+    check_classescape_noescape("d", option);
+    check_classescape_noescape("D", option);
+    check_classescape_noescape("w", option);
+    check_classescape_noescape("W", option);
+    check_classescape_noescape("s", option);
+    check_classescape_noescape("S", option);
+}
+
+void test_gh_5244_classescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\1])", option);
+    g_regexTester.should_not_match("aa", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a0", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1]\(a\))", option);
+    g_regexTester.should_match("\\a", R"([\1]\(a\))", option);
+    g_regexTester.should_not_match("aa", R"([\1]\(a\))", option);
+}
+
+void test_gh_5244_classescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"((a)[\1])", option);
+    g_regexTester.should_match("a\\", R"((a)[\1])", option);
+    g_regexTester.should_not_match("aa", R"((a)[\1])", option);
+    g_regexTester.should_match("a0", R"((a)[\0])", option);
+    g_regexTester.should_match("a\\", R"((a)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1](a))", option);
+    g_regexTester.should_match("\\a", R"([\1](a))", option);
+    g_regexTester.should_not_match("aa", R"([\1](a))", option);
+}
+
+void test_gh_5244_classescape_awk() {
+    // awk-only sequences and backslash
+    g_regexTester.should_match("\\", "[\\\\]", awk);
+    g_regexTester.should_not_match("g", "[\\\\]", awk);
+    check_classescape_controlescape("\a", "a", awk);
+    check_classescape_controlescape("\b", "b", awk);
+    check_classescape_controlescape("\f", "f", awk);
+    check_classescape_controlescape("\n", "n", awk);
+    check_classescape_controlescape("\r", "r", awk);
+    check_classescape_controlescape("\t", "t", awk);
+    check_classescape_controlescape("\v", "v", awk);
+    check_classescape_identityescape("\"", awk);
+    check_classescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"([\1])", awk);
+    g_regexTester.should_not_match("1", R"([\1])", awk);
+    g_regexTester.should_not_match("\\", R"([\1])", awk);
+    g_regexTester.should_match("\11", R"([\11])", awk);
+    g_regexTester.should_not_match("1", R"([\11])", awk);
+    g_regexTester.should_not_match("\\", R"([\11])", awk);
+    g_regexTester.should_match("\111", R"([\111])", awk);
+    g_regexTester.should_not_match("0", R"([\111])", awk);
+    g_regexTester.should_not_match("\\", R"([\111])", awk);
+    g_regexTester.should_match("\111", R"([\1111])", awk);
+    g_regexTester.should_match("1", R"([\1111])", awk);
+    g_regexTester.should_not_match("\\", R"([\1111])", awk);
+    g_regexTester.should_match("A", R"([\101])", awk);
+    g_regexTester.should_not_match("\\", R"([\101])", awk);
+    g_regexTester.should_not_match("1", R"([\101])", awk);
+    g_regexTester.should_not_match("0", R"([\101])", awk);
+    g_regexTester.should_not_match("\1", R"([\101])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\101])", awk);
+    g_regexTester.should_match("A", R"([\101a])", awk);
+    g_regexTester.should_match("a", R"([\101a])", awk);
+    g_regexTester.should_not_match("\\", R"([\101a])", awk);
+    g_regexTester.should_not_match("1", R"([\101a])", awk);
+    g_regexTester.should_not_match("0", R"([\101a])", awk);
+    g_regexTester.should_not_match("\1", R"([\101a])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\101a])", awk);
+    g_regexTester.should_match("\33", R"([\033])", awk);
+    g_regexTester.should_not_match("\\", R"([\033])", awk);
+    g_regexTester.should_not_match("3", R"([\033])", awk);
+    g_regexTester.should_not_match("0", R"([\033])", awk);
+    g_regexTester.should_not_match("\3", R"([\033])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\033])", awk);
+    g_regexTester.should_match("\33", R"([\033a])", awk);
+    g_regexTester.should_match("a", R"([\033a])", awk);
+    g_regexTester.should_not_match("\\", R"([\033a])", awk);
+    g_regexTester.should_not_match("3", R"([\033a])", awk);
+    g_regexTester.should_not_match("0", R"([\033a])", awk);
+    g_regexTester.should_not_match("\3", R"([\033a])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\033a])", awk);
+    g_regexTester.should_match("\33", R"([\33])", awk);
+    g_regexTester.should_not_match("\\", R"([\33])", awk);
+    g_regexTester.should_not_match("3", R"([\33])", awk);
+    g_regexTester.should_not_match("\3", R"([\33])", awk);
+    g_regexTester.should_match("\33", R"([\33a])", awk);
+    g_regexTester.should_match("a", R"([\33a])", awk);
+    g_regexTester.should_not_match("\\", R"([\33a])", awk);
+    g_regexTester.should_not_match("3", R"([\33a])", awk);
+    g_regexTester.should_not_match("\3", R"([\33a])", awk);
+    g_regexTester.should_match("\1", R"([\001])", awk);
+    g_regexTester.should_not_match("\\", R"([\001])", awk);
+    g_regexTester.should_not_match("1", R"([\001])", awk);
+    g_regexTester.should_not_match("0", R"([\001])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\001])", awk);
+    g_regexTester.should_match("\1", R"([\001a])", awk);
+    g_regexTester.should_match("a", R"([\001a])", awk);
+    g_regexTester.should_not_match("\\", R"([\001a])", awk);
+    g_regexTester.should_not_match("1", R"([\001a])", awk);
+    g_regexTester.should_not_match("0", R"([\001a])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\001a])", awk);
+    g_regexTester.should_match("\1", R"([\01])", awk);
+    g_regexTester.should_not_match("\\", R"([\01])", awk);
+    g_regexTester.should_not_match("1", R"([\01])", awk);
+    g_regexTester.should_not_match("0", R"([\01])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\01])", awk);
+    g_regexTester.should_match("\1", R"([\01a])", awk);
+    g_regexTester.should_match("a", R"([\01a])", awk);
+    g_regexTester.should_not_match("\\", R"([\01a])", awk);
+    g_regexTester.should_not_match("1", R"([\01a])", awk);
+    g_regexTester.should_not_match("0", R"([\01a])", awk);
+    g_regexTester.should_not_match("\0"s, R"([\01a])", awk);
+    g_regexTester.should_match("\1", R"([\1])", awk);
+    g_regexTester.should_not_match("\\", R"([\1])", awk);
+    g_regexTester.should_not_match("1", R"([\1])", awk);
+    g_regexTester.should_match("\1", R"([\1a])", awk);
+    g_regexTester.should_match("a", R"([\1a])", awk);
+    g_regexTester.should_not_match("\\", R"([\1a])", awk);
+    g_regexTester.should_not_match("1", R"([\1a])", awk);
+    g_regexTester.should_throw(R"([\8])", error_escape, awk);
+    g_regexTester.should_match("\1", R"([\18])", awk);
+    g_regexTester.should_match("8", R"([\18])", awk);
+    g_regexTester.should_not_match("\\", R"([\18])", awk);
+    g_regexTester.should_not_match("1", R"([\18])", awk);
+    g_regexTester.should_match("\12", R"([\129])", awk);
+    g_regexTester.should_match("9", R"([\129])", awk);
+    g_regexTester.should_not_match("\\", R"([\129])", awk);
+    g_regexTester.should_not_match("1", R"([\129])", awk);
+    g_regexTester.should_not_match("2", R"([\129])", awk);
+    g_regexTester.should_not_match("\131", R"([\129])", awk);
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"([\0])", error_escape, awk);
+    g_regexTester.should_throw(R"([\00])", error_escape, awk);
+    g_regexTester.should_throw(R"([\000])", error_escape, awk);
+
+    // all other escapes (including the atom escapes of basic and extended regexes) are undefined
+    g_regexTester.should_throw(R"([\.])", error_escape, awk);
+    g_regexTester.should_throw(R"([\[])", error_escape, awk);
+    g_regexTester.should_throw(R"([\*])", error_escape, awk);
+    g_regexTester.should_throw(R"([\^])", error_escape, awk);
+    g_regexTester.should_throw(R"([\$])", error_escape, awk);
+    g_regexTester.should_throw(R"([\]])", error_escape, awk);
+    g_regexTester.should_throw(R"([\+])", error_escape, awk);
+    g_regexTester.should_throw(R"([\?])", error_escape, awk);
+    g_regexTester.should_throw(R"([\|])", error_escape, awk);
+    g_regexTester.should_throw(R"([\(])", error_escape, awk);
+    g_regexTester.should_throw(R"([\)])", error_escape, awk);
+    g_regexTester.should_throw(R"([\{])", error_escape, awk);
+    g_regexTester.should_throw(R"([\}])", error_escape, awk);
+    g_regexTester.should_throw(R"([\B])", error_escape, awk);
+    g_regexTester.should_throw(R"([\c])", error_escape, awk);
+    g_regexTester.should_throw(R"([\ca])", error_escape, awk);
+    g_regexTester.should_throw(R"([\x000])", error_escape, awk);
+    g_regexTester.should_throw(R"([\u0000])", error_escape, awk);
+    g_regexTester.should_throw(R"([\d])", error_escape, awk);
+    g_regexTester.should_throw(R"([\D])", error_escape, awk);
+    g_regexTester.should_throw(R"([\w])", error_escape, awk);
+    g_regexTester.should_throw(R"([\W])", error_escape, awk);
+    g_regexTester.should_throw(R"([\s])", error_escape, awk);
+    g_regexTester.should_throw(R"([\S])", error_escape, awk);
+}
+
+void test_gh_5244() {
+    // GH-5244: <regex>: Some escape sequences are mishandled
+    test_gh_5244_atomescape_ecmascript();
+    test_gh_5244_classescape_ecmascript();
+
+    for (syntax_option_type basic_or_grep : {basic, grep}) {
+        test_gh_5244_atomescape_basic_or_grep(basic_or_grep);
+        test_gh_5244_classescape_basic_or_grep(basic_or_grep);
+    }
+
+    for (syntax_option_type extended_or_egrep : {extended, egrep}) {
+        test_gh_5244_atomescape_extended_or_egrep(extended_or_egrep);
+        test_gh_5244_classescape_extended_or_egrep(extended_or_egrep);
+    }
+
+    test_gh_5244_atomescape_awk();
+    test_gh_5244_classescape_awk();
+}
+
+int main() {
+    test_gh_5244();
+
+    return g_regexTester.result();
+}
diff --git a/tests/tr1/tests/regex2/test.cpp b/tests/tr1/tests/regex2/test.cpp
index b720710ff9f..630b96463ac 100644
--- a/tests/tr1/tests/regex2/test.cpp
+++ b/tests/tr1/tests/regex2/test.cpp
@@ -434,7 +434,6 @@ static const regex_test tests[] = {
     {__LINE__, T("[]"), T(""), "-1", NOT_ECMA},
     {__LINE__, T("[]]"), T("]"), "-1", ECMA},
     {__LINE__, T("[]]"), T("]"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\]]"), T("]"), "1 0 1", ECMA},
     {__LINE__, T("[^]"), T("^"), "1 0 1", ECMA},
     {__LINE__, T("[^]]"), T("^"), "1 0 1", NOT_ECMA},
     {__LINE__, T("[^]]"), T("]"), "0", NOT_ECMA},
@@ -460,8 +459,7 @@ static const regex_test tests[] = {
     {__LINE__, T("[\\a]"), T("\\"), "1 0 1", BASIC | GREP | EXTENDED | EGREP},
     {__LINE__, T("[\\a]"), T("a"), "1 0 1", ECMA | BASIC | GREP | EXTENDED | EGREP},
 
-    {__LINE__, T("[\\b]"), T("\b"), "1 0 1", AWK},
-    {__LINE__, T("[\\b]"), T(""), "0", ECMA},
+    {__LINE__, T("[\\b]"), T("\b"), "1 0 1", AWK | ECMA},
     {__LINE__, T("[\\b]"), T("\\"), "1 0 1", BASIC | GREP | EXTENDED | EGREP},
     {__LINE__, T("[\\b]"), T("b"), "1 0 1", BASIC | GREP | EXTENDED | EGREP},
 
@@ -487,45 +485,65 @@ static const regex_test tests[] = {
 
     {__LINE__, T("[\\ca]"), T("\x01"), "1 0 1", ECMA},
     {__LINE__, T("[\\c3]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
-    {__LINE__, T("[\\c3]"), T("c"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\c3]"), T("3"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\c3]"), T("c"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\c3]"), T("3"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\c3]"), T("c"), "-1", AWK},
 
     {__LINE__, T("[\\x1b]"), T("\x1b"), "1 0 1", ECMA},
     {__LINE__, T("[\\x1b]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
-    {__LINE__, T("[\\x1b]"), T("x"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\x1b]"), T("1"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\x1b]"), T("b"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\x1b]"), T("x"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("1"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("b"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("b"), "-1", AWK},
 #ifdef WIDE
     {__LINE__, T("[\\u12cd]"), T("\u12cd"), "1 0 1", ECMA},
 #else // defined WIDE
     {__LINE__, T("[\\u12cd]"), T(""), "-1", ECMA},
 #endif // defined WIDE
-    {__LINE__, T("[\\u12cd]"), T("\\"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("u"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("1"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("2"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("c"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("d"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\u12cd]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("u"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("1"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("2"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("c"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("d"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("d"), "-1", AWK},
 
     {__LINE__, T("[\\\\]"), T("\\"), "1 0 1", ALL},
-    {__LINE__, T("[\\^]"), T("^"), "1 0 1", ALL},
-    {__LINE__, T("[\\(]"), T("("), "1 0 1", ALL},
-    {__LINE__, T("[\\)]"), T(")"), "1 0 1", ALL},
-    {__LINE__, T("[\\{]"), T("{"), "1 0 1", ALL},
-    {__LINE__, T("[\\}]"), T("}"), "1 0 1", ALL},
-    {__LINE__, T("[\\[]"), T("["), "1 0 1", ALL},
+    {__LINE__, T("[\\^]"), T("^"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\^]"), T("^"), "-1", AWK},
+    {__LINE__, T("[\\(]"), T("("), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\(]"), T("("), "-1", AWK},
+    {__LINE__, T("[\\)]"), T(")"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\)]"), T(")"), "-1", AWK},
+    {__LINE__, T("[\\{]"), T("{"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\{]"), T("{"), "-1", AWK},
+    {__LINE__, T("[\\}]"), T("}"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\}]"), T("}"), "-1", AWK},
+    {__LINE__, T("[\\[]"), T("["), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\[]"), T("["), "-1", AWK},
     {__LINE__, T("[\\]]"), T("]"), "1 0 1", ECMA},
-    {__LINE__, T("[\\+]"), T("+"), "1 0 1", ALL},
-    {__LINE__, T("[\\*]"), T("*"), "1 0 1", ALL},
-    {__LINE__, T("[\\?]"), T("?"), "1 0 1", ALL},
+    {__LINE__, T("[\\]]"), T("\\]"), "1 0 2", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\]]"), T("]"), "-1", AWK},
+    {__LINE__, T("[\\+]"), T("+"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\+]"), T("+"), "-1", AWK},
+    {__LINE__, T("[\\*]"), T("*"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\*]"), T("*"), "-1", AWK},
+    {__LINE__, T("[\\?]"), T("?"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\?]"), T("?"), "-1", AWK},
 
     // character class escape
-    {__LINE__, T("[\\d]"), T("1"), "0", NOT_ECMA},
-    {__LINE__, T("[\\D]"), T("a"), "0", NOT_ECMA},
-    {__LINE__, T("[\\s]"), T(" "), "0", NOT_ECMA},
-    {__LINE__, T("[\\S]"), T("a"), "0", NOT_ECMA},
-    {__LINE__, T("[\\w]"), T("a"), "0", NOT_ECMA},
-    {__LINE__, T("[\\W]"), T(" "), "0", NOT_ECMA},
+    {__LINE__, T("[\\d]"), T("1"), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\d]"), T("1"), "-1", AWK},
+    {__LINE__, T("[\\D]"), T("a"), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\D]"), T("a"), "-1", AWK},
+    {__LINE__, T("[\\s]"), T(" "), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\s]"), T(" "), "-1", AWK},
+    {__LINE__, T("[\\S]"), T("a"), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\S]"), T("a"), "-1", AWK},
+    {__LINE__, T("[\\w]"), T("a"), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\w]"), T("a"), "-1", AWK},
+    {__LINE__, T("[\\W]"), T(" "), "0", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\W]"), T(" "), "-1", AWK},
 
     // named character classes
     {__LINE__, T("[[:alnum:]]"), T("b"), "1 0 1", ALL},


Reviews:
user: login: muellerj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T14:37:22Z
user: login: muellerj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T18:01:07Z
user: login: StephanTLavavej
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T11:17:50Z
user: login: StephanTLavavej
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T11:18:36Z
user: login: StephanTLavavej
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T11:20:55Z
user: login: StephanTLavavej
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T06:51:45Z

Review comments:
diff_hunk: @@ -4406,22 +4407,24 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_AtomEscape() { // check for valid atom escape
     constexpr int _Bre_max_backref_digits = 1;
-    if ((_L_flags & _L_bckr)
-        && _DecimalDigits2(regex_constants::error_backref,
-            (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
-        if (_Val == 0) { // handle \0
-            if (!(_L_flags & _L_bzr_chr)) {
-                _Error(regex_constants::error_escape);
-            } else {
-                _Nfa._Add_char2(static_cast<_Elem>(_Val));
-            }
+    if ((_L_flags & _L_bzr_chr) && _Traits.value(_Char, 10) == 0) { // handle \0
+        _Next();
+        if (_Traits.value(_Char, 10) != -1) {
+            _Error(regex_constants::error_escape);
+        }
+        _Nfa._Add_char2(_Elem{});
+    } else if ((_L_flags & _L_bckr)
+               && _DecimalDigits2(regex_constants::error_backref,
+                   (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
path: stl/inc/regex
user: login: muellerj2
body: This is processing the first character as a decimal digit twice, but I didn't find an elegant way to adjust the control flow such that this is avoided.
created_at: 2025-03-30T14:37:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4406,22 +4407,24 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_AtomEscape() { // check for valid atom escape
     constexpr int _Bre_max_backref_digits = 1;
-    if ((_L_flags & _L_bckr)
-        && _DecimalDigits2(regex_constants::error_backref,
-            (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
-        if (_Val == 0) { // handle \0
-            if (!(_L_flags & _L_bzr_chr)) {
-                _Error(regex_constants::error_escape);
-            } else {
-                _Nfa._Add_char2(static_cast<_Elem>(_Val));
-            }
+    if ((_L_flags & _L_bzr_chr) && _Traits.value(_Char, 10) == 0) { // handle \0
+        _Next();
+        if (_Traits.value(_Char, 10) != -1) {
+            _Error(regex_constants::error_escape);
+        }
+        _Nfa._Add_char2(_Elem{});
+    } else if ((_L_flags & _L_bckr)
+               && _DecimalDigits2(regex_constants::error_backref,
+                   (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
path: stl/inc/regex
user: login: muellerj2
body: I adjusted the logic now to avoid this double-processing of the first digit in the latest commit, but I'm not sure if I like this change. If you prefer the code before these changes, feel free to undo the commit.
created_at: 2025-03-31T18:01:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4278,7 +4275,8 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_Wrapped_disjunction() { // add disjunct
 }
 
 template <class _FwdIt, class _Elem, class _RxTraits>
-bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape() const { // check for valid identity escape
+bool _Parser<_FwdIt, _Elem, _RxTraits>::_IsIdentityEscape(
+    bool _In_character_class) const { // check for valid identity escape
path: stl/inc/regex
user: login: StephanTLavavej
body: Style nitpick: When adding parameters causes a function signature to wrap like this, we usually "detach" the comment from its opening brace.
created_at: 2025-04-09T07:47:19Z
author_association: MEMBER
diff_hunk: @@ -4405,23 +4406,29 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
 
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_AtomEscape() { // check for valid atom escape
-    constexpr int _Bre_max_backref_digits = 1;
-    if ((_L_flags & _L_bckr)
-        && _DecimalDigits2(regex_constants::error_backref,
-            (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
-        if (_Val == 0) { // handle \0
-            if (!(_L_flags & _L_bzr_chr)) {
+    if ((_L_flags & (_L_bzr_chr | _L_bckr)) && (_Val = _Traits.value(_Char, 10)) != -1) { // escaped decimal sequence
+        _Next();
+        if ((_L_flags & _L_bzr_chr) && _Val == 0) { // handle \0
+            if (_Traits.value(_Char, 10) != -1) {
                 _Error(regex_constants::error_escape);
+            }
+            _Nfa._Add_char2(_Elem{});
+        } else if (_L_flags & _L_bckr) { // check for valid backreference
+
+            if (!(_L_flags & _L_lim_bckr)) {
path: stl/inc/regex
user: login: StephanTLavavej
body: Style nitpick: We usually don't put a newline here.
created_at: 2025-04-09T07:50:16Z
author_association: MEMBER
diff_hunk: @@ -4526,7 +4533,7 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_Alternative() { // check for valid alte
             _Error(regex_constants::error_badrepeat);
         } else if (_Mchar == _Meta_rbr && !(_L_flags & _L_paren_bal)) {
             _Error(regex_constants::error_brace);
-        } else if (_Mchar == _Meta_rsq && !(_L_flags & _L_paren_bal)) {
+        } else if (_Mchar == _Meta_rsq && !(_L_flags & (_L_paren_bal | _L_brk_rstr))) {
             _Error(regex_constants::error_brack);
path: stl/inc/regex
user: login: StephanTLavavej
body: You explained:

> * basic, grep: The regular expression `]` is accepted now, as [the POSIX standard requires because it does not declare `]` to be a special character](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_02)). (I noticed this issue when I added `[\]]` as a test case.)
>   * Unfortunately, there is no truly suitable `_L_flags` for this, so I check one of the flags that are set for basic regular expressions but not for any of the other supported grammars. Setting `_L_paren_bal` for basic regular expressions would make the parser accept wrong usages of `\}` and `\)` as well.

But `_L_brk_rstr` is set for both the `_Basic_flags` and `_Extended_flags` families:

https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L1799
https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L4662-L4663
https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L4667-L4668

In this case, the effective change is indeed limited to the `_Basic_flags` family because the `_Extended_flags` family is the only one that sets `_L_paren_bal` which is also being tested here. So really this is just a nitpick for the description, but it got me thinking about the following.

In `main`, here are all of the uses of `_L_paren_bal`:

https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L1798
https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L4484-L4485
https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L4527-L4530
https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/stl/inc/regex#L4667-L4668

My concerns are:

* The comment for `_L_brk_rstr` is now confusing:
  + It currently talks about "first character in set" which doesn't apply to this usage
  + The additional meaning being imbued here is not mentioned
* The comment for `_L_paren_bal` is now confusing because it mentions `']'` but doesn't solely control that behavior anymore
* The comment for `_L_paren_bal` has a pre-existing typo - observe how `only after '('/'{'/']'` says open/open/close :joy_cat:

So I propose transforming your ABI-compatible solution, keeping the same behavior:

* Introduce a new named flag, identical in value to `_L_brk_rstr`, which will be active for both the `_Basic_flags` and `_Extended_flags` families.
* Transfer the square bracket part of `_L_paren_bal`'s comment to this new named flag
* And fix the pre-existing typo while we're at it

created_at: 2025-04-09T08:48:57Z
author_association: MEMBER
diff_hunk: @@ -487,45 +486,63 @@ static const regex_test tests[] = {
 
     {__LINE__, T("[\\ca]"), T("\x01"), "1 0 1", ECMA},
     {__LINE__, T("[\\c3]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
-    {__LINE__, T("[\\c3]"), T("c"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\c3]"), T("3"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\c3]"), T("c"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\c3]"), T("3"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\c3]"), T("c"), "-1", AWK},
 
     {__LINE__, T("[\\x1b]"), T("\x1b"), "1 0 1", ECMA},
     {__LINE__, T("[\\x1b]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
-    {__LINE__, T("[\\x1b]"), T("x"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\x1b]"), T("1"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\x1b]"), T("b"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\x1b]"), T("x"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("1"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("b"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\x1b]"), T("b"), "-1", AWK},
 #ifdef WIDE
     {__LINE__, T("[\\u12cd]"), T("\u12cd"), "1 0 1", ECMA},
 #else // defined WIDE
     {__LINE__, T("[\\u12cd]"), T(""), "-1", ECMA},
 #endif // defined WIDE
-    {__LINE__, T("[\\u12cd]"), T("\\"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("u"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("1"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("2"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("c"), "1 0 1", NOT_ECMA},
-    {__LINE__, T("[\\u12cd]"), T("d"), "1 0 1", NOT_ECMA},
+    {__LINE__, T("[\\u12cd]"), T("\\"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("u"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("1"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("2"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("c"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("d"), "1 0 1", BASIC | EXTENDED | GREP | EGREP},
+    {__LINE__, T("[\\u12cd]"), T("d"), "-1", AWK},
 
     {__LINE__, T("[\\\\]"), T("\\"), "1 0 1", ALL},
-    {__LINE__, T("[\\^]"), T("^"), "1 0 1", ALL},
-    {__LINE__, T("[\\(]"), T("("), "1 0 1", ALL},
-    {__LINE__, T("[\\)]"), T(")"), "1 0 1", ALL},
-    {__LINE__, T("[\\{]"), T("{"), "1 0 1", ALL},
-    {__LINE__, T("[\\}]"), T("}"), "1 0 1", ALL},
-    {__LINE__, T("[\\[]"), T("["), "1 0 1", ALL},
+    {__LINE__, T("[\\^]"), T("^"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\^]"), T("^"), "-1", AWK},
+    {__LINE__, T("[\\(]"), T("("), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\(]"), T("("), "-1", AWK},
+    {__LINE__, T("[\\)]"), T(")"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\)]"), T(")"), "-1", AWK},
+    {__LINE__, T("[\\{]"), T("{"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\{]"), T("{"), "-1", AWK},
+    {__LINE__, T("[\\}]"), T("}"), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\}]"), T("}"), "-1", AWK},
+    {__LINE__, T("[\\[]"), T("["), "1 0 1", NOT_AWK},
+    {__LINE__, T("[\\[]"), T("["), "-1", AWK},
     {__LINE__, T("[\\]]"), T("]"), "1 0 1", ECMA},
path: tests/tr1/tests/regex2/test.cpp
user: login: StephanTLavavej
body: Pre-existing: This line is exactly repeated above:

https://github.com/microsoft/STL/blob/6d253467512d9a4295fdf25fa6fc92f898c9d52c/tests/tr1/tests/regex2/test.cpp#L437

I think we should remove the first occurrence since the second occurrence here is grouped with all of the other character escapes.

But this originally caught my eye because we're testing it only for ECMA. While our general rule is to refrain from cleaning up `tr1`, we of course have to modify it in response to behavior changes as you're doing here, and it seems worthwhile to make this test consistent with the others that are being split.
created_at: 2025-04-09T09:27:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: We can avoid the need to suppress clang-format by concatenating `std::string` UDLs with `+`.
created_at: 2025-04-09T09:56:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: We should include `<cstdio>` for `printf`.
created_at: 2025-04-09T10:05:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\u0000""0"s, R"(\u00000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
+
+    g_regexTester.should_match("a", R"(\w)", ECMAScript);
+    g_regexTester.should_match("A", R"(\w)", ECMAScript);
+    g_regexTester.should_match("0", R"(\w)", ECMAScript);
+    g_regexTester.should_match("_", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("A", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\W)", ECMAScript);
+    g_regexTester.should_match("-", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\W)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\W)", ECMAScript);
+
+    g_regexTester.should_match(" ", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\t", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\n", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\t", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\n", R"(\S)", ECMAScript);
+    g_regexTester.should_match("a", R"(\S)", ECMAScript);
+    g_regexTester.should_match("0", R"(\S)", ECMAScript);
+    g_regexTester.should_match("_", R"(\S)", ECMAScript);
+    g_regexTester.should_match("-", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\S)", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("g", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("\\\\", R"(\\)", ECMAScript);
+    check_atomescape_identityescape("a", ECMAScript);
+    check_atomescape_identityescape("-", ECMAScript);
+    check_atomescape_identityescape(" ", ECMAScript);
+    check_atomescape_identityescape("(", ECMAScript);
+    check_atomescape_identityescape(")", ECMAScript);
+    check_atomescape_identityescape("[", ECMAScript);
+    check_atomescape_identityescape("]", ECMAScript);
+    check_atomescape_identityescape("{", ECMAScript);
+    check_atomescape_identityescape("}", ECMAScript);
+    check_atomescape_identityescape("*", ECMAScript);
+    check_atomescape_identityescape("?", ECMAScript);
+    check_atomescape_identityescape("+", ECMAScript);
+    check_atomescape_identityescape("^", ECMAScript);
+    check_atomescape_identityescape("$", ECMAScript);
+    check_atomescape_identityescape(".", ECMAScript);
+
+    {
+        string pattern = R"(\z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"(\Z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_classescape_ecmascript() {
+
+    // ClassEscape :: DecimalEscape
+    // only accepts literal NUL (\0), all other decimal escapes result in error
+    g_regexTester.should_match("\0"s, R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\0])", ECMAScript);
+    g_regexTester.should_throw(R"([\00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\01])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\10])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\01])", error_escape, ECMAScript);
+
+    // ClassEscape :: 'b'
+    check_classescape_controlescape("\b", "b", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: ControlEscape
+    check_classescape_controlescape("\f", "f", ECMAScript);
+    check_classescape_controlescape("\n", "n", ECMAScript);
+    check_classescape_controlescape("\r", "r", ECMAScript);
+    check_classescape_controlescape("\t", "t", ECMAScript);
+    check_classescape_controlescape("\v", "v", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"([\cA])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cZ])", ECMAScript);
+    g_regexTester.should_match("\x1", R"([\ca])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cz])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("c", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("ca", R"([\ca])", ECMAScript);
+    g_regexTester.should_throw(R"([\c0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\c])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"([\x00])", ECMAScript);
+    g_regexTester.should_match("\x00"s, R"([\x000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\x000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\x41])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\x41])", ECMAScript);
+    g_regexTester.should_match("\xff", R"([\xff])", ECMAScript);
+    g_regexTester.should_throw(R"([\x])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\x0])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"([\u0000])", ECMAScript);
+    g_regexTester.should_match("\u0000"s, R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\u0041])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\u0041])", ECMAScript);
+    g_regexTester.should_throw(R"([\uffff])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u2600])", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"([\u])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u000])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"([\d])", ECMAScript);
+    g_regexTester.should_match("5", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\D])", ECMAScript);
+    g_regexTester.should_not_match("5", R"([\D])", ECMAScript);
+    g_regexTester.should_match("a", R"([\D])", ECMAScript);
+    g_regexTester.should_match("_", R"([\D])", ECMAScript);
+    g_regexTester.should_match("-", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\D])", ECMAScript);
+
+    g_regexTester.should_match("a", R"([\w])", ECMAScript);
+    g_regexTester.should_match("A", R"([\w])", ECMAScript);
+    g_regexTester.should_match("0", R"([\w])", ECMAScript);
+    g_regexTester.should_match("_", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("A", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\W])", ECMAScript);
+    g_regexTester.should_match("-", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\W])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\W])", ECMAScript);
+
+    g_regexTester.should_match(" ", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\t", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\n", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\t", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\n", R"([\S])", ECMAScript);
+    g_regexTester.should_match("a", R"([\S])", ECMAScript);
+    g_regexTester.should_match("0", R"([\S])", ECMAScript);
+    g_regexTester.should_match("_", R"([\S])", ECMAScript);
+    g_regexTester.should_match("-", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\S])", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"([\\])", ECMAScript);
+    g_regexTester.should_not_match("b", R"([\\])", ECMAScript);
+    check_classescape_identityescape("a", ECMAScript);
+    check_classescape_identityescape("-", ECMAScript);
+    check_classescape_identityescape(" ", ECMAScript);
+    check_classescape_identityescape("(", ECMAScript);
+    check_classescape_identityescape(")", ECMAScript);
+    check_classescape_identityescape("[", ECMAScript);
+    check_classescape_identityescape("]", ECMAScript);
+    check_classescape_identityescape("{", ECMAScript);
+    check_classescape_identityescape("}", ECMAScript);
+    check_classescape_identityescape("*", ECMAScript);
+    check_classescape_identityescape("?", ECMAScript);
+    check_classescape_identityescape("+", ECMAScript);
+    check_classescape_identityescape("^", ECMAScript);
+    check_classescape_identityescape("$", ECMAScript);
+    check_classescape_identityescape(".", ECMAScript);
+    check_classescape_identityescape("B", ECMAScript);
+
+    {
+        string pattern = R"([\z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"([\Z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_atomescape_posix_common(syntax_option_type option) {
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // When special character is preceded by backslash, the special character matches itself
+    g_regexTester.should_match("\\", R"(\\)", option);
+    g_regexTester.should_not_match("b", R"(\\)", option);
+    g_regexTester.should_not_match("\\\\", R"(\\)", option);
+    check_atomescape_identityescape(".", option);
+    check_atomescape_identityescape("[", option);
+    check_atomescape_identityescape("*", option);
+    check_atomescape_identityescape("^", option);
+    check_atomescape_identityescape("$", option);
+
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // escaping ordinary characters is undefined -> reject
+    g_regexTester.should_throw(R"(\B)", error_escape, option);
+    g_regexTester.should_throw(R"(\c)", error_escape, option);
+    g_regexTester.should_throw(R"(\ca)", error_escape, option);
+    g_regexTester.should_throw(R"(\x000)", error_escape, option);
+    g_regexTester.should_throw(R"(\u0000)", error_escape, option);
+    g_regexTester.should_throw(R"(\d)", error_escape, option);
+    g_regexTester.should_throw(R"(\D)", error_escape, option);
+    g_regexTester.should_throw(R"(\w)", error_escape, option);
+    g_regexTester.should_throw(R"(\W)", error_escape, option);
+    g_regexTester.should_throw(R"(\s)", error_escape, option);
+    g_regexTester.should_throw(R"(\S)", error_escape, option);
+
+    // while [ is special, ] is not
+    g_regexTester.should_throw(R"(\])", error_escape, option);
+}
+
+void test_gh_5244_atomescape_posix_not_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // reject awk-only escapes
+    g_regexTester.should_throw(R"(\a)", error_escape, option);
+    g_regexTester.should_throw(R"(\b)", error_escape, option);
+    g_regexTester.should_throw(R"(\f)", error_escape, option);
+    g_regexTester.should_throw(R"(\n)", error_escape, option);
+    g_regexTester.should_throw(R"(\t)", error_escape, option);
+    g_regexTester.should_throw(R"(\r)", error_escape, option);
+    g_regexTester.should_throw(R"(\v)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // Section on "BREs Matching Multiple Characters":
+    // \ plus digit is backreference to previously completed subexpression
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_backref, option);
+    g_regexTester.should_match("aa", R"(\(a\)\1)", option);
+    g_regexTester.should_throw(R"(\(a\)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1\(a\))", error_backref, option);
+
+    // check that the parser rejects escaped characters
+    // that are only special in extended regexes or awk
+    g_regexTester.should_throw(R"(\+)", error_escape, option);
+    g_regexTester.should_throw(R"(\?)", error_escape, option);
+    g_regexTester.should_throw(R"(\|)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_extended_egrep_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // check that the parser accepts escaped characters
+    // that are only special in extended regexes
+    check_atomescape_identityescape("+", option);
+    check_atomescape_identityescape("?", option);
+    check_atomescape_identityescape("|", option);
+    check_atomescape_identityescape("(", option);
+    check_atomescape_identityescape(")", option);
+    check_atomescape_identityescape("{", option);
+
+    // Even though { is special, } is not,
+    // so the interpretation of the escape sequence \} is undefined
+    // according to the POSIX standard referenced in the C++ standard.
+    // But we treat \} as an identity escape in line with
+    // more recent versions of the POSIX standard.
+    check_atomescape_identityescape("}", option);
+}
+
+void test_gh_5244_atomescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_atomescape_extended_egrep_awk(option);
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // there are no backreferences in extended regexes
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1(a))", error_escape, option);
+}
+
+void test_gh_5244_atomescape_awk() {
+    test_gh_5244_atomescape_extended_egrep_awk(awk);
+
+    // awk-only escapes
+    check_atomescape_controlescape("\a", "a", awk);
+    check_atomescape_controlescape("\b", "b", awk);
+    check_atomescape_controlescape("\f", "f", awk);
+    check_atomescape_controlescape("\n", "n", awk);
+    check_atomescape_controlescape("\r", "r", awk);
+    check_atomescape_controlescape("\t", "t", awk);
+    check_atomescape_controlescape("\v", "v", awk);
+    check_atomescape_identityescape("\"", awk);
+    check_atomescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_not_match("1", R"(\1)", awk);
+    g_regexTester.should_not_match("\\1", R"(\1)", awk);
+    g_regexTester.should_match("\11", R"(\11)", awk);
+    g_regexTester.should_not_match("1", R"(\11)", awk);
+    g_regexTester.should_not_match("11", R"(\11)", awk);
+    g_regexTester.should_not_match("\\11", R"(\11)", awk);
+    g_regexTester.should_match("\111", R"(\111)", awk);
+    g_regexTester.should_not_match("1", R"(\111)", awk);
+    g_regexTester.should_not_match("11", R"(\111)", awk);
+    g_regexTester.should_not_match("111", R"(\111)", awk);
+    g_regexTester.should_not_match("\\111", R"(\111)", awk);
+    // clang-format off
+    g_regexTester.should_match("\111""1", R"(\1111)", awk);
+    // clang-format on
+    g_regexTester.should_not_match("\111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1", R"(\1111)", awk);
+    g_regexTester.should_not_match("11", R"(\1111)", awk);
+    g_regexTester.should_not_match("111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1111", R"(\1111)", awk);
+    g_regexTester.should_not_match("\\1111", R"(\1111)", awk);
+    g_regexTester.should_match("A", R"(\101)", awk);
+    g_regexTester.should_match("Aa", R"(\101a)", awk);
+    g_regexTester.should_match("\33", R"(\033)", awk);
+    g_regexTester.should_match("\33a", R"(\033a)", awk);
+    g_regexTester.should_match("\33", R"(\33)", awk);
+    g_regexTester.should_match("\33a", R"(\33a)", awk);
+    g_regexTester.should_match("\1", R"(\001)", awk);
+    g_regexTester.should_match("\1a", R"(\001a)", awk);
+    g_regexTester.should_match("\1", R"(\01)", awk);
+    g_regexTester.should_match("\1a", R"(\01a)", awk);
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_match("\1a", R"(\1a)", awk);
+    g_regexTester.should_throw(R"(\8)", error_escape, awk);
+    // clang-format off
+    g_regexTester.should_match("\1""8", R"(\18)", awk);
+    g_regexTester.should_match("\12""9", R"(\129)", awk);
+    // clang-format on
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"(\0)", error_escape, awk);
+    g_regexTester.should_throw(R"(\00)", error_escape, awk);
+    g_regexTester.should_throw(R"(\000)", error_escape, awk);
+}
+
+void test_gh_5244_classescape_posix_not_awk(syntax_option_type option) {
+    // Sections "BRE Bracket Expressions" and "ERE Bracket Expressions":
+    // Backslash shall lose its special meaning, so \c should always match \ + c
+
+    // common special characters outside character classes
+    check_classescape_noescape(".", option);
+    check_classescape_noescape("[", option);
+    check_classescape_noescape("*", option);
+    check_classescape_noescape("^", option);
+    check_classescape_noescape("$", option);
+    check_classescape_noescape("B", option);
+    check_classescape_noescape("c", option);
+
+    // special characters outside character classes in extended regexes
+    check_classescape_noescape("+", option);
+    check_classescape_noescape("?", option);
+    check_classescape_noescape("|", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep | awk)) {
+        check_classescape_noescape("(", option);
+        check_classescape_noescape(")", option);
+        check_classescape_noescape("{", option);
+    }
+
+    // closing characters that are not considered special
+    g_regexTester.should_match("\\]", R"([\]])", option);
+    g_regexTester.should_not_match("]", R"([\]])", option);
+    g_regexTester.should_not_match("\\", R"([\]])", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep | awk)) {
+        check_classescape_noescape("}", option);
+    }
+
+    // awk escape sequences
+    check_classescape_noescape("a", option);
+    check_classescape_noescape("b", option);
+    check_classescape_noescape("f", option);
+    check_classescape_noescape("n", option);
+    check_classescape_noescape("r", option);
+    check_classescape_noescape("t", option);
+    check_classescape_noescape("v", option);
+    check_classescape_noescape("\"", option);
+    check_classescape_noescape("/", option);
+
+    // awk octal sequences
+    check_classescape_noescape("0", option);
+    check_classescape_noescape("1", option);
+    g_regexTester.should_match("1", R"([\101])", option);
+    g_regexTester.should_match("0", R"([\101])", option);
+    g_regexTester.should_match("\\", R"([\101])", option);
+    g_regexTester.should_match("1", R"([\101a])", option);
+    g_regexTester.should_match("0", R"([\101a])", option);
+    g_regexTester.should_match("\\", R"([\101a])", option);
+    g_regexTester.should_match("a", R"([\101a])", option);
+    g_regexTester.should_match("3", R"([\033a])", option);
+    g_regexTester.should_match("0", R"([\033a])", option);
+    g_regexTester.should_match("\\", R"([\033a])", option);
+    g_regexTester.should_match("a", R"([\033a])", option);
+    g_regexTester.should_match("3", R"([\33a])", option);
+    g_regexTester.should_match("\\", R"([\33a])", option);
+    g_regexTester.should_match("a", R"([\33a])", option);
+    g_regexTester.should_match("1", R"([\001a])", option);
+    g_regexTester.should_match("0", R"([\001a])", option);
+    g_regexTester.should_match("\\", R"([\001a])", option);
+    g_regexTester.should_match("a", R"([\001a])", option);
+
+    // ECMAScript escape sequences
+    g_regexTester.should_match("c", R"([\ca])", option);
+    g_regexTester.should_match("a", R"([\ca])", option);
+    g_regexTester.should_match("\\", R"([\ca])", option);
+    g_regexTester.should_match("x", R"([\x000])", option);
+    g_regexTester.should_match("0", R"([\x000])", option);
+    g_regexTester.should_match("\\", R"([\x000])", option);
+    g_regexTester.should_match("u", R"([\u0000])", option);
+    g_regexTester.should_match("0", R"([\u0000])", option);
+    g_regexTester.should_match("\\", R"([\u0000])", option);
+    check_classescape_noescape("d", option);
+    check_classescape_noescape("D", option);
+    check_classescape_noescape("w", option);
+    check_classescape_noescape("W", option);
+    check_classescape_noescape("s", option);
+    check_classescape_noescape("S", option);
+}
+
+void test_gh_5244_classescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\1])", option);
+    g_regexTester.should_not_match("aa", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a0", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1]\(a\))", option);
+    g_regexTester.should_match("\\a", R"([\1]\(a\))", option);
+    g_regexTester.should_not_match("aa", R"([\1]\(a\))", option);
+}
+
+void test_gh_5244_classescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"((a)[\1])", option);
+    g_regexTester.should_match("a\\", R"((a)[\1])", option);
+    g_regexTester.should_not_match("aa", R"((a)[\1])", option);
+    g_regexTester.should_match("a0", R"((a)[\0])", option);
+    g_regexTester.should_match("a\\", R"((a)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1](a))", option);
+    g_regexTester.should_match("\\a", R"([\1](a))", option);
+    g_regexTester.should_not_match("aa", R"([\1](a))", option);
+}
+
+void test_gh_5244_classescape_awk() {
+    // awk-only sequences and backslash
+    g_regexTester.should_match("\\", "[\\\\]", awk);
+    g_regexTester.should_not_match("g", "[\\\\]", awk);
+    check_classescape_controlescape("\a", "a", awk);
+    check_classescape_controlescape("\b", "b", awk);
+    check_classescape_controlescape("\f", "f", awk);
+    check_classescape_controlescape("\n", "n", awk);
+    check_classescape_controlescape("\r", "r", awk);
+    check_classescape_controlescape("\t", "t", awk);
+    check_classescape_controlescape("\v", "v", awk);
+    check_classescape_identityescape("\"", awk);
+    check_classescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"([\1])", awk);
+    g_regexTester.should_not_match("1", R"([\1])", awk);
+    g_regexTester.should_not_match("\\", R"([\1])", awk);
+    g_regexTester.should_match("\11", R"([\11])", awk);
+    g_regexTester.should_not_match("1", R"([\11])", awk);
+    g_regexTester.should_not_match("\\", R"([\11])", awk);
+    g_regexTester.should_match("\111", R"([\111])", awk);
+    g_regexTester.should_not_match("0", R"([\111])", awk);
+    g_regexTester.should_not_match("\\", R"([\111])", awk);
+    g_regexTester.should_match("\111", R"([\1111])", awk);
+    g_regexTester.should_match("1", R"([\1111])", awk);
+    g_regexTester.should_not_match("\\", R"([\1111])", awk);
+    g_regexTester.should_match("A", R"([\101])", awk);
+    g_regexTester.should_not_match("\\", R"([\101])", awk);
+    g_regexTester.should_not_match("1", R"([\101])", awk);
+    g_regexTester.should_not_match("0", R"([\101])", awk);
+    g_regexTester.should_not_match("\1", R"([\101])", awk);
+    g_regexTester.should_not_match("\0", R"([\101])", awk);
+    g_regexTester.should_match("A", R"([\101a])", awk);
+    g_regexTester.should_match("a", R"([\101a])", awk);
+    g_regexTester.should_not_match("\\", R"([\101a])", awk);
+    g_regexTester.should_not_match("1", R"([\101a])", awk);
+    g_regexTester.should_not_match("0", R"([\101a])", awk);
+    g_regexTester.should_not_match("\1", R"([\101a])", awk);
+    g_regexTester.should_not_match("\0", R"([\101a])", awk);
+    g_regexTester.should_match("\33", R"([\033])", awk);
+    g_regexTester.should_not_match("\\", R"([\033])", awk);
+    g_regexTester.should_not_match("3", R"([\033])", awk);
+    g_regexTester.should_not_match("0", R"([\033])", awk);
+    g_regexTester.should_not_match("\3", R"([\033])", awk);
+    g_regexTester.should_not_match("\0", R"([\033])", awk);
+    g_regexTester.should_match("\33", R"([\033a])", awk);
+    g_regexTester.should_match("a", R"([\033a])", awk);
+    g_regexTester.should_not_match("\\", R"([\033a])", awk);
+    g_regexTester.should_not_match("3", R"([\033a])", awk);
+    g_regexTester.should_not_match("0", R"([\033a])", awk);
+    g_regexTester.should_not_match("\3", R"([\033a])", awk);
+    g_regexTester.should_not_match("\0", R"([\033a])", awk);
+    g_regexTester.should_match("\33", R"([\33])", awk);
+    g_regexTester.should_not_match("\\", R"([\33])", awk);
+    g_regexTester.should_not_match("3", R"([\33])", awk);
+    g_regexTester.should_not_match("\3", R"([\33])", awk);
+    g_regexTester.should_match("\33", R"([\33a])", awk);
+    g_regexTester.should_match("a", R"([\33a])", awk);
+    g_regexTester.should_not_match("\\", R"([\33a])", awk);
+    g_regexTester.should_not_match("3", R"([\33a])", awk);
+    g_regexTester.should_not_match("\3", R"([\33a])", awk);
+    g_regexTester.should_match("\1", R"([\001])", awk);
+    g_regexTester.should_not_match("\\", R"([\001])", awk);
+    g_regexTester.should_not_match("1", R"([\001])", awk);
+    g_regexTester.should_not_match("0", R"([\001])", awk);
+    g_regexTester.should_not_match("\0", R"([\001])", awk);
+    g_regexTester.should_match("\1", R"([\001a])", awk);
+    g_regexTester.should_match("a", R"([\001a])", awk);
+    g_regexTester.should_not_match("\\", R"([\001a])", awk);
+    g_regexTester.should_not_match("1", R"([\001a])", awk);
+    g_regexTester.should_not_match("0", R"([\001a])", awk);
+    g_regexTester.should_not_match("\0", R"([\001a])", awk);
+    g_regexTester.should_match("\1", R"([\01])", awk);
+    g_regexTester.should_not_match("\\", R"([\01])", awk);
+    g_regexTester.should_not_match("1", R"([\01])", awk);
+    g_regexTester.should_not_match("0", R"([\01])", awk);
+    g_regexTester.should_not_match("\0", R"([\01])", awk);
+    g_regexTester.should_match("\1", R"([\01a])", awk);
+    g_regexTester.should_match("a", R"([\01a])", awk);
+    g_regexTester.should_not_match("\\", R"([\01a])", awk);
+    g_regexTester.should_not_match("1", R"([\01a])", awk);
+    g_regexTester.should_not_match("0", R"([\01a])", awk);
+    g_regexTester.should_not_match("\0", R"([\01a])", awk);
+    g_regexTester.should_match("\1", R"([\1])", awk);
+    g_regexTester.should_not_match("\\", R"([\1])", awk);
+    g_regexTester.should_not_match("1", R"([\1])", awk);
+    g_regexTester.should_match("\1", R"([\1a])", awk);
+    g_regexTester.should_match("a", R"([\1a])", awk);
+    g_regexTester.should_not_match("\\", R"([\1a])", awk);
+    g_regexTester.should_not_match("1", R"([\1a])", awk);
+    g_regexTester.should_throw(R"([\8])", error_escape, awk);
+    g_regexTester.should_match("\1", R"([\18])", awk);
+    g_regexTester.should_match("8", R"([\18])", awk);
+    g_regexTester.should_not_match("\\", R"([\18])", awk);
+    g_regexTester.should_not_match("1", R"([\18])", awk);
+    g_regexTester.should_match("\12", R"([\129])", awk);
+    g_regexTester.should_match("9", R"([\129])", awk);
+    g_regexTester.should_not_match("\\", R"([\129])", awk);
+    g_regexTester.should_not_match("1", R"([\129])", awk);
+    g_regexTester.should_not_match("2", R"([\129])", awk);
+    g_regexTester.should_not_match("\131", R"([\129])", awk);
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"([\0])", error_escape, awk);
+    g_regexTester.should_throw(R"([\00])", error_escape, awk);
+    g_regexTester.should_throw(R"([\000])", error_escape, awk);
+
+    // all other escapes (including the atom escapes of basic and extended regexes) are undefined
+    g_regexTester.should_throw(R"([\.])", error_escape, awk);
+    g_regexTester.should_throw(R"([\[])", error_escape, awk);
+    g_regexTester.should_throw(R"([\*])", error_escape, awk);
+    g_regexTester.should_throw(R"([\^])", error_escape, awk);
+    g_regexTester.should_throw(R"([\$])", error_escape, awk);
+    g_regexTester.should_throw(R"([\]])", error_escape, awk);
+    g_regexTester.should_throw(R"([\+])", error_escape, awk);
+    g_regexTester.should_throw(R"([\?])", error_escape, awk);
+    g_regexTester.should_throw(R"([\|])", error_escape, awk);
+    g_regexTester.should_throw(R"([\(])", error_escape, awk);
+    g_regexTester.should_throw(R"([\)])", error_escape, awk);
+    g_regexTester.should_throw(R"([\{])", error_escape, awk);
+    g_regexTester.should_throw(R"([\}])", error_escape, awk);
+    g_regexTester.should_throw(R"([\B])", error_escape, awk);
+    g_regexTester.should_throw(R"([\c])", error_escape, awk);
+    g_regexTester.should_throw(R"([\ca])", error_escape, awk);
+    g_regexTester.should_throw(R"([\x000])", error_escape, awk);
+    g_regexTester.should_throw(R"([\u0000])", error_escape, awk);
+    g_regexTester.should_throw(R"([\d])", error_escape, awk);
+    g_regexTester.should_throw(R"([\D])", error_escape, awk);
+    g_regexTester.should_throw(R"([\w])", error_escape, awk);
+    g_regexTester.should_throw(R"([\W])", error_escape, awk);
+    g_regexTester.should_throw(R"([\s])", error_escape, awk);
+    g_regexTester.should_throw(R"([\S])", error_escape, awk);
+}
+
+void test_gh_5244() {
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: The bug title is basically captured in the test directory name, but we usually include it as a comment.
created_at: 2025-04-09T10:08:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: In test code we're more relaxed than in product code, but we usually take strings by const ref to avoid unnecessary copies. (It's worth noting that the vast majority of our tests are built non-optimized.)
created_at: 2025-04-09T10:11:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: Technically we should include `<cstddef>` for `size_t`.
created_at: 2025-04-09T10:18:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\u0000""0"s, R"(\u00000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: We should test spaces against digit/non-digit for completeness.
created_at: 2025-04-09T10:41:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\u0000""0"s, R"(\u00000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
+
+    g_regexTester.should_match("a", R"(\w)", ECMAScript);
+    g_regexTester.should_match("A", R"(\w)", ECMAScript);
+    g_regexTester.should_match("0", R"(\w)", ECMAScript);
+    g_regexTester.should_match("_", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("A", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\W)", ECMAScript);
+    g_regexTester.should_match("-", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\W)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\W)", ECMAScript);
+
+    g_regexTester.should_match(" ", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\t", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\n", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\t", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\n", R"(\S)", ECMAScript);
+    g_regexTester.should_match("a", R"(\S)", ECMAScript);
+    g_regexTester.should_match("0", R"(\S)", ECMAScript);
+    g_regexTester.should_match("_", R"(\S)", ECMAScript);
+    g_regexTester.should_match("-", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\S)", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("g", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("\\\\", R"(\\)", ECMAScript);
+    check_atomescape_identityescape("a", ECMAScript);
+    check_atomescape_identityescape("-", ECMAScript);
+    check_atomescape_identityescape(" ", ECMAScript);
+    check_atomescape_identityescape("(", ECMAScript);
+    check_atomescape_identityescape(")", ECMAScript);
+    check_atomescape_identityescape("[", ECMAScript);
+    check_atomescape_identityescape("]", ECMAScript);
+    check_atomescape_identityescape("{", ECMAScript);
+    check_atomescape_identityescape("}", ECMAScript);
+    check_atomescape_identityescape("*", ECMAScript);
+    check_atomescape_identityescape("?", ECMAScript);
+    check_atomescape_identityescape("+", ECMAScript);
+    check_atomescape_identityescape("^", ECMAScript);
+    check_atomescape_identityescape("$", ECMAScript);
+    check_atomescape_identityescape(".", ECMAScript);
+
+    {
+        string pattern = R"(\z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"(\Z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_classescape_ecmascript() {
+
+    // ClassEscape :: DecimalEscape
+    // only accepts literal NUL (\0), all other decimal escapes result in error
+    g_regexTester.should_match("\0"s, R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\0])", ECMAScript);
+    g_regexTester.should_throw(R"([\00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\01])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\10])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\01])", error_escape, ECMAScript);
+
+    // ClassEscape :: 'b'
+    check_classescape_controlescape("\b", "b", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: ControlEscape
+    check_classescape_controlescape("\f", "f", ECMAScript);
+    check_classescape_controlescape("\n", "n", ECMAScript);
+    check_classescape_controlescape("\r", "r", ECMAScript);
+    check_classescape_controlescape("\t", "t", ECMAScript);
+    check_classescape_controlescape("\v", "v", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"([\cA])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cZ])", ECMAScript);
+    g_regexTester.should_match("\x1", R"([\ca])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cz])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("c", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("ca", R"([\ca])", ECMAScript);
+    g_regexTester.should_throw(R"([\c0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\c])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"([\x00])", ECMAScript);
+    g_regexTester.should_match("\x00"s, R"([\x000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\x000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\x41])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\x41])", ECMAScript);
+    g_regexTester.should_match("\xff", R"([\xff])", ECMAScript);
+    g_regexTester.should_throw(R"([\x])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\x0])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"([\u0000])", ECMAScript);
+    g_regexTester.should_match("\u0000"s, R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\u0041])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\u0041])", ECMAScript);
+    g_regexTester.should_throw(R"([\uffff])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u2600])", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"([\u])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u000])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"([\d])", ECMAScript);
+    g_regexTester.should_match("5", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\D])", ECMAScript);
+    g_regexTester.should_not_match("5", R"([\D])", ECMAScript);
+    g_regexTester.should_match("a", R"([\D])", ECMAScript);
+    g_regexTester.should_match("_", R"([\D])", ECMAScript);
+    g_regexTester.should_match("-", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\D])", ECMAScript);
+
+    g_regexTester.should_match("a", R"([\w])", ECMAScript);
+    g_regexTester.should_match("A", R"([\w])", ECMAScript);
+    g_regexTester.should_match("0", R"([\w])", ECMAScript);
+    g_regexTester.should_match("_", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("A", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\W])", ECMAScript);
+    g_regexTester.should_match("-", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\W])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\W])", ECMAScript);
+
+    g_regexTester.should_match(" ", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\t", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\n", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\t", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\n", R"([\S])", ECMAScript);
+    g_regexTester.should_match("a", R"([\S])", ECMAScript);
+    g_regexTester.should_match("0", R"([\S])", ECMAScript);
+    g_regexTester.should_match("_", R"([\S])", ECMAScript);
+    g_regexTester.should_match("-", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\S])", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"([\\])", ECMAScript);
+    g_regexTester.should_not_match("b", R"([\\])", ECMAScript);
+    check_classescape_identityescape("a", ECMAScript);
+    check_classescape_identityescape("-", ECMAScript);
+    check_classescape_identityescape(" ", ECMAScript);
+    check_classescape_identityescape("(", ECMAScript);
+    check_classescape_identityescape(")", ECMAScript);
+    check_classescape_identityescape("[", ECMAScript);
+    check_classescape_identityescape("]", ECMAScript);
+    check_classescape_identityescape("{", ECMAScript);
+    check_classescape_identityescape("}", ECMAScript);
+    check_classescape_identityescape("*", ECMAScript);
+    check_classescape_identityescape("?", ECMAScript);
+    check_classescape_identityescape("+", ECMAScript);
+    check_classescape_identityescape("^", ECMAScript);
+    check_classescape_identityescape("$", ECMAScript);
+    check_classescape_identityescape(".", ECMAScript);
+    check_classescape_identityescape("B", ECMAScript);
+
+    {
+        string pattern = R"([\z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"([\Z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_atomescape_posix_common(syntax_option_type option) {
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // When special character is preceded by backslash, the special character matches itself
+    g_regexTester.should_match("\\", R"(\\)", option);
+    g_regexTester.should_not_match("b", R"(\\)", option);
+    g_regexTester.should_not_match("\\\\", R"(\\)", option);
+    check_atomescape_identityescape(".", option);
+    check_atomescape_identityescape("[", option);
+    check_atomescape_identityescape("*", option);
+    check_atomescape_identityescape("^", option);
+    check_atomescape_identityescape("$", option);
+
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // escaping ordinary characters is undefined -> reject
+    g_regexTester.should_throw(R"(\B)", error_escape, option);
+    g_regexTester.should_throw(R"(\c)", error_escape, option);
+    g_regexTester.should_throw(R"(\ca)", error_escape, option);
+    g_regexTester.should_throw(R"(\x000)", error_escape, option);
+    g_regexTester.should_throw(R"(\u0000)", error_escape, option);
+    g_regexTester.should_throw(R"(\d)", error_escape, option);
+    g_regexTester.should_throw(R"(\D)", error_escape, option);
+    g_regexTester.should_throw(R"(\w)", error_escape, option);
+    g_regexTester.should_throw(R"(\W)", error_escape, option);
+    g_regexTester.should_throw(R"(\s)", error_escape, option);
+    g_regexTester.should_throw(R"(\S)", error_escape, option);
+
+    // while [ is special, ] is not
+    g_regexTester.should_throw(R"(\])", error_escape, option);
+}
+
+void test_gh_5244_atomescape_posix_not_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // reject awk-only escapes
+    g_regexTester.should_throw(R"(\a)", error_escape, option);
+    g_regexTester.should_throw(R"(\b)", error_escape, option);
+    g_regexTester.should_throw(R"(\f)", error_escape, option);
+    g_regexTester.should_throw(R"(\n)", error_escape, option);
+    g_regexTester.should_throw(R"(\t)", error_escape, option);
+    g_regexTester.should_throw(R"(\r)", error_escape, option);
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: Ultra nitpick: These aren't in sorted order, but later the positive tests for awk are. Let's follow sorted order in both.
created_at: 2025-04-09T10:54:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\u0000""0"s, R"(\u00000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
+
+    g_regexTester.should_match("a", R"(\w)", ECMAScript);
+    g_regexTester.should_match("A", R"(\w)", ECMAScript);
+    g_regexTester.should_match("0", R"(\w)", ECMAScript);
+    g_regexTester.should_match("_", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("A", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\W)", ECMAScript);
+    g_regexTester.should_match("-", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\W)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\W)", ECMAScript);
+
+    g_regexTester.should_match(" ", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\t", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\n", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\t", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\n", R"(\S)", ECMAScript);
+    g_regexTester.should_match("a", R"(\S)", ECMAScript);
+    g_regexTester.should_match("0", R"(\S)", ECMAScript);
+    g_regexTester.should_match("_", R"(\S)", ECMAScript);
+    g_regexTester.should_match("-", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\S)", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("g", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("\\\\", R"(\\)", ECMAScript);
+    check_atomescape_identityescape("a", ECMAScript);
+    check_atomescape_identityescape("-", ECMAScript);
+    check_atomescape_identityescape(" ", ECMAScript);
+    check_atomescape_identityescape("(", ECMAScript);
+    check_atomescape_identityescape(")", ECMAScript);
+    check_atomescape_identityescape("[", ECMAScript);
+    check_atomescape_identityescape("]", ECMAScript);
+    check_atomescape_identityescape("{", ECMAScript);
+    check_atomescape_identityescape("}", ECMAScript);
+    check_atomescape_identityescape("*", ECMAScript);
+    check_atomescape_identityescape("?", ECMAScript);
+    check_atomescape_identityescape("+", ECMAScript);
+    check_atomescape_identityescape("^", ECMAScript);
+    check_atomescape_identityescape("$", ECMAScript);
+    check_atomescape_identityescape(".", ECMAScript);
+
+    {
+        string pattern = R"(\z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"(\Z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_classescape_ecmascript() {
+
+    // ClassEscape :: DecimalEscape
+    // only accepts literal NUL (\0), all other decimal escapes result in error
+    g_regexTester.should_match("\0"s, R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\0])", ECMAScript);
+    g_regexTester.should_throw(R"([\00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\01])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\10])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\01])", error_escape, ECMAScript);
+
+    // ClassEscape :: 'b'
+    check_classescape_controlescape("\b", "b", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: ControlEscape
+    check_classescape_controlescape("\f", "f", ECMAScript);
+    check_classescape_controlescape("\n", "n", ECMAScript);
+    check_classescape_controlescape("\r", "r", ECMAScript);
+    check_classescape_controlescape("\t", "t", ECMAScript);
+    check_classescape_controlescape("\v", "v", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"([\cA])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cZ])", ECMAScript);
+    g_regexTester.should_match("\x1", R"([\ca])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cz])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("c", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("ca", R"([\ca])", ECMAScript);
+    g_regexTester.should_throw(R"([\c0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\c])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"([\x00])", ECMAScript);
+    g_regexTester.should_match("\x00"s, R"([\x000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\x000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\x41])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\x41])", ECMAScript);
+    g_regexTester.should_match("\xff", R"([\xff])", ECMAScript);
+    g_regexTester.should_throw(R"([\x])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\x0])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"([\u0000])", ECMAScript);
+    g_regexTester.should_match("\u0000"s, R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\u0041])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\u0041])", ECMAScript);
+    g_regexTester.should_throw(R"([\uffff])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u2600])", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"([\u])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u000])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"([\d])", ECMAScript);
+    g_regexTester.should_match("5", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\D])", ECMAScript);
+    g_regexTester.should_not_match("5", R"([\D])", ECMAScript);
+    g_regexTester.should_match("a", R"([\D])", ECMAScript);
+    g_regexTester.should_match("_", R"([\D])", ECMAScript);
+    g_regexTester.should_match("-", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\D])", ECMAScript);
+
+    g_regexTester.should_match("a", R"([\w])", ECMAScript);
+    g_regexTester.should_match("A", R"([\w])", ECMAScript);
+    g_regexTester.should_match("0", R"([\w])", ECMAScript);
+    g_regexTester.should_match("_", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("A", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\W])", ECMAScript);
+    g_regexTester.should_match("-", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\W])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\W])", ECMAScript);
+
+    g_regexTester.should_match(" ", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\t", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\n", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\t", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\n", R"([\S])", ECMAScript);
+    g_regexTester.should_match("a", R"([\S])", ECMAScript);
+    g_regexTester.should_match("0", R"([\S])", ECMAScript);
+    g_regexTester.should_match("_", R"([\S])", ECMAScript);
+    g_regexTester.should_match("-", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\S])", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"([\\])", ECMAScript);
+    g_regexTester.should_not_match("b", R"([\\])", ECMAScript);
+    check_classescape_identityescape("a", ECMAScript);
+    check_classescape_identityescape("-", ECMAScript);
+    check_classescape_identityescape(" ", ECMAScript);
+    check_classescape_identityescape("(", ECMAScript);
+    check_classescape_identityescape(")", ECMAScript);
+    check_classescape_identityescape("[", ECMAScript);
+    check_classescape_identityescape("]", ECMAScript);
+    check_classescape_identityescape("{", ECMAScript);
+    check_classescape_identityescape("}", ECMAScript);
+    check_classescape_identityescape("*", ECMAScript);
+    check_classescape_identityescape("?", ECMAScript);
+    check_classescape_identityescape("+", ECMAScript);
+    check_classescape_identityescape("^", ECMAScript);
+    check_classescape_identityescape("$", ECMAScript);
+    check_classescape_identityescape(".", ECMAScript);
+    check_classescape_identityescape("B", ECMAScript);
+
+    {
+        string pattern = R"([\z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"([\Z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_atomescape_posix_common(syntax_option_type option) {
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // When special character is preceded by backslash, the special character matches itself
+    g_regexTester.should_match("\\", R"(\\)", option);
+    g_regexTester.should_not_match("b", R"(\\)", option);
+    g_regexTester.should_not_match("\\\\", R"(\\)", option);
+    check_atomescape_identityescape(".", option);
+    check_atomescape_identityescape("[", option);
+    check_atomescape_identityescape("*", option);
+    check_atomescape_identityescape("^", option);
+    check_atomescape_identityescape("$", option);
+
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // escaping ordinary characters is undefined -> reject
+    g_regexTester.should_throw(R"(\B)", error_escape, option);
+    g_regexTester.should_throw(R"(\c)", error_escape, option);
+    g_regexTester.should_throw(R"(\ca)", error_escape, option);
+    g_regexTester.should_throw(R"(\x000)", error_escape, option);
+    g_regexTester.should_throw(R"(\u0000)", error_escape, option);
+    g_regexTester.should_throw(R"(\d)", error_escape, option);
+    g_regexTester.should_throw(R"(\D)", error_escape, option);
+    g_regexTester.should_throw(R"(\w)", error_escape, option);
+    g_regexTester.should_throw(R"(\W)", error_escape, option);
+    g_regexTester.should_throw(R"(\s)", error_escape, option);
+    g_regexTester.should_throw(R"(\S)", error_escape, option);
+
+    // while [ is special, ] is not
+    g_regexTester.should_throw(R"(\])", error_escape, option);
+}
+
+void test_gh_5244_atomescape_posix_not_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // reject awk-only escapes
+    g_regexTester.should_throw(R"(\a)", error_escape, option);
+    g_regexTester.should_throw(R"(\b)", error_escape, option);
+    g_regexTester.should_throw(R"(\f)", error_escape, option);
+    g_regexTester.should_throw(R"(\n)", error_escape, option);
+    g_regexTester.should_throw(R"(\t)", error_escape, option);
+    g_regexTester.should_throw(R"(\r)", error_escape, option);
+    g_regexTester.should_throw(R"(\v)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // Section on "BREs Matching Multiple Characters":
+    // \ plus digit is backreference to previously completed subexpression
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_backref, option);
+    g_regexTester.should_match("aa", R"(\(a\)\1)", option);
+    g_regexTester.should_throw(R"(\(a\)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1\(a\))", error_backref, option);
+
+    // check that the parser rejects escaped characters
+    // that are only special in extended regexes or awk
+    g_regexTester.should_throw(R"(\+)", error_escape, option);
+    g_regexTester.should_throw(R"(\?)", error_escape, option);
+    g_regexTester.should_throw(R"(\|)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_extended_egrep_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // check that the parser accepts escaped characters
+    // that are only special in extended regexes
+    check_atomescape_identityescape("+", option);
+    check_atomescape_identityescape("?", option);
+    check_atomescape_identityescape("|", option);
+    check_atomescape_identityescape("(", option);
+    check_atomescape_identityescape(")", option);
+    check_atomescape_identityescape("{", option);
+
+    // Even though { is special, } is not,
+    // so the interpretation of the escape sequence \} is undefined
+    // according to the POSIX standard referenced in the C++ standard.
+    // But we treat \} as an identity escape in line with
+    // more recent versions of the POSIX standard.
+    check_atomescape_identityescape("}", option);
+}
+
+void test_gh_5244_atomescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_atomescape_extended_egrep_awk(option);
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // there are no backreferences in extended regexes
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1(a))", error_escape, option);
+}
+
+void test_gh_5244_atomescape_awk() {
+    test_gh_5244_atomescape_extended_egrep_awk(awk);
+
+    // awk-only escapes
+    check_atomescape_controlescape("\a", "a", awk);
+    check_atomescape_controlescape("\b", "b", awk);
+    check_atomescape_controlescape("\f", "f", awk);
+    check_atomescape_controlescape("\n", "n", awk);
+    check_atomescape_controlescape("\r", "r", awk);
+    check_atomescape_controlescape("\t", "t", awk);
+    check_atomescape_controlescape("\v", "v", awk);
+    check_atomescape_identityescape("\"", awk);
+    check_atomescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_not_match("1", R"(\1)", awk);
+    g_regexTester.should_not_match("\\1", R"(\1)", awk);
+    g_regexTester.should_match("\11", R"(\11)", awk);
+    g_regexTester.should_not_match("1", R"(\11)", awk);
+    g_regexTester.should_not_match("11", R"(\11)", awk);
+    g_regexTester.should_not_match("\\11", R"(\11)", awk);
+    g_regexTester.should_match("\111", R"(\111)", awk);
+    g_regexTester.should_not_match("1", R"(\111)", awk);
+    g_regexTester.should_not_match("11", R"(\111)", awk);
+    g_regexTester.should_not_match("111", R"(\111)", awk);
+    g_regexTester.should_not_match("\\111", R"(\111)", awk);
+    // clang-format off
+    g_regexTester.should_match("\111""1", R"(\1111)", awk);
+    // clang-format on
+    g_regexTester.should_not_match("\111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1", R"(\1111)", awk);
+    g_regexTester.should_not_match("11", R"(\1111)", awk);
+    g_regexTester.should_not_match("111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1111", R"(\1111)", awk);
+    g_regexTester.should_not_match("\\1111", R"(\1111)", awk);
+    g_regexTester.should_match("A", R"(\101)", awk);
+    g_regexTester.should_match("Aa", R"(\101a)", awk);
+    g_regexTester.should_match("\33", R"(\033)", awk);
+    g_regexTester.should_match("\33a", R"(\033a)", awk);
+    g_regexTester.should_match("\33", R"(\33)", awk);
+    g_regexTester.should_match("\33a", R"(\33a)", awk);
+    g_regexTester.should_match("\1", R"(\001)", awk);
+    g_regexTester.should_match("\1a", R"(\001a)", awk);
+    g_regexTester.should_match("\1", R"(\01)", awk);
+    g_regexTester.should_match("\1a", R"(\01a)", awk);
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_match("\1a", R"(\1a)", awk);
+    g_regexTester.should_throw(R"(\8)", error_escape, awk);
+    // clang-format off
+    g_regexTester.should_match("\1""8", R"(\18)", awk);
+    g_regexTester.should_match("\12""9", R"(\129)", awk);
+    // clang-format on
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"(\0)", error_escape, awk);
+    g_regexTester.should_throw(R"(\00)", error_escape, awk);
+    g_regexTester.should_throw(R"(\000)", error_escape, awk);
+}
+
+void test_gh_5244_classescape_posix_not_awk(syntax_option_type option) {
+    // Sections "BRE Bracket Expressions" and "ERE Bracket Expressions":
+    // Backslash shall lose its special meaning, so \c should always match \ + c
+
+    // common special characters outside character classes
+    check_classescape_noescape(".", option);
+    check_classescape_noescape("[", option);
+    check_classescape_noescape("*", option);
+    check_classescape_noescape("^", option);
+    check_classescape_noescape("$", option);
+    check_classescape_noescape("B", option);
+    check_classescape_noescape("c", option);
+
+    // special characters outside character classes in extended regexes
+    check_classescape_noescape("+", option);
+    check_classescape_noescape("?", option);
+    check_classescape_noescape("|", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep | awk)) {
+        check_classescape_noescape("(", option);
+        check_classescape_noescape(")", option);
+        check_classescape_noescape("{", option);
+    }
+
+    // closing characters that are not considered special
+    g_regexTester.should_match("\\]", R"([\]])", option);
+    g_regexTester.should_not_match("]", R"([\]])", option);
+    g_regexTester.should_not_match("\\", R"([\]])", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep | awk)) {
+        check_classescape_noescape("}", option);
+    }
+
+    // awk escape sequences
+    check_classescape_noescape("a", option);
+    check_classescape_noescape("b", option);
+    check_classescape_noescape("f", option);
+    check_classescape_noescape("n", option);
+    check_classescape_noescape("r", option);
+    check_classescape_noescape("t", option);
+    check_classescape_noescape("v", option);
+    check_classescape_noescape("\"", option);
+    check_classescape_noescape("/", option);
+
+    // awk octal sequences
+    check_classescape_noescape("0", option);
+    check_classescape_noescape("1", option);
+    g_regexTester.should_match("1", R"([\101])", option);
+    g_regexTester.should_match("0", R"([\101])", option);
+    g_regexTester.should_match("\\", R"([\101])", option);
+    g_regexTester.should_match("1", R"([\101a])", option);
+    g_regexTester.should_match("0", R"([\101a])", option);
+    g_regexTester.should_match("\\", R"([\101a])", option);
+    g_regexTester.should_match("a", R"([\101a])", option);
+    g_regexTester.should_match("3", R"([\033a])", option);
+    g_regexTester.should_match("0", R"([\033a])", option);
+    g_regexTester.should_match("\\", R"([\033a])", option);
+    g_regexTester.should_match("a", R"([\033a])", option);
+    g_regexTester.should_match("3", R"([\33a])", option);
+    g_regexTester.should_match("\\", R"([\33a])", option);
+    g_regexTester.should_match("a", R"([\33a])", option);
+    g_regexTester.should_match("1", R"([\001a])", option);
+    g_regexTester.should_match("0", R"([\001a])", option);
+    g_regexTester.should_match("\\", R"([\001a])", option);
+    g_regexTester.should_match("a", R"([\001a])", option);
+
+    // ECMAScript escape sequences
+    g_regexTester.should_match("c", R"([\ca])", option);
+    g_regexTester.should_match("a", R"([\ca])", option);
+    g_regexTester.should_match("\\", R"([\ca])", option);
+    g_regexTester.should_match("x", R"([\x000])", option);
+    g_regexTester.should_match("0", R"([\x000])", option);
+    g_regexTester.should_match("\\", R"([\x000])", option);
+    g_regexTester.should_match("u", R"([\u0000])", option);
+    g_regexTester.should_match("0", R"([\u0000])", option);
+    g_regexTester.should_match("\\", R"([\u0000])", option);
+    check_classescape_noescape("d", option);
+    check_classescape_noescape("D", option);
+    check_classescape_noescape("w", option);
+    check_classescape_noescape("W", option);
+    check_classescape_noescape("s", option);
+    check_classescape_noescape("S", option);
+}
+
+void test_gh_5244_classescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\1])", option);
+    g_regexTester.should_not_match("aa", R"(\(a\)[\1])", option);
+    g_regexTester.should_match("a0", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("a\\", R"(\(a\)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1]\(a\))", option);
+    g_regexTester.should_match("\\a", R"([\1]\(a\))", option);
+    g_regexTester.should_not_match("aa", R"([\1]\(a\))", option);
+}
+
+void test_gh_5244_classescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_classescape_posix_not_awk(option);
+
+    // check no backreference handling
+    g_regexTester.should_match("a1", R"((a)[\1])", option);
+    g_regexTester.should_match("a\\", R"((a)[\1])", option);
+    g_regexTester.should_not_match("aa", R"((a)[\1])", option);
+    g_regexTester.should_match("a0", R"((a)[\0])", option);
+    g_regexTester.should_match("a\\", R"((a)[\0])", option);
+    g_regexTester.should_match("1a", R"([\1](a))", option);
+    g_regexTester.should_match("\\a", R"([\1](a))", option);
+    g_regexTester.should_not_match("aa", R"([\1](a))", option);
+}
+
+void test_gh_5244_classescape_awk() {
+    // awk-only sequences and backslash
+    g_regexTester.should_match("\\", "[\\\\]", awk);
+    g_regexTester.should_not_match("g", "[\\\\]", awk);
+    check_classescape_controlescape("\a", "a", awk);
+    check_classescape_controlescape("\b", "b", awk);
+    check_classescape_controlescape("\f", "f", awk);
+    check_classescape_controlescape("\n", "n", awk);
+    check_classescape_controlescape("\r", "r", awk);
+    check_classescape_controlescape("\t", "t", awk);
+    check_classescape_controlescape("\v", "v", awk);
+    check_classescape_identityescape("\"", awk);
+    check_classescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"([\1])", awk);
+    g_regexTester.should_not_match("1", R"([\1])", awk);
+    g_regexTester.should_not_match("\\", R"([\1])", awk);
+    g_regexTester.should_match("\11", R"([\11])", awk);
+    g_regexTester.should_not_match("1", R"([\11])", awk);
+    g_regexTester.should_not_match("\\", R"([\11])", awk);
+    g_regexTester.should_match("\111", R"([\111])", awk);
+    g_regexTester.should_not_match("0", R"([\111])", awk);
+    g_regexTester.should_not_match("\\", R"([\111])", awk);
+    g_regexTester.should_match("\111", R"([\1111])", awk);
+    g_regexTester.should_match("1", R"([\1111])", awk);
+    g_regexTester.should_not_match("\\", R"([\1111])", awk);
+    g_regexTester.should_match("A", R"([\101])", awk);
+    g_regexTester.should_not_match("\\", R"([\101])", awk);
+    g_regexTester.should_not_match("1", R"([\101])", awk);
+    g_regexTester.should_not_match("0", R"([\101])", awk);
+    g_regexTester.should_not_match("\1", R"([\101])", awk);
+    g_regexTester.should_not_match("\0", R"([\101])", awk);
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: Plain `"\0"` will result in an empty `std::string` being constructed for the function call. We need `"\0"s`.
created_at: 2025-04-09T11:06:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,882 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <regex>
+#include <string>
+
+#include <test_regex_support.hpp>
+
+using namespace std;
+using namespace std::regex_constants;
+
+regex_fixture g_regexTester;
+
+template <class charT>
+class test_regex_traits {
+private:
+    using rx_traits = regex_traits<charT>;
+
+public:
+    using char_type       = typename rx_traits::char_type;
+    using string_type     = typename rx_traits::string_type;
+    using locale_type     = typename rx_traits::locale_type;
+    using char_class_type = typename rx_traits::char_class_type;
+
+    // TRANSITION, GH-995
+    using _Uelem                    = typename rx_traits::_Uelem;
+    static constexpr auto _Ch_upper = rx_traits::_Ch_upper;
+    static constexpr auto _Ch_alpha = rx_traits::_Ch_alpha;
+
+    test_regex_traits() = default;
+
+    static size_t length(const charT* p) {
+        return rx_traits::length(p);
+    }
+
+    charT translate(const charT c) const {
+        return inner.translate(c);
+    }
+
+    charT translate_nocase(const charT c) const {
+        return inner.translate_nocase(c);
+    }
+
+    template <class FwdIt>
+    string_type transform(FwdIt first, FwdIt last) const {
+        return inner.transform(first, last);
+    }
+
+    template <class FwdIt>
+    string_type transform_primary(FwdIt first, FwdIt last) const {
+        return inner.transform_primary(first, last);
+    }
+
+    template <class FwdIt>
+    char_class_type lookup_classname(FwdIt first, FwdIt last, bool icase = false) const {
+        FwdIt next = first;
+        ++next;
+        if (next == last && (*first == 'z' || *first == 'Z')) {
+            const charT space_class = 's';
+            return inner.lookup_classname(&space_class, &space_class + 1, icase);
+        }
+        return inner.lookup_classname(first, last, icase);
+    }
+
+    bool isctype(charT c, char_class_type f) const {
+        return inner.isctype(c, f);
+    }
+
+    int value(charT ch, int radix) const {
+        return inner.value(ch, radix);
+    }
+
+    locale_type imbue(locale_type l) {
+        return inner.imbue(l);
+    }
+
+    locale_type getloc() const {
+        return inner.getloc();
+    }
+
+private:
+    regex_traits<charT> inner;
+};
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags, bool matches) {
+    if (regex_match(subject, re, flags) != matches) {
+        printf(R"(Expected regex_match("%s", regex("%s", 0x%X)) to be %s.)", subject.c_str(), pattern.c_str(),
+            re.flags(), matches ? "true" : "false");
+        g_regexTester.fail_regex();
+    }
+}
+
+template <class Rx>
+void check_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, true);
+}
+
+template <class Rx>
+void check_no_match(const string& subject, const string& pattern, const Rx& re, match_flag_type flags = match_default) {
+    check_match(subject, pattern, re, flags, false);
+}
+
+void check_atomescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_controlescape(string expected, string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(expected, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match(c, pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_atomescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "\\" + c;
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+    g_regexTester.should_not_match(pattern, pattern, option);
+}
+
+void check_classescape_identityescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\", pattern, option);
+}
+
+void check_classescape_noescape(string c, syntax_option_type option) {
+    string pattern = "[\\" + c + "]";
+    g_regexTester.should_match(c, pattern, option);
+    g_regexTester.should_match("\\", pattern, option);
+    g_regexTester.should_not_match("g", pattern, option);
+    g_regexTester.should_not_match("\\" + c, pattern, option);
+}
+
+void test_gh_5244_atomescape_ecmascript() {
+
+    // AtomEscape :: DecimalEscape
+    // ECMAScript standard says:
+    // \ followed by decimal whose first digit is not zero is a backreference.
+    // It is an error if a backreference does not refer to a capture group.
+    // \0 refers to literal NUL and must not be followed by another digit.
+
+    // literal NUL tests
+    g_regexTester.should_match("\0"s, R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("", R"(\0)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\0)", ECMAScript);
+    g_regexTester.should_throw(R"(\00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\01)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)\01)", error_escape, ECMAScript);
+
+    // backreference tests
+    g_regexTester.should_throw(R"(\1)", error_backref, ECMAScript);
+    g_regexTester.should_throw(R"((a)\10)", error_backref, ECMAScript);
+    g_regexTester.should_match("aa", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa\\", R"((a)\1)", ECMAScript);
+    g_regexTester.should_not_match("aa1", R"((a)\1)", ECMAScript);
+    g_regexTester.should_match("aaaaaaaaabb", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(b)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+    g_regexTester.should_not_match("aaaaaaaaaba0", R"((.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\10)");
+
+    // AtomEscape :: CharacterEscape :: ControlEscape
+    check_atomescape_controlescape("\f", "f", ECMAScript);
+    check_atomescape_controlescape("\n", "n", ECMAScript);
+    check_atomescape_controlescape("\r", "r", ECMAScript);
+    check_atomescape_controlescape("\t", "t", ECMAScript);
+    check_atomescape_controlescape("\v", "v", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"(\cA)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cZ)", ECMAScript);
+    g_regexTester.should_match("\x1", R"(\ca)", ECMAScript);
+    g_regexTester.should_match("\x1a", R"(\cz)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("c", R"(\ca)", ECMAScript);
+    g_regexTester.should_not_match("ca", R"(\ca)", ECMAScript);
+    g_regexTester.should_throw(R"(\c0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\c)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"(\x00)", ECMAScript);
+    g_regexTester.should_not_match("\\x00", R"(\x00)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\x00""0"s, R"(\x000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\x41)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\x41)", ECMAScript);
+    g_regexTester.should_match("\xff", R"(\xff)", ECMAScript);
+    g_regexTester.should_throw(R"(\x)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\x0)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"(\u0000)", ECMAScript);
+    g_regexTester.should_not_match("\\u0000", R"(\u0000)", ECMAScript);
+    // clang-format off
+    g_regexTester.should_match("\u0000""0"s, R"(\u00000)", ECMAScript);
+    // clang-format on
+    g_regexTester.should_match("A", R"(\u0041)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\u0041)", ECMAScript);
+    g_regexTester.should_throw(R"(\uffff)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u2600)", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"(\u)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u0)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u00)", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"(\u000)", error_escape, ECMAScript);
+
+    // AtomEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"(\d)", ECMAScript);
+    g_regexTester.should_match("5", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\d)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\D)", ECMAScript);
+    g_regexTester.should_not_match("5", R"(\D)", ECMAScript);
+    g_regexTester.should_match("a", R"(\D)", ECMAScript);
+    g_regexTester.should_match("_", R"(\D)", ECMAScript);
+    g_regexTester.should_match("-", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\D)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\D)", ECMAScript);
+
+    g_regexTester.should_match("a", R"(\w)", ECMAScript);
+    g_regexTester.should_match("A", R"(\w)", ECMAScript);
+    g_regexTester.should_match("0", R"(\w)", ECMAScript);
+    g_regexTester.should_match("_", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\w)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("A", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\W)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\W)", ECMAScript);
+    g_regexTester.should_match("-", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\W)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\W)", ECMAScript);
+    g_regexTester.should_match(" ", R"(\W)", ECMAScript);
+
+    g_regexTester.should_match(" ", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\t", R"(\s)", ECMAScript);
+    g_regexTester.should_match("\n", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("a", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("0", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("_", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("-", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\1", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match("\\", R"(\s)", ECMAScript);
+    g_regexTester.should_not_match(" ", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\t", R"(\S)", ECMAScript);
+    g_regexTester.should_not_match("\n", R"(\S)", ECMAScript);
+    g_regexTester.should_match("a", R"(\S)", ECMAScript);
+    g_regexTester.should_match("0", R"(\S)", ECMAScript);
+    g_regexTester.should_match("_", R"(\S)", ECMAScript);
+    g_regexTester.should_match("-", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\1", R"(\S)", ECMAScript);
+    g_regexTester.should_match("\\", R"(\S)", ECMAScript);
+
+    // AtomEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("g", R"(\\)", ECMAScript);
+    g_regexTester.should_not_match("\\\\", R"(\\)", ECMAScript);
+    check_atomescape_identityescape("a", ECMAScript);
+    check_atomescape_identityescape("-", ECMAScript);
+    check_atomescape_identityescape(" ", ECMAScript);
+    check_atomescape_identityescape("(", ECMAScript);
+    check_atomescape_identityescape(")", ECMAScript);
+    check_atomescape_identityescape("[", ECMAScript);
+    check_atomescape_identityescape("]", ECMAScript);
+    check_atomescape_identityescape("{", ECMAScript);
+    check_atomescape_identityescape("}", ECMAScript);
+    check_atomescape_identityescape("*", ECMAScript);
+    check_atomescape_identityescape("?", ECMAScript);
+    check_atomescape_identityescape("+", ECMAScript);
+    check_atomescape_identityescape("^", ECMAScript);
+    check_atomescape_identityescape("$", ECMAScript);
+    check_atomescape_identityescape(".", ECMAScript);
+
+    {
+        string pattern = R"(\z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"(\Z)";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_classescape_ecmascript() {
+
+    // ClassEscape :: DecimalEscape
+    // only accepts literal NUL (\0), all other decimal escapes result in error
+    g_regexTester.should_match("\0"s, R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("", R"([\0])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\0])", ECMAScript);
+    g_regexTester.should_throw(R"([\00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\01])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\10])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\1])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"((a)[\01])", error_escape, ECMAScript);
+
+    // ClassEscape :: 'b'
+    check_classescape_controlescape("\b", "b", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: ControlEscape
+    check_classescape_controlescape("\f", "f", ECMAScript);
+    check_classescape_controlescape("\n", "n", ECMAScript);
+    check_classescape_controlescape("\r", "r", ECMAScript);
+    check_classescape_controlescape("\t", "t", ECMAScript);
+    check_classescape_controlescape("\v", "v", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: 'c' ControlLetter
+    g_regexTester.should_match("\x1", R"([\cA])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cZ])", ECMAScript);
+    g_regexTester.should_match("\x1", R"([\ca])", ECMAScript);
+    g_regexTester.should_match("\x1a", R"([\cz])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("c", R"([\ca])", ECMAScript);
+    g_regexTester.should_not_match("ca", R"([\ca])", ECMAScript);
+    g_regexTester.should_throw(R"([\c0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\c])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: HexEscapeSequence
+    g_regexTester.should_match("\x00"s, R"([\x00])", ECMAScript);
+    g_regexTester.should_match("\x00"s, R"([\x000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\x000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\x41])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\x41])", ECMAScript);
+    g_regexTester.should_match("\xff", R"([\xff])", ECMAScript);
+    g_regexTester.should_throw(R"([\x])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\x0])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: UnicodeEscapeSequence
+    g_regexTester.should_match("\u0000"s, R"([\u0000])", ECMAScript);
+    g_regexTester.should_match("\u0000"s, R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("0", R"([\u00000])", ECMAScript);
+    g_regexTester.should_match("A", R"([\u0041])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\u0041])", ECMAScript);
+    g_regexTester.should_throw(R"([\uffff])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u2600])", error_escape, ECMAScript); // U+2600 BLACK SUN WITH RAYS
+    g_regexTester.should_throw(R"([\u])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u0])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u00])", error_escape, ECMAScript);
+    g_regexTester.should_throw(R"([\u000])", error_escape, ECMAScript);
+
+    // ClassEscape :: CharacterClassEscape
+    g_regexTester.should_match("0", R"([\d])", ECMAScript);
+    g_regexTester.should_match("5", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\d])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\D])", ECMAScript);
+    g_regexTester.should_not_match("5", R"([\D])", ECMAScript);
+    g_regexTester.should_match("a", R"([\D])", ECMAScript);
+    g_regexTester.should_match("_", R"([\D])", ECMAScript);
+    g_regexTester.should_match("-", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\D])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\D])", ECMAScript);
+
+    g_regexTester.should_match("a", R"([\w])", ECMAScript);
+    g_regexTester.should_match("A", R"([\w])", ECMAScript);
+    g_regexTester.should_match("0", R"([\w])", ECMAScript);
+    g_regexTester.should_match("_", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\w])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("A", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\W])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\W])", ECMAScript);
+    g_regexTester.should_match("-", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\W])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\W])", ECMAScript);
+    g_regexTester.should_match(" ", R"([\W])", ECMAScript);
+
+    g_regexTester.should_match(" ", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\t", R"([\s])", ECMAScript);
+    g_regexTester.should_match("\n", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("a", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("0", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("_", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("-", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\1", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match("\\", R"([\s])", ECMAScript);
+    g_regexTester.should_not_match(" ", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\t", R"([\S])", ECMAScript);
+    g_regexTester.should_not_match("\n", R"([\S])", ECMAScript);
+    g_regexTester.should_match("a", R"([\S])", ECMAScript);
+    g_regexTester.should_match("0", R"([\S])", ECMAScript);
+    g_regexTester.should_match("_", R"([\S])", ECMAScript);
+    g_regexTester.should_match("-", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\1", R"([\S])", ECMAScript);
+    g_regexTester.should_match("\\", R"([\S])", ECMAScript);
+
+    // ClassEscape :: CharacterEscape :: IdentityEscape
+    g_regexTester.should_match("\\", R"([\\])", ECMAScript);
+    g_regexTester.should_not_match("b", R"([\\])", ECMAScript);
+    check_classescape_identityescape("a", ECMAScript);
+    check_classescape_identityescape("-", ECMAScript);
+    check_classescape_identityescape(" ", ECMAScript);
+    check_classescape_identityescape("(", ECMAScript);
+    check_classescape_identityescape(")", ECMAScript);
+    check_classescape_identityescape("[", ECMAScript);
+    check_classescape_identityescape("]", ECMAScript);
+    check_classescape_identityescape("{", ECMAScript);
+    check_classescape_identityescape("}", ECMAScript);
+    check_classescape_identityescape("*", ECMAScript);
+    check_classescape_identityescape("?", ECMAScript);
+    check_classescape_identityescape("+", ECMAScript);
+    check_classescape_identityescape("^", ECMAScript);
+    check_classescape_identityescape("$", ECMAScript);
+    check_classescape_identityescape(".", ECMAScript);
+    check_classescape_identityescape("B", ECMAScript);
+
+    {
+        string pattern = R"([\z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+    {
+        string pattern = R"([\Z])";
+        basic_regex<char, test_regex_traits<char>> custom_charclass_regex{pattern, ECMAScript};
+        check_match("Z", pattern, custom_charclass_regex);
+        check_no_match("\\", pattern, custom_charclass_regex);
+        check_no_match("A", pattern, custom_charclass_regex);
+        check_no_match(" ", pattern, custom_charclass_regex);
+    }
+}
+
+void test_gh_5244_atomescape_posix_common(syntax_option_type option) {
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // When special character is preceded by backslash, the special character matches itself
+    g_regexTester.should_match("\\", R"(\\)", option);
+    g_regexTester.should_not_match("b", R"(\\)", option);
+    g_regexTester.should_not_match("\\\\", R"(\\)", option);
+    check_atomescape_identityescape(".", option);
+    check_atomescape_identityescape("[", option);
+    check_atomescape_identityescape("*", option);
+    check_atomescape_identityescape("^", option);
+    check_atomescape_identityescape("$", option);
+
+    // Sections on "BRE Special Characters" and "ERE Special Characters":
+    // escaping ordinary characters is undefined -> reject
+    g_regexTester.should_throw(R"(\B)", error_escape, option);
+    g_regexTester.should_throw(R"(\c)", error_escape, option);
+    g_regexTester.should_throw(R"(\ca)", error_escape, option);
+    g_regexTester.should_throw(R"(\x000)", error_escape, option);
+    g_regexTester.should_throw(R"(\u0000)", error_escape, option);
+    g_regexTester.should_throw(R"(\d)", error_escape, option);
+    g_regexTester.should_throw(R"(\D)", error_escape, option);
+    g_regexTester.should_throw(R"(\w)", error_escape, option);
+    g_regexTester.should_throw(R"(\W)", error_escape, option);
+    g_regexTester.should_throw(R"(\s)", error_escape, option);
+    g_regexTester.should_throw(R"(\S)", error_escape, option);
+
+    // while [ is special, ] is not
+    g_regexTester.should_throw(R"(\])", error_escape, option);
+}
+
+void test_gh_5244_atomescape_posix_not_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // reject awk-only escapes
+    g_regexTester.should_throw(R"(\a)", error_escape, option);
+    g_regexTester.should_throw(R"(\b)", error_escape, option);
+    g_regexTester.should_throw(R"(\f)", error_escape, option);
+    g_regexTester.should_throw(R"(\n)", error_escape, option);
+    g_regexTester.should_throw(R"(\t)", error_escape, option);
+    g_regexTester.should_throw(R"(\r)", error_escape, option);
+    g_regexTester.should_throw(R"(\v)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_basic_or_grep(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // Section on "BREs Matching Multiple Characters":
+    // \ plus digit is backreference to previously completed subexpression
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_backref, option);
+    g_regexTester.should_match("aa", R"(\(a\)\1)", option);
+    g_regexTester.should_throw(R"(\(a\)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1\(a\))", error_backref, option);
+
+    // check that the parser rejects escaped characters
+    // that are only special in extended regexes or awk
+    g_regexTester.should_throw(R"(\+)", error_escape, option);
+    g_regexTester.should_throw(R"(\?)", error_escape, option);
+    g_regexTester.should_throw(R"(\|)", error_escape, option);
+    g_regexTester.should_throw(R"(\")", error_escape, option);
+    g_regexTester.should_throw(R"(\/)", error_escape, option);
+}
+
+void test_gh_5244_atomescape_extended_egrep_awk(syntax_option_type option) {
+    test_gh_5244_atomescape_posix_common(option);
+
+    // check that the parser accepts escaped characters
+    // that are only special in extended regexes
+    check_atomescape_identityescape("+", option);
+    check_atomescape_identityescape("?", option);
+    check_atomescape_identityescape("|", option);
+    check_atomescape_identityescape("(", option);
+    check_atomescape_identityescape(")", option);
+    check_atomescape_identityescape("{", option);
+
+    // Even though { is special, } is not,
+    // so the interpretation of the escape sequence \} is undefined
+    // according to the POSIX standard referenced in the C++ standard.
+    // But we treat \} as an identity escape in line with
+    // more recent versions of the POSIX standard.
+    check_atomescape_identityescape("}", option);
+}
+
+void test_gh_5244_atomescape_extended_or_egrep(syntax_option_type option) {
+    test_gh_5244_atomescape_extended_egrep_awk(option);
+    test_gh_5244_atomescape_posix_not_awk(option);
+
+    // there are no backreferences in extended regexes
+    g_regexTester.should_throw(R"(\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\1)", error_escape, option);
+    g_regexTester.should_throw(R"((a)\0)", error_escape, option);
+    g_regexTester.should_throw(R"(\1(a))", error_escape, option);
+}
+
+void test_gh_5244_atomescape_awk() {
+    test_gh_5244_atomescape_extended_egrep_awk(awk);
+
+    // awk-only escapes
+    check_atomescape_controlescape("\a", "a", awk);
+    check_atomescape_controlescape("\b", "b", awk);
+    check_atomescape_controlescape("\f", "f", awk);
+    check_atomescape_controlescape("\n", "n", awk);
+    check_atomescape_controlescape("\r", "r", awk);
+    check_atomescape_controlescape("\t", "t", awk);
+    check_atomescape_controlescape("\v", "v", awk);
+    check_atomescape_identityescape("\"", awk);
+    check_atomescape_identityescape("/", awk);
+
+    // awk supports octal sequences
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_not_match("1", R"(\1)", awk);
+    g_regexTester.should_not_match("\\1", R"(\1)", awk);
+    g_regexTester.should_match("\11", R"(\11)", awk);
+    g_regexTester.should_not_match("1", R"(\11)", awk);
+    g_regexTester.should_not_match("11", R"(\11)", awk);
+    g_regexTester.should_not_match("\\11", R"(\11)", awk);
+    g_regexTester.should_match("\111", R"(\111)", awk);
+    g_regexTester.should_not_match("1", R"(\111)", awk);
+    g_regexTester.should_not_match("11", R"(\111)", awk);
+    g_regexTester.should_not_match("111", R"(\111)", awk);
+    g_regexTester.should_not_match("\\111", R"(\111)", awk);
+    // clang-format off
+    g_regexTester.should_match("\111""1", R"(\1111)", awk);
+    // clang-format on
+    g_regexTester.should_not_match("\111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1", R"(\1111)", awk);
+    g_regexTester.should_not_match("11", R"(\1111)", awk);
+    g_regexTester.should_not_match("111", R"(\1111)", awk);
+    g_regexTester.should_not_match("1111", R"(\1111)", awk);
+    g_regexTester.should_not_match("\\1111", R"(\1111)", awk);
+    g_regexTester.should_match("A", R"(\101)", awk);
+    g_regexTester.should_match("Aa", R"(\101a)", awk);
+    g_regexTester.should_match("\33", R"(\033)", awk);
+    g_regexTester.should_match("\33a", R"(\033a)", awk);
+    g_regexTester.should_match("\33", R"(\33)", awk);
+    g_regexTester.should_match("\33a", R"(\33a)", awk);
+    g_regexTester.should_match("\1", R"(\001)", awk);
+    g_regexTester.should_match("\1a", R"(\001a)", awk);
+    g_regexTester.should_match("\1", R"(\01)", awk);
+    g_regexTester.should_match("\1a", R"(\01a)", awk);
+    g_regexTester.should_match("\1", R"(\1)", awk);
+    g_regexTester.should_match("\1a", R"(\1a)", awk);
+    g_regexTester.should_throw(R"(\8)", error_escape, awk);
+    // clang-format off
+    g_regexTester.should_match("\1""8", R"(\18)", awk);
+    g_regexTester.should_match("\12""9", R"(\129)", awk);
+    // clang-format on
+
+    // octal sequences evaluating to 0 are considered undefined by the standard
+    g_regexTester.should_throw(R"(\0)", error_escape, awk);
+    g_regexTester.should_throw(R"(\00)", error_escape, awk);
+    g_regexTester.should_throw(R"(\000)", error_escape, awk);
+}
+
+void test_gh_5244_classescape_posix_not_awk(syntax_option_type option) {
+    // Sections "BRE Bracket Expressions" and "ERE Bracket Expressions":
+    // Backslash shall lose its special meaning, so \c should always match \ + c
+
+    // common special characters outside character classes
+    check_classescape_noescape(".", option);
+    check_classescape_noescape("[", option);
+    check_classescape_noescape("*", option);
+    check_classescape_noescape("^", option);
+    check_classescape_noescape("$", option);
+    check_classescape_noescape("B", option);
+    check_classescape_noescape("c", option);
+
+    // special characters outside character classes in extended regexes
+    check_classescape_noescape("+", option);
+    check_classescape_noescape("?", option);
+    check_classescape_noescape("|", option);
+
+    // TRANSITION, GH-5379
+    if (option & (extended | egrep | awk)) {
+        check_classescape_noescape("(", option);
+        check_classescape_noescape(")", option);
+        check_classescape_noescape("{", option);
+    }
path: tests/std/tests/GH_005244_regex_escape_sequences/test.cpp
user: login: StephanTLavavej
body: We're in `test_gh_5244_classescape_posix_not_awk`, so we don't need to test for awk. And in fact, this is forming `string pattern = "[\\" + c + "]";`, which awk will throw exceptions for (as tested in the awk-specific function).
created_at: 2025-04-09T11:12:25Z
author_association: MEMBER
diff_hunk: @@ -4406,22 +4407,24 @@ bool _Parser<_FwdIt, _Elem, _RxTraits>::_CharacterEscape() { // check for valid
 template <class _FwdIt, class _Elem, class _RxTraits>
 void _Parser<_FwdIt, _Elem, _RxTraits>::_AtomEscape() { // check for valid atom escape
     constexpr int _Bre_max_backref_digits = 1;
-    if ((_L_flags & _L_bckr)
-        && _DecimalDigits2(regex_constants::error_backref,
-            (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
-        if (_Val == 0) { // handle \0
-            if (!(_L_flags & _L_bzr_chr)) {
-                _Error(regex_constants::error_escape);
-            } else {
-                _Nfa._Add_char2(static_cast<_Elem>(_Val));
-            }
+    if ((_L_flags & _L_bzr_chr) && _Traits.value(_Char, 10) == 0) { // handle \0
+        _Next();
+        if (_Traits.value(_Char, 10) != -1) {
+            _Error(regex_constants::error_escape);
+        }
+        _Nfa._Add_char2(_Elem{});
+    } else if ((_L_flags & _L_bckr)
+               && _DecimalDigits2(regex_constants::error_backref,
+                   (_L_flags & _L_lim_bckr) ? _Bre_max_backref_digits : INT_MAX)) { // check for valid back reference
path: stl/inc/regex
user: login: StephanTLavavej
body: I like the new logic, thanks!
created_at: 2025-04-09T11:18:36Z
author_association: MEMBER

PR comments:
user: login: StephanTLavavej
created_at: 2025-04-09T07:18:07Z
body: I am reviewing this now - thanks as always for the extremely detailed PR description with citations, it is super appreciated for these complex changes. I edited it to fix a typo that made a citation harder to understand - please meow if I messed it up instead.

> The parser supports the escape sequence `\[` but not `\]`. More recent POSIX versions define `\]` as well in basic and extended regular expressions. Should we add support for `\]`?

Yes, I would be in favor of that, in a followup PR - it seems analogous to implementing a C++ Defect Report, and I suspect 99.9% of users would already expect that behavior.


author_association: MEMBER
user: login: StephanTLavavej
created_at: 2025-04-09T11:22:06Z
body: Thanks for this amazing overhaul and extreme attention to detail! :heart_eyes_cat: I pushed one significant change to the product code and a few to the test code - please meow if I messed anything up.
author_association: MEMBER
user: login: StephanTLavavej
created_at: 2025-04-09T12:09:37Z
body: I'm mirroring this to the MSVC-internal repo - please notify me if any further changes are pushed.
author_association: MEMBER
user: login: StephanTLavavej
created_at: 2025-04-10T06:51:13Z
body: Resolved adjacent-edit conflicts with #5364 in `<regex>` where:

* it changed to `_ClassAtom(bool)` and this PR changed to `_ClassEscape3()`
* it changed to `_ClassAtom(const bool _Initial)` and this PR expanded "check for valid escape sequence"
author_association: MEMBER
user: login: StephanTLavavej
created_at: 2025-04-10T07:45:39Z
body: :arrow_left: :runner: :tada:
===
author_association: MEMBER