brown-uk / dict_uk

Project to generate POS tag dictionary for Ukrainian language
GNU General Public License v3.0
550 stars 71 forks source link

Помилка у Hunspell словнику, яка псує пам'ять #253

Closed roman-kruglov closed 3 years ago

roman-kruglov commented 3 years ago

Вітаю!

Коли завантажував ваш словник у Hunspell під дебагером MSVC - стабільно псується пам'ять на наступних строках:

SFX P есь сього ^весь
SFX P есь сьому ^весь
SFX P есь сім ^весь
SFX P есь ся ^весь
SFX P есь сієї ^весь
SFX P есь сій ^весь
SFX P есь сю ^весь
SFX P есь сією ^весь
SFX P есь се ^весь
SFX P есь сі ^весь
SFX P есь сіх ^весь
SFX P есь сіма ^весь

місце у коді, якщо знадобиться:

void AffixMgr::reverse_condition(std::string& piece) {
      ...
      case '^': {
        if (*(k - 1) == ']')
          neg = 1;
        else
          *(k - 1) = *k;

там де *(k - 1) - одразу виходить за межі у чужу пам'ять. А потім ще і записує в неї символ ^. А там як пощастить, що там в динамічній пам'яті поряд розташувалося. Але UB 100%.

Оновлення до останніх версій і словника і Hunspell не допомогли - в словнику ці строки наявні і в Hunspell код вище не відрізняється.

Я не впевнений на 100%, як повинно бути, але мені зда'ється з того, що я знайшов про формати словників, і того що видно з коду - ^ у цьому випадку може бути використана лише після [ або (.

Ось як я виправив ці строки, ніби відповідає початковому задуму, якщо я його вірно вгадав:

SFX P есь сього [^в]есь
SFX P есь сьому [^в]есь
SFX P есь сім [^в]есь
SFX P есь ся [^в]есь
SFX P есь сієї [^в]есь
SFX P есь сій [^в]есь
SFX P есь сю [^в]есь
SFX P есь сією [^в]есь
SFX P есь се [^в]есь
SFX P есь сі [^в]есь
SFX P есь сіх [^в]есь
SFX P есь сіма [^в]есь

так працює ніби нормально, принаймні пам'ять не псує. Нажаль я знайшов це у вже готових словниках, а де у вас виправити тут у репозиторії - не певен.

П.С. вдячний вам за ваші чудові словники і вашу роботу, дуже важлива справа!

До речі, у вас випадком немає у словниках окремо власне української лексики (незапозичені слова, конкретно не (спільні \ схожі) на російські)? Або можливо знаєте, де можна знайти? Поки що нажаль не зміг знайти нічого подібного. Хочу створити колоду для Anki.

arysin commented 3 years ago

Вітання, я спробував запустити hunspell через valgrid на Linux і мені нічого не показало. Але «^весь» має працювати лише для слова «весь» (і не працювати для «увесь»). І наскільки я бачу, принаймні на Linux воно похідні форми (напр. «всього») розпізнає. Я можу спробувати зробити виняток для цього випадку, щоб не було неоднозначності. Але варіант «[^в]есь» виправлення вилучить похідні від «весь», ви б не могли перевірити? Якщо так, то може «(^весь)» натомість спрацює, поки я не закину виняток?

roman-kruglov commented 3 years ago

Напишу окремо, щоб не плутатися. Щодо valgrind на Linux.

Можливо у вас якась інша версія hunspell? Я спробував на двох версіях - якась близька до 1.6 та останній master коміт. В обох версіях ці умови проходили один і той же шлях, приблизно

bool AffixMgr::parse_affix(const std::string& line,
                          const char at,
                          FileMgr* af,
                          char* dupflags)
.....
// piece 5 - is the conditions descriptions
std::string chunk(start_piece, iter); // here chunk == "^весь"
reverseword(chunk); // here it is something like "....^" - what's important, the ^ at the end now
reverse_condition(chunk);

де всередині reverse_condition одразу йде

void AffixMgr::reverse_condition(std::string& piece) {
  if (piece.empty())
      return;

  int neg = 0;
  for (std::string::reverse_iterator k = piece.rbegin(); k != piece.rend(); ++k) {
    switch (*k) { // here *k == '^' on the first iteration
...
      case '^': {
        if (*(k - 1) == ']') // here it accesses a byte outside (after) piece's std::string buffer. Usually it != ']', but some other stuff. It could be though
          neg = 1;
        else
          *(k - 1) = *k; // usually it goes here, overwriting the next byte after original chunk std::string buffer. UB
        break;
      }

А яке приладдя використовували с valgrind? MemCheck за замовчуванням?

roman-kruglov commented 3 years ago

Наскільки я зрозумів по тим небагатьом шматочкам інформації, що я знайшов про формат афікс файлів, якщо «^весь» має працювати лише для слова «весь», то може там повинно бути просто "весь"? Тоді воно мабуть спрацює і коли "весь" буде просто в кінці слова, а не окремим словом. Якщо такі бувають.

Мабуть там ^ і означала - початок строки \ слова. Але мені здається конкретно для цієї директиви (SFX опис правила, мабуть так воно називається) в коді такої можливості не задумано. Хоча бачив для деяких інших директив таку можливість. А ось що буде, якщо (^весь) - не знаю, мабуть спрацює. Спробую.

Варіант «[^в]есь» гадаю справді вилучить похідні від «весь», я чогось подумав що так і задумано.

А хіба.. воно не повинно і для «увесь» працювати? Усього, усьому, усім, уся, усієї, усе - google ai не бачить у цих словах проблем. Ну то може вже я тонкощі не розумію.

roman-kruglov commented 3 years ago

Наприклад тут http://manpages.ubuntu.com/manpages/bionic/man5/hunspell.5.html - спробуйте пошукати символ ^ по цій сторінці, для суфіксів (і префіксів) він згадується лише як

          (4) condition.

          Zero stripping or affix are indicated by zero. Zero condition is indicated by  dot.
          Condition  is  a  simplified,  regular  expression-like  pattern, which must be met
          before the affix can be applied. (Dot signs an arbitrary character.  Characters  in
          braces  sign  an  arbitrary  character  from  the character subset. Dash hasn't got
          special meaning, but circumflex (^) next the  first  brace  sets  the  complementer
          character set.)

але функціонал, схожий на той, що ви намагалися використати, є для іншої директиви афікс файлів:

   REP what replacement
          This  table  specifies modifications to try first.  First REP is the header of this
          table and one or more REP data line are following it.  With  this  table,  Hunspell
          can  suggest  the  right forms for the typical spelling mistakes when the incorrect
          form differs by more than 1 letter from the right form.  The search string supports
          the  regex  boundary  signs  (^ and $).

хоча не впевнений, що підійде.. Просто я так розумію функціонал для афіксів (суфіксів, префіксів) може взагалі задуманий тільки для частин слів?

roman-kruglov commented 3 years ago

Спробував варіант з (^весь). Ну, словник завантажився, MSVC не скаржиться, ніби в тому місці немає проблеми. Спробував перевірити слово. Не впевнений як правильно було б перевірити саме цю ситуацію, але на обидва слова "сього" і "вього" видає в списку першим "всього". Слова "всього", "всьому", "весь", "всім" - вважає правильними. Це ті, що я спробував. Якщо підкажете як ще перевірити - зроблю.

arysin commented 3 years ago

Напишу окремо, щоб не плутатися. Щодо valgrind на Linux.

Можливо у вас якась інша версія hunspell? Я спробував на двох версіях - якась близька до 1.6 та останній master коміт. В обох версіях ці умови проходили один і той же шлях, приблизно ... А яке приладдя використовували с valgrind? MemCheck за замовчуванням? Так, типовий valgrind з memcheck hunspell 1.7.0 Не псує, але додав тестів і схоже ^весь не працює, відпишу докладніше нижче

roman-kruglov commented 3 years ago

хм.. хоча якщо поміркувати, то виходить, що можливо воно не в сусідню пам'ять, а останній "нульовий" символ заміняє. Але це також нічого доброго не зробить. Може тому valgrind не бачить, бо воно формально у той же самий буфер пише, який строка виділила.

Ну якщо там далі десь скопіювати в інший інстанс std::string, то воно орієнтуватиметься на .size(), яке не орієнтується на нульовий символ. А ось якщо на цій "зіпсованій" строці спробують .c_str(), то гадаю біда буде. Ну, може не в чужу пам'ять, але за межу початку ітератора точно. Якщо не помиляюся, все ще Undefined Behavior (UB).

arysin commented 3 years ago

Але варіант «[^в]есь» виправлення вилучить похідні від «весь», ви б не могли перевірити? Якщо так, то може «(^весь)» натомість спрацює, поки я не закину виняток?

Фактично тут ідея в тому, щоб від «весь» генерувало «всьому», але від «увесь» не генерувало «увсього» (правильно «усього»). Тобто треба дві перевірки - «всього» має бути ок, а «увсього» - ні. Й тут у мене другий тест не проходить. Я гляну, чи варто це просто занести у виняток, чи зробити (^весь) - тоді, схоже, все гаразд.

roman-kruglov commented 3 years ago

Спробував у себе - з варіантом (^весь) для всіх тих суфікс правил в словнику - «всього» вважає правильним, «увсього» вважає невірним, пропонує "усього", "всього", "у всього". Ніби працює, як потрібно.

arysin commented 3 years ago

Перепрошую, не відповів на інше питання: словника окремо власне української лексики у мене немає.

І дуже дякую за звіт про проблему!

roman-kruglov commented 3 years ago

Був радий допомогти! Вдячний вам за роз'яснення, а то б невірний варіант використав.