2012-10-10 19 views
7

Używam std::regex_iterator do analizowania plików dziennika. Mój program działa całkiem ładnie od kilku tygodni i przeanalizował miliony wierszy dziennika, do dziś, kiedy dziś uruchomiłem go w pliku dziennika i uzyskałem przepełnienie stosu. Okazało się, że przyczyną problemu był tylko jeden wiersz dziennika w pliku dziennika. Czy ktoś wie, dlaczego moje regex powoduje tak masową rekursję? Oto mały autonomiczny program, który pokazuje problem (mój kompilator jest VC2012):Dlaczego std :: regex_iterator powoduje przepełnienie stosu tymi danymi?

#include <string> 
#include <regex> 
#include <iostream> 

using namespace std; 

std::wstring test = L"L3 T15356 79726859 [CreateRegistryAction] Creating REGISTRY Action:\n" 
       L" Identity: 272A4FE2-A7EE-49B7-ABAF-7C57BEA0E081\n" 
       L" Description: Set Registry Value: \"SortOrder\" in Key HKEY_CURRENT_USER\\Software\\Hummingbird\\PowerDOCS\\Core\\Plugins\\Fusion\\Settings\\DetailColumns\\LONEDOCS1\\Search Unsaved\\$AUTHOR.FULL_NAME;DOCSADM.PEOPLE.SYSTEM_ID\n" 
       L" Operation: 3\n" 
       L" Hive: HKEY_CURRENT_USER\n" 
       L" Key: Software\\Hummingbird\\PowerDOCS\\Core\\Plugins\\Fusion\\Settings\\DetailColumns\\LONEDOCS1\\Search Unsaved\\$AUTHOR.FULL_NAME;DOCSADM.PEOPLE.SYSTEM_ID\n" 
       L" ValueName: SortOrder\n" 
       L" ValueType: REG_DWORD\n" 
       L" ValueData: 0\n" 
       L"L4 T15356 79726859 [CEMRegistryValueAction::ClearRevertData] [ENTER]\n"; 

int wmain(int argc, wchar_t* argv[]) 
{ 
    static wregex rgx_log_lines(
     L"^L(\\d+)\\s+"    // Level 
     L"T(\\d+)\\s+"    // TID 
     L"(\\d+)\\s+"    // Timestamp 
     L"\\[((?:\\w|\\:)+)\\]"  // Function name 
     L"((?:"      // Complex pattern 
      L"(?!"     // Stop matching when... 
      L"^L\\d"    // New log statement at the beginning of a line 
      L")"      
      L"[^]"     // Matching all until then 
     L")*)"      // 
     ); 

    try 
    { 
     for (std::wsregex_iterator it(test.begin(), test.end(), rgx_log_lines), end; it != end; ++it) 
     { 
      wcout << (*it)[1] << endl; 
      wcout << (*it)[2] << endl; 
      wcout << (*it)[3] << endl; 
      wcout << (*it)[4] << endl; 
      wcout << (*it)[5] << endl; 
     } 
    } 
    catch (std::exception& e) 
    { 
     cout << e.what() << endl; 
    } 

    return 0; 
} 
+0

Kompleks część wzór wydaje się być przyczyną tego. Nie wiem dlaczego. –

+0

Założę się, że jest w porządku w perlu, nie całkiem ufam 'std :: regex' jeszcze. – Benj

+2

@Benj Wut? FUD. Może to być wyrażenie regularne niepoprawnie wykładające. Najczęściej dotyczy to nested kleene stars. Spróbuj użyć nie-chciwych dopasowań i lub, jeśli to możliwe, użyj '+' zamiast '*'. Uważaj także na opcje w powtarzających się grupach. Najlepsza rada ... Zacznij od małej. Buduj krok po kroku. Przetestuj swoje wyrażenie regularne na każdym kroku. – sehe

Odpowiedz

4

Negatywne wzory z wyprzedzeniem, które są testowane na każdej z postaci, wydają mi się złym pomysłem, a to, co próbujesz zrobić, nie jest skomplikowane. Chcesz dopasować (1) resztę linii, a następnie (2) dowolną liczbę następujących (3) linii, które zaczynają się od czegoś innego niż L \ d (mały błąd, patrz poniżej): (inna edycja: są to wyrażenia regularne; jeśli chcesz zapisać je jako napisowych, trzeba zmienić \ do \\.)

.*\n(?:(?:[^L]|L\D).*\n)* 
| | | 
+-1 | +---------------3 
    +---------------------2 

w trybie ECMAScript, . nie powinien pasować \ n, ale zawsze można wymienić dwie . sw tego wyrazu [^\n]

Edytowany w celu dodania: Rozumiem, że może to nie działać, jeśli jest pusta linia tuż przed końcem wpisu dziennika, ale powinno to obejmować ten przypadek; Zmieniłem . do [^\n] za dodatkową precyzję:

[^\n]*\n(?:(?:(?:[^L\n]|L\D)[^\n]*)?\n)* 
+0

Dobra robota ;-) To działa, nie przyszło mi do głowy, że można to zrobić bez negatywnego uprzedzenia. – Benj

+0

Warto zwrócić uwagę na potomność, musiałem użyć '[^ \ n]' jak sugerowałeś. – Benj

+0

@Benj Dobrze wiedzieć; Nie mam odwagi VC, żeby to wypróbować. Zakładam, że używasz '[^]' do oznaczania "dowolnej postaci", która [^ L] faktycznie będzie pasować również do pustej linii. W takim przypadku edytuję z niewielką modyfikacją. – rici

1

regex wydaje się być OK; przynajmniej nie ma w nim nic, co mogłoby spowodować katastroficzne cofnięcie.

widzę mała możliwość, aby zoptymalizować regex, wycinanie na użyciu stosu:

static wregex rgx_log_lines(
    L"^L(\\d+)\\s+"    // Level 
    L"T(\\d+)\\s+"    // TID 
    L"(\\d+)\\s+"    // Timestamp 
    L"\\[([\\w:]+)\\]"   // Function name 
    L"((?:"      // Complex pattern 
     L"(?!"     // Stop matching when... 
     L"^L\\d"    // New log statement at the beginning of a line 
     L")"      
     L"[^]"     // Matching all until then 
    L")*)"      // 
    ); 

Czy chodziło Ci set the ECMAScript option? W przeciwnym razie podejrzewam, że biblioteka regex domyślnie używa wyrażeń regularnych POSIX, a te nie obsługują asercji z wyprzedzeniem.

+0

Niestety 'std :: regex' nie ma koncepcji regexu wielowierszowego (w przeciwieństwie do perla), więc' .' nie może być używane między liniami i '^' i '$' oznacza początek/koniec linii.Te zakotwiczenia faktycznie zmieniają się w perlu w zależności od tego, czy jesteś w trybie pojedynczego/wieloliniowego – Benj

+0

@Benj: Ah, OK, dobrze, to jest dobre dla tego regex. moja wersja nadal powoduje StackOverflow następnie? –

+0

Mogę być ślepy :-) Ale co się zmieniło? Czy to nie jest takie samo? – Benj

Powiązane problemy