avva: (Default)
[personal profile] avva

Пытаясь понять, почему моя программа не выводит текст так, как я хочу, обнаружил баг в одной из стандартных функций языка Перл. Подробности под катом - понятны будут только тем, кто знает Перл.

Баг в функции sprintf. Он проявляется, когда при выводе строки (аргумент %s), которая к тому же содержит широкие символы в UTF-8 (напр. русские буквы), используются одновременно оба ограничения на длину - минимальное и максимальное - и они равны. Ниже копирую вывод моей тест-программы, которая изолирует баг, и демонстрирует, при каких вызовах функции он происходит.

Первые две пары вызовов printf демонстрируют баг, остальные - его отсутствие при других аргументах.

printf('%-3.3s', 'абвгд'): |абв  |
printf('%-3.3s', '01234'): |012|

printf('%3.3s', 'абвгд'): |  абв|
printf('%3.3s', '01234'): |012|

printf('%3s', 'абвгд'): |абвгд|
printf('%3s', '01234'): |01234|

printf('%.3s', 'абвгд'): |абв|
printf('%.3s', '01234'): |012|

printf('%3.4s', 'абвгд'): |абвг|
printf('%3.4s', '01234'): |0123|

Проверил, что баг содержится и в самой последней версии языка. Теперь надо посмотреть, сколько осталось пороха в пороховницах и насколько будет легко его починить...

Date: 2006-10-08 09:11 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
Как ведет себя данный код при use locale; и при use utf8; ?

Не может ли это быть ошибкой libc?

Date: 2006-10-08 09:17 pm (UTC)
From: [identity profile] avva.livejournal.com
Подразумевается use utf8; (поэтому я могу вставлять строку прямо в текст программы). use locale; тут вообще не должно играть роли. С точки зрения Перла (при наличии use utf8;) строки "абвгд" и "01234" одинаковой длины, 5 символов, и все указания длин внутри форматной строки sprintf относятся к символам, а не к байтам (и в принципе это работает, как видно из тех примеров, где баг не происходит).

Это точно не ошибка libc, sprintf перла не пользуется sprintf'ом системы, вместо этого все делает сам внутри.

Date: 2006-10-08 09:24 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
А, про utf-8 было не совсем ясно, мы-то тут до сих пор KOI8-R балуемся. Поэтому вызвала удивление ссылка на wide characters.

Да, при таких вводных ошибка проявляется :(

Date: 2006-10-08 09:35 pm (UTC)
From: [identity profile] avva.livejournal.com
Добавил про UTF-8, чтобы было понятнее.
Я вообще теперь стараюсь работать только в utf-8, и локаль у меня такая стоит, и вообще все :)

Date: 2006-10-08 09:53 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Хм. В моей версии нет проблемы. Следующий код:

print "_"; printf('%-3.3s', 'abcde');print "_\n";
print "_"; printf('%-3.3s', '01234');print "_\n";
print "_"; printf('%3.3s', 'abcde');print "_\n";
print "_"; printf('%3.3s', '01234');print "_\n";

Печатает(ожидаемо):
_abc_
_012_
_abc_
_012_


А следующий код:
print "_"; printf '<%.5s>', "truncated"; print "_\n";
print "_"; printf '<%.5s>', "012345678"; print "_\n\n;

print "_"; printf '<%10.5s>', "truncated"; print "_\n";
print "_"; printf '<%10.5s>', "012345678"; print "_\n\n";

print "_"; printf '<%-.5s>', "truncated"; print "_\n";
print "_"; printf '<%-.5s>', "012345678"; print "_\n\n";

print "_"; printf '<%-10.5s>', "truncated"; print "_\n";
print "_"; printf '<%-10.5s>', "012345678"; print "_\n\n";

Печатает (тоже ожидаемо):
__
_<01234>_

_< trunc>_
_< 01234>_

__
_<01234>_

__
_<01234 >_

А какая у Вас версия версия perl? У меня v5.8.4

Date: 2006-10-08 09:55 pm (UTC)
From: [identity profile] ninazino.livejournal.com
А-а-а, с этого надо было начинать. С этой локалью таки могут быть проблемы.

Date: 2006-10-08 10:12 pm (UTC)
From: [identity profile] avva.livejournal.com
Дело не в локали. Локаль не играет роли. Перл сохраняет строки внутри всегда либо как последовательность байтов, либо в UTF-8 (как в данном случае). Это - баг работы Перла внутри со своими utf-8-строками.

Date: 2006-10-08 10:13 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, проверила, с UTF-8 и у меня проявилось.

Date: 2006-10-08 10:13 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
Я вот на FreeBSD, не смотря на поддержку оной UTF-8 все не так хорошо, как хотелось бы, поэтому пока KOI8-R. Ну, и соответсвенно - мышление однобайтное :)

Date: 2006-10-08 10:21 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Хм. Когда под веб приходится писать, да под много языков, локаль консоли перестаёт играть определяющую роль %)

Date: 2006-10-08 10:25 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
А где тут символы с кодом более 255? %)
Попробуйте русские буквы (начинаются с 0x410), расширенную латиницу (от 0x100), греческие (0x391), etc.

Date: 2006-10-08 10:28 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Правда Ваша. Но вот так -- работает правильно:

use utf8;
my $string = 'абвгд';
utf8::encode($string);
printf('%-3.3s', $string);
printf('%3.3s', $string);

Date: 2006-10-08 10:31 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, я уже поняла, в чем тут дело. И даже нашла work-around (http://avva.livejournal.com/1657361.html?thread=37461777#t37461777).

Date: 2006-10-08 10:39 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Хм. А может, это не workaround, а вообще intended use? %)

Date: 2006-10-08 10:50 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет. Какое же это правильно? Это выключает utf8-флаг на скаларе $string, превращая его из строки в 5 широких символов в строку из 10 восьмибитных символов. Теперь printf обрежет строку на третьем байте, а не на шестом байте (третьем символе), как хотелось бы. Т.е. с точки зрения байтов printf больше не ошибается, верно, но вся семантика работы со строкой теряется.

Date: 2006-10-08 10:51 pm (UTC)
From: [identity profile] avva.livejournal.com
Ни в коем случае. См. ответ выше.
utf8::encode выключает utf8-флаг, делая строку "абвгд" идентичной строке "0123456789" (с точки зрения количества символов и того, как перл с ними работает).

Date: 2006-10-08 10:52 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, мне тоже пришла в голову такая мысль ((:

Date: 2006-10-08 10:57 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Но у меня печатает "абв", что в этом неправильного? То есть как раз на третьем символе.

Date: 2006-10-08 11:09 pm (UTC)
From: [identity profile] avva.livejournal.com
Вроде бы починил.

--- perl-5.9.4-orig/sv.c        2006-08-15 15:37:41.000000000 +0300
+++ perl-5.9.4/sv.c     2006-10-09 01:06:31.000000000 +0200
@@ -8874,6 +8874,8 @@
                    }
                    if (width) { /* fudge width (can't fudge elen) */
                        width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen)
+                           width += precis - elen;
                    }
                    is_utf8 = TRUE;
                }


Теперь послать им еще наверное неплохо бы.

Date: 2006-10-08 11:12 pm (UTC)
From: [identity profile] avva.livejournal.com
Не знаю. У меня печатает а + половинку utf8-пары б, т.е. мусор. Возможно, как-то связано с разным поведением utf8 в разных версиях, это довольно новая прагма (особенно ее методы, т.е. utf8::encode).

Date: 2006-10-08 11:43 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет, это не совсем верно. Надо еще кое-что подкрутить.

Date: 2006-10-08 11:50 pm (UTC)
From: [identity profile] avva.livejournal.com
Новая версия. Проходит тесты и не падает даже :)

--- sv.c.old    2006-10-09 01:29:33.000000000 +0200
+++ sv.c        2006-10-09 01:46:15.000000000 +0200
@@ -8873,7 +8873,13 @@
                        precis = p;
                    }
                    if (width) { /* fudge width (can't fudge elen) */
-                       width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen) {
+                           I32 p = precis; /* in bytes now */
+                           sv_pos_b2u(argsv, &p);
+                           width += precis - p;
+                       }
+                       else
+                           width += elen - sv_len_utf8(argsv);
                    }
                    is_utf8 = TRUE;
                }

Date: 2006-10-08 11:55 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Любопытно. У меня честно выпечатывает "абв", сохраняя тем самым семантику работы со строкой. Интересно бы понять, чем такое различие объясняется. Опять вернусь к вопросу о версии перла. У Вас какая?

Date: 2006-10-08 11:58 pm (UTC)
From: [identity profile] avva.livejournal.com
5.8.7 и 5.9.4, поведение одинаково.

Date: 2006-10-09 12:02 am (UTC)
From: [identity profile] ninazino.livejournal.com
Вот интересно. У меня старше -- 5.8.4. Так что же получается, в более поздних версиях что-то сломали?

Date: 2006-10-09 12:52 am (UTC)
From: [identity profile] avva.livejournal.com
Последняя версия, посланная с perlbug. Вызывать sv_pos_b2u все же не надо, у нас уже есть эта информация.
--- sv.c.old    2006-10-09 01:29:33.000000000 +0200
+++ sv.c        2006-10-09 02:40:58.000000000 +0200
@@ -8867,13 +8867,17 @@
            else {
                eptr = SvPVx_const(argsv, elen);
                if (DO_UTF8(argsv)) {
+                   I32 old_precis = precis;
                    if (has_precis && precis < elen) {
                        I32 p = precis;
                        sv_pos_u2b(argsv, &p, 0); /* sticks at end */
                        precis = p;
                    }
                    if (width) { /* fudge width (can't fudge elen) */
-                       width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen)
+                           width += precis - old_precis;
+                       else
+                           width += elen - sv_len_utf8(argsv);
                    }
                    is_utf8 = TRUE;
                }

Date: 2006-10-09 12:54 am (UTC)
From: [identity profile] avva.livejournal.com
Или наоборот, исправили. Ведь мое поведение соответствует документации utf::encode(), хоть оно и показывает мусор. Может быть, в 5.8.4 sprintf неправильно распознает utf8-аргументы... не знаю.

Date: 2006-10-09 01:13 am (UTC)
From: [identity profile] smilga.livejournal.com
У меня куда серьёзней проблема вылезла в субботу. Представьте себе, что нужно прочитать файл, кодированный в utf-8, это, например, open(IN, "<:encoding(utf-8)", $FILE) or die; while(<IN>) { ... }. Если в файле при этом испорченный utf (что в Сети встречается гораздо чаще, чем следовало бы), то 5.8.6-й перл может при чтении ошибочной строки свалиться в бесконечный цикл, печатая всё время utf8 "..." does not map to Unicode. Я пока не разобрался, исправлено ли это в более свежих версиях.

Date: 2006-10-09 01:18 am (UTC)
From: [identity profile] avva.livejournal.com
Какая прелесть!

Видимо, читайте весь файл в память в байтах и переводите через Encode после чтения (или построчно).

Date: 2006-10-09 03:03 am (UTC)
From: [identity profile] serejik.livejournal.com
О господи. :-)) 7 последних лет пишу на перле, всегда обхожусь минимумом доступого синтаксиса, всю обработку строк, в том числе и в разных локалях делаю ручками, как будто в языке ничего кроме побайтовой выборки из строки нет.

Прямо америку открыли для меня, честно. Занятно...
From: [identity profile] aburachil.livejournal.com
А вот скажите, Анатолий, может быть Вы знаете ответ на вопрос, который меня давно занимает --- если я говорю что-то вроде

use charnames ':full';print "\x{0041}\N{WHITE SMILING FACE}"



то получаю на выходе букву А и смайлик, что вполне понятно, но кроме того, пёрл говорит "Wide character in print". Вот вопрос собственно такой --- зачем он это говорит, наверное что-то я не так делаю, но не понимаю, что именно?
From: [identity profile] avva.livejournal.com
Да, надо сделать binmode STDOUT, ":utf8"; чтобы он не боялся посылать юникодные символы в данный поток вывода. С точки зрения самого потока ничего не изменится, те же utf8-байты. Если писать в файл, то можно его сразу открывать чем-то вроде open OUT, ">:utf8", "filename".
From: [identity profile] aburachil.livejournal.com
А понятно, то есть пёрл боится, что консолька не юникодная, несмотря на переменную "LANG", в которой стоит, "что-то-там.UTF8"... А стоит такое поведение тоже рассматривать как ошибку или есть разумные основания именно так себя вести?

Date: 2006-10-09 09:18 am (UTC)
From: [identity profile] avva.livejournal.com
перл не может и не должен знать, что STDOUT ведет на консоль - может вести куда угодно. В принципе мне это кажется разумным компромиссом между внутренней достаточно чистой utf8-имплементацией перла, и необходимостью существовать во внешней среде, в которой до недавнего времени хорошая поддержка utf-8 была редкостью.

может вести куда угодно

Date: 2006-10-09 09:21 am (UTC)
From: [identity profile] aburachil.livejournal.com
Аминь! Действительно ;-)

Date: 2006-10-09 10:35 am (UTC)
From: [identity profile] smilga.livejournal.com
Я, собственно, так и сделал — «но осадок остался».

Date: 2006-10-09 11:00 am (UTC)
From: [identity profile] egorfine.livejournal.com
перл и ютф8 - какая боль, какая боль. :(

Date: 2006-10-09 11:31 am (UTC)
From: [identity profile] avva.livejournal.com
Уже не очень - начиная с 5.8.

Date: 2006-10-09 11:53 am (UTC)
From: [identity profile] egorfine.livejournal.com
я бы сказал что жить можно более-менее начиная с 5.8.6. На 5.8.4 это был сплошной pain.

Date: 2006-10-09 03:45 pm (UTC)
From: [identity profile] smilga.livejournal.com
А вот ещё лучше. Имеется файл (довольно большой) с совершенно на этот раз, извините за выражение, доброформенным ютээфом. В нём словоформы одного известного западнославянского языка, т.е. много характерных пар байт \xC4\x8D, \xC5\xBE и т.п. Открываем файл тем же способом, что описан выше, читаем строки. На этот раз бесконечных циклов, слава богу программистов, никаких нет, но нерегулярно раз в несколько сотен строк цифруха высказывается, что, дескать, кто-то там опять does not map to Unicode. Но несмотря ни на что конечный результат получается совершенно корректный, ни одного знака и ни одной строки не теряется.

Хочется научить перл время от времени писать: фашизм поднимает голову at foo.pl line 14142 или там — поток убит кровавой гебней at bar.pl line 2718. У нас тут любят такие развлечения.

Date: 2006-10-09 03:56 pm (UTC)
From: [identity profile] avva.livejournal.com
А это reproducible? Всегда выдается ошибка в одном и том же месте? Тогда можно изолировать и найти баг.

Date: 2006-10-09 04:19 pm (UTC)
From: [identity profile] smilga.livejournal.com
А, нет. Вся прелесть в том, что это не вызывается никакой конкретной строкой. Скажем, для всего большого файла первые предупреждения вылезают в строках 617, 630 и дальше каких-то. Если выбрать из файла строки с 600-й по 650-ю и пропустить через тот же скрипт, предупреждения не печатаются вовсе. Если выбрать строки с 100-й по 700-ю, печатаются в строках 233 и 534 (т.е., соответственно, 332 и 633 исходного файла); если выбрать со 101-й по 700-ю, то печатается в одной строке 235; если с 200-й по 1700-ю, то в строках 419 и 915; если с 200-й по 1000-ную, то в одной только 419.

Date: 2006-10-09 04:22 pm (UTC)
From: [identity profile] avva.livejournal.com
Тем не менее, если файл один и тот же, предупреждение всегда вылезет в одной и той же строке или не всегда? Если всегда, и вам не лень, пришлите мне пример файла и самый тривиальный пример скрипта, который вызывает предупреждение, и я попробую разобраться.

Date: 2006-10-09 05:46 pm (UTC)
From: (Anonymous)
Простите, милый Авва, что не в дугу, но все же выбрала спросить вас,т.к. вы всегда вежливы и все знаете к тому ж.
Я тут тоже размечталась открыть журнал - но кто дает? В смысле я для начала хочу фрее попробовать, а мне в ответ - сначала заплатите.
А я в кантри три тысячи заплатила и ни разу не пошла.
Может фрее акоунт отменинили? И где взять мануал на все действия - открытие и последующая эксплуатация.
Тут есть ваш мобильник - но это я думаю уже слишком?
сто раз спасибо и извините.
Ирина
i_risha7@hotmail.com

Date: 2006-10-09 05:55 pm (UTC)
From: [identity profile] avva.livejournal.com
https://www.livejournal.com/create.bml

Никто не отменял бесплатные аккаунты, попробуйте еще раз.

http://www.livejournal.com/manage/settings/

здесь вы можете выбрать русский язык просмотра и потом читать FAQ сайта: http://www.livejournal.com/support/faq.bml

Date: 2006-10-10 07:41 am (UTC)
From: [identity profile] pernat1y.livejournal.com
У avva исходники в кодировке utf8, а у ninazino очевидно нет. Отсюда и разница в работе.

Date: 2006-10-10 08:33 am (UTC)
From: [identity profile] avva.livejournal.com
Да, действительно.

Date: 2006-10-11 08:46 am (UTC)
From: [identity profile] smilga.livejournal.com
Если один и тот же, то да, всегда в одних и тех же строках. Т.е. это скорее heisenbug, чем schroedinbug.

Вам точно хочется с этим возиться? Я проверил, в 5.8.8 уже обе блохи исправлены. Иначе бы я и сам стал разбираться.

Date: 2006-10-11 08:57 am (UTC)
From: [identity profile] avva.livejournal.com
А, тогда не надо, естественно.

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
2829 30 31   

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Dec. 31st, 2025 08:27 pm
Powered by Dreamwidth Studios