Skip to content

<regex>: Incorrect submatch result for empty group #6028

Description

@sorokin

Describe the bug

Running regex (|)*a on text a produces a successfully matched submatch[1]. I believe this is incorrect and the correct behavior is to have unmatched submatch[1].

I don't know much about ECMAScript regexes, but from my understanding this is incorrect because * should not allow its subexpression to match empty string. This current behavior of STL differs from the behavior of the regex engine in Chrome and Edge. Also it is inconsistent with similar regex ()*a which correctly doesn't match submatch[1].

Command-line test case

C:\Temp>type repro.cpp
#include <cassert>
#include <iostream>
#include <regex>
#include <string>

void print_submatch(std::ssub_match const& subm, std::string::const_iterator begin)
{
    std::cout << "\"" << subm << "\" [";
    std::cout << (subm.matched ? "matched" : "not matched");
    std::cout << ", start: " << subm.first - begin << ", end: " << subm.second - begin;
    std::cout << ']';
}

void run_regex(std::string const& pattern, std::string const& text)
{
    std::cout << "Regex: " << pattern << ", Text: \"" << text << "\"" << '\n';
    try
    {
        std::regex re(pattern);
        std::smatch match;
        std::regex_match(text, match, re);
        assert(match.ready());
        for (size_t i = 0; i < match.size(); ++i)
        {
            if (i == 0)
            {
                std::cout << "  Full match: ";
                print_submatch(match[i], text.begin());
                std::cout << '\n';
            }
            else
            {
                std::cout << "  Submatch [" << i << "]: ";
                print_submatch(match[i], text.begin());
                std::cout << '\n';
            }
        }
    }
    catch (std::regex_error const& e)
    {
        std::cout << "  Regex error: " << e.what() << '\n';
    }
    std::cout << '\n';
}

int main()
{
    run_regex("()*a", "a");
    run_regex("(|)*a", "a");
    return 0;
}

C:\Temp>cl /EHsc /W4 /WX .\repro.cpp
Оптимизирующий компилятор Microsoft (R) C/C++ версии 19.44.35219 для x64
(C) Корпорация Майкрософт (Microsoft Corporation).  Все права защищены.

repro.cpp
Microsoft (R) Incremental Linker Version 14.44.35219.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:repro.exe
repro.obj

C:\Temp>.\repro.exe
Regex: ()*a, Text: "a"
  Full match: "a" [matched, start: 0, end: 1]
  Submatch [1]: "" [not matched, start: 1, end: 1]

Regex: (|)*a, Text: "a"
  Full match: "a" [matched, start: 0, end: 1]
  Submatch [1]: "" [matched, start: 0, end: 0]

Expected behavior

The expected behavior is to have output

Regex: (|)*a, Text: "a"
  Full match: "a" [matched, start: 0, end: 1]
  Submatch [1]: "" [not matched, start: 1, end: 1]

similar to the other test case and the behavior of the browsers.

I also made a small sample for browsers. I used it to validate that the behavior of std::regex doesn't match theirs:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JS Regex Repro</title>
</head>
<body>
    <pre id="output"></pre>
    <script>
        const output = document.getElementById('output');
        function log(msg) {
            output.textContent += msg + '\n';
        }

        function runRegex(patternStr, text) {
            log(`Regex: /${patternStr}/, Text: "${text}"`);
            try {
                const re = new RegExp(patternStr, 'd');
                const match = re.exec(text);
                if (match) {
                    for (let i = 0; i < match.length; i++) {
                        const prefix = i === 0 ? "  Full match: " : `  Submatch [${i}]: `;
                        const value = match[i] === undefined ? "undefined" : `"${match[i]}"`;
                        const matched = match[i] !== undefined ? "matched" : "not matched";
                        const start = match.indices[i] === undefined ? "undefined" : match.indices[i][0];
                        const end = match.indices[i] === undefined ? "undefined" : match.indices[i][1];
                        log(`${prefix}${value} [${matched}, start: ${start}, end: ${end}]`);
                    }
                } else {
                    log(`  No match.`);
                }
            } catch (e) {
                log(`  Error: ${e.message}`);
            }
            log('');
        }

        runRegex('()*a', 'a');
        runRegex('(|)*a', 'a');
    </script>
</body>
</html>

STL version

_MSVC_STL_UPDATE value is 202503.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingregexmeow is a substring of homeownerresolvedSuccessfully resolved without a commit

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions