Подтвердить что ты не робот

С++ regex segfault на длинных последовательностях

Я анализировал свалку stackoverflow и придумал этот, казалось бы, невинный вопрос с небольшими, почти невидимыми деталями, которые имеют 22311 пробелов в конце текста.

Я использую std:: regex (как-то они работают лучше для меня, чем boost:: regex), чтобы заменить все непрерывные пробелы одним пространством следующим образом:

std::regex space_regex("\\s+", std::regex::optimize);
...
std::regex_replace(out, in, in + strlen(in), space_regex, " ");

SIGSEGV появляется, и я начал исследовать.

Тестовый код:

#include <regex>
...
std::regex r("\\s+",  std::regex::optimize);
const char* bomb2 = "Small text\n\nwith several\n\nlines.";
std::string test(bomb2);
for (auto i = 0; i < N; ++i) test += " ";

std::string out = std::regex_replace(test.c_str(), r, " ");
std::cout << out << std::endl;

для (gcc 5.3.0)

$ g++ -O3 -std=c++14 regex-test.cpp -o regex-test.out

максимум N до появления SIGSEGV - 21818 (для этой конкретной строки) и для

$ g++ -O0 -std=c++14 regex-test.cpp -o regex-test.out

it 12180.

'Хорошо, пусть попробует clang, это тренда и нацелено на замену gcc' - никогда не было так плохо. С -O0 clang (v. 3.7.1) падает на 9696 пробелов - меньше, чем gcc, но не много, но с -O3 и даже с -O2 он падает в пространствах ZERO.

Дамп сбоя представляет огромные стеки (35k кадров) рекурсивных вызовов

std::__detail::_Executor<char*, std::allocator<std::__cxx11::sub_match<char*> >, std::__cxx11::regex_traits<char>, true>::_M_dfs

Вопрос 1: Это ошибка? Если да, должен ли я сообщить об этом?

Вопрос 2: есть ли умный способ преодолеть проблему (кроме увеличения размера системного стека, попыток других библиотек регулярных выражений и запись собственной функции для замены пробелов)?


Поправка: отчет об ошибке, созданный для libstdС++

4b9b3361

Ответ 1

Это ошибка? Если да, должен ли я сообщить об этом?

Да, это ошибка.

cout << '"' << regex_replace("Small text\n\nwith several\n\nlines." + string(22311, ' '), regex("\\s+", regex::optimize), " ") << '"' << endl;

Но это просто ошибка с libstdС++, поэтому не стесняйтесь сообщать об этом здесь: https://gcc.gnu.org/bugzilla/buglist.cgi?product=gcc&component=libstdc%2B%2B&resolution=---

Есть ли умный способ преодолеть проблему?

Если вы запрашиваете новый regex, который работает, я пробовал несколько разных версий, и все они не работают на libstdС++, поэтому я бы сказал, если вы хотите использовать regex Чтобы решить эту проблему, вам нужно скомпилировать файл libС++.

Но, честно говоря, если вы используете regex для выделения дублированного пробела, "Теперь у вас две проблемы"

Лучшее решение может использовать adjacent_find, который работает нормально с libstdС++ также:

const auto func = [](const char a, const char b){ return isspace(a) && isspace(b); };

for(auto it = adjacent_find(begin(test), end(test), func); it != end(test); it = adjacent_find(it, end(test), func)) {
    *it = ' ';
    it = test.erase(next(it), find_if_not(next(it), end(test), [](const auto& i) { return isspace(i); }));
}

Это вернет то же самое, что и ваш regex:

"Маленький текст с несколькими строками".

Но если вы собираетесь простота, вы также можете использовать unique:

test.resize(distance(test.begin(), unique(test.begin(), test.end(), [](const auto& a, const auto& b) { return isspace(a) && isspace(b); })));

Что вернет:

"Малый текст
с несколькими
линий."

Ответ 2

Вопрос 2 (умный способ преодоления проблемы)

Не очень умный, но... вы можете повторить ограниченную замену.

Пример

#include <regex>
#include <iostream>

int main()
 {
   constexpr int N = 22311;

   //std::regex r("\\s+");
   std::regex r("\\s{2,100}");

   const char* bomb2 = "Small text\n\nwith several\n\nlines.";

   std::string test(bomb2);

   for (auto i = 0; i < N; ++i)
      test += " ";

   std::string out = test;

   std::size_t  preSize;

   do
    {
      preSize = out.size();

      out = std::regex_replace(out, r, " ");
    }
   while ( out.size() < preSize );

   std::cout << '\"' << out << '\"' << std::endl;

   return 0;
 }