перл и три бага
Oct. 18th, 2003 06:19 pmСегодня один из классических багов Перла привёл к тому, что в только что созданные журналы/коммьюнити невозможно было отправить записи, в течение 5 часов примерно. То есть баг не самого перла как языка, а типичный баг в программах на нём: в данном случае, путаница между 0 и undef.
Однако, несмотря на такие баги, отдельное значение undef в Перле себя оправдало, по-моему.
Другой типичный баг, который время от времени всплывает в исходниках ЖЖ, связан с тем фундаментальным фактом, что в Перле нет отдельных типов строк и чисел; скаляр в Перле может содержать целое число, или действительное число, или строку, и переводить одно в другое в зависимости от контекста. Но это значит, в частности, что проверка if("0") не проходит, т.к. "0" это то же самое, что 0, т.е. false. Для строк, возможно, удобнее было бы иметь другой критерий истинности, при котором любая непустая строка истинна, а пустая - ложна, но не получается. Из-за забывания этого иногда получаются баги.
Третий баг возникает как раз вследствие единственного нарушения вышеописанного принципа. Оператор & (двоичный and) действует по-разному в зависимости от того, что расположено внутри скаляров, на которые он действует: числа или строки.
Почему результаты разные? Потому что & действует на двоичное представление скаляра, не пытаясь привести его к канонической форме (которая в данном случае должна быть численной, конечно). Когда выполняется 12 & 5, выходит двоичный and между 1100 и 0101; результат - 0100, 4. Когда же выполняется "12" & "5", происходит двоичный and между ASCII-представлением строки "12", т.е.
00110001 00110010 , и ASCII-представлением строки "5", т.е. 00110101 . В результате выходит то же, самое, что "1" & "5", т.к. второму байту строки "12" ничего не соответствует в строке "5"; а
"1" & "5" выходит 00110001, т.е. "1", что корректно преобразуется при надобности в 1.
Таким образом, "x" & "y" и x & y дают одинаковый результат, если x и y - цифры от 0 до 9 (это выходит благодаря тому, что ASCII-представления цифр 0..9 начинаются с 48, т.е. совпадают с самими цифрами в четырёх последних двоичных регистрах), но если числа выходят за предел 0..9, результаты выходят разными.
Особенно больно это бьёт по разработчику, если он использует & для вычисления двоичной маски секьюрити, скажем, в целях обеспечения привилегированного доступа к какому-то объекту. Если при этом его числа, с которыми он делает &, на самом деле не числа, а в данный момент строки (например, потому, что их вернул модуль доступа к БД, всегда возвращающий строки, или Перл их внутри перевёл в строки для какой-то операции, того же print), то результат операции будет неверен, и легко может случиться так, что непривилегированный юзер получит доступ к объекту (всё это касается и двоичного or, кстати, т.е. оператора |, но он используется намного реже на практике, так что редко случаются баги с ним).
Четвёртый... пусть будет на баг, а kludge довольно забавный внутри Перла.
Цитируя документацию функции ioctl:
То же верно ещё для нескольких функций, являющихся перловскими обложками для системных функций: они возвращают обычно результат системной функции или undef в случае ошибки, но, если системная функция возвращает 0 и это для неё не ошибка, они возвращают "0 but true", таким образом пытаясь предотвратить второй баг, описанный выше: если неосторожный разработчик напишет
if(ioctl(...)), то это сработает, т.к. "0 but true" истинно, в отличие от "0"; а если нужно перевести в числовой контекст, то "0 but true" переведётся в 0:
Но если мы запускаем perl с опцией -w (warnings), то обычно перевод строки с не-числовым "мусором" в число выдаёт предупреждение:
А со строкой "0 but true" это не происходит, как и обещано в документации выше:
И это верно только для этой строки. На уровне исходников Перла это происходит так. В файле sv.c, имплементирующем операции со скалярами (sv=scalar value), есть функция looks_like_number(). Её вызывают, когда нужно определить, можно ли перевести данную строку в число; она смотрит на строку, и возвращает ноль, если эта строка не представляет из себя число, и ненулевое значение в обратном случае, причём тогда она возвращает значение, указывающее на то, какую функцию надо использовать для перевода данной строки в число (atol() для целых чисел, atof() для действительных). И вот, в этой функции, когда она уже проверила, выглядит ли строка как целое или действительное число, и вернула соответствующие значения в таких случаях, и уже совсем готова возвращать 0, указывая на неудачу, там стоит:
Классический пример того, что по-английски называют kludge, по-моему.
Однако, несмотря на такие баги, отдельное значение undef в Перле себя оправдало, по-моему.
Другой типичный баг, который время от времени всплывает в исходниках ЖЖ, связан с тем фундаментальным фактом, что в Перле нет отдельных типов строк и чисел; скаляр в Перле может содержать целое число, или действительное число, или строку, и переводить одно в другое в зависимости от контекста. Но это значит, в частности, что проверка if("0") не проходит, т.к. "0" это то же самое, что 0, т.е. false. Для строк, возможно, удобнее было бы иметь другой критерий истинности, при котором любая непустая строка истинна, а пустая - ложна, но не получается. Из-за забывания этого иногда получаются баги.
Третий баг возникает как раз вследствие единственного нарушения вышеописанного принципа. Оператор & (двоичный and) действует по-разному в зависимости от того, что расположено внутри скаляров, на которые он действует: числа или строки.
$ perl -e 'print 12 & 5;' 4 $ perl -e 'print "12" & "5";' 1
Почему результаты разные? Потому что & действует на двоичное представление скаляра, не пытаясь привести его к канонической форме (которая в данном случае должна быть численной, конечно). Когда выполняется 12 & 5, выходит двоичный and между 1100 и 0101; результат - 0100, 4. Когда же выполняется "12" & "5", происходит двоичный and между ASCII-представлением строки "12", т.е.
00110001 00110010 , и ASCII-представлением строки "5", т.е. 00110101 . В результате выходит то же, самое, что "1" & "5", т.к. второму байту строки "12" ничего не соответствует в строке "5"; а
"1" & "5" выходит 00110001, т.е. "1", что корректно преобразуется при надобности в 1.
Таким образом, "x" & "y" и x & y дают одинаковый результат, если x и y - цифры от 0 до 9 (это выходит благодаря тому, что ASCII-представления цифр 0..9 начинаются с 48, т.е. совпадают с самими цифрами в четырёх последних двоичных регистрах), но если числа выходят за предел 0..9, результаты выходят разными.
Особенно больно это бьёт по разработчику, если он использует & для вычисления двоичной маски секьюрити, скажем, в целях обеспечения привилегированного доступа к какому-то объекту. Если при этом его числа, с которыми он делает &, на самом деле не числа, а в данный момент строки (например, потому, что их вернул модуль доступа к БД, всегда возвращающий строки, или Перл их внутри перевёл в строки для какой-то операции, того же print), то результат операции будет неверен, и легко может случиться так, что непривилегированный юзер получит доступ к объекту (всё это касается и двоичного or, кстати, т.е. оператора |, но он используется намного реже на практике, так что редко случаются баги с ним).
Четвёртый... пусть будет на баг, а kludge довольно забавный внутри Перла.
Цитируя документацию функции ioctl:
The return value of "ioctl" (and "fcntl") is as follows:
if OS returns: then Perl returns:
-1 undefined value
0 string "0 but true"
anything else that number
Thus Perl returns true on success and false on failure, yet you
can still easily determine the actual value returned by the
operating system:
$retval = ioctl(...) || -1;
printf "System returned %d\n", $retval;
The special string "0 but true" is exempt from -w complaints
about improper numeric conversions.
То же верно ещё для нескольких функций, являющихся перловскими обложками для системных функций: они возвращают обычно результат системной функции или undef в случае ошибки, но, если системная функция возвращает 0 и это для неё не ошибка, они возвращают "0 but true", таким образом пытаясь предотвратить второй баг, описанный выше: если неосторожный разработчик напишет
if(ioctl(...)), то это сработает, т.к. "0 but true" истинно, в отличие от "0"; а если нужно перевести в числовой контекст, то "0 but true" переведётся в 0:
$ perl -e 'print "0 but true" + 5;' 5
Но если мы запускаем perl с опцией -w (warnings), то обычно перевод строки с не-числовым "мусором" в число выдаёт предупреждение:
$ perl -w -e 'print "12garbage" + 5;' Argument "12garbage" isn't numeric in addition (+) at -e line 1. 17
А со строкой "0 but true" это не происходит, как и обещано в документации выше:
$ perl -w -e 'print "0 but true" + 5;' 5
И это верно только для этой строки. На уровне исходников Перла это происходит так. В файле sv.c, имплементирующем операции со скалярами (sv=scalar value), есть функция looks_like_number(). Её вызывают, когда нужно определить, можно ли перевести данную строку в число; она смотрит на строку, и возвращает ноль, если эта строка не представляет из себя число, и ненулевое значение в обратном случае, причём тогда она возвращает значение, указывающее на то, какую функцию надо использовать для перевода данной строки в число (atol() для целых чисел, atof() для действительных). И вот, в этой функции, когда она уже проверила, выглядит ли строка как целое или действительное число, и вернула соответствующие значения в таких случаях, и уже совсем готова возвращать 0, указывая на неудачу, там стоит:
if (len == 10 && memEQ(sbegin, "0 but true", 10))
return IS_NUMBER_TO_INT_BY_ATOL;
return 0;
Классический пример того, что по-английски называют kludge, по-моему.
no subject
Date: 2003-10-18 10:00 am (UTC)00110001 00110010 , и ASCII-представлением строки "5", т.е. 00110101 . В результате выходит то же, самое, что "1" & "5", т.к. второму байту строки "12" ничего не соответствует в строке "5"; а
"1" & "5" выходит 00110001, т.е. "1", что корректно преобразуется при надобности в 1.
Это не является ли одним из примеров того что Perl -- просто плохо продуманный язык? Я помню здесь раньше был пример того что случайный break внутри под-процедуты может вдруг "найти" какой-то цикл в вызываящих процедурах и сработать как non-local goto из того цикла. Это-же бред! Не понимаю как кто-то может изпользовать этот hack (Perl) для мало-мальски серьёзных систем. (Про необязательность декларации переменных я не говорю, хотя это похоже скорее на BASIC чем на серьёзный язык).
no subject
Я вообще не понимаю
Date: 2003-10-18 10:04 am (UTC)no subject
Date: 2003-10-18 10:06 am (UTC)no subject
Date: 2003-10-18 10:14 am (UTC)$count_plus_two = $input{'field1'}+2;
было именно так просто как написано, а не требовало чего-то вроде atoi со всеми сопутствующими проблемами. Соответственно, числа и строки делаются автоматически конвертируемыми друг в друга, а дальше уже вырастает весь букет.
Ещё интересно: "0 but true" на самом деле не столь уж уникален. Например, выражение "1 but true" + 2 равно 3, а "13 and a candle" + "42 is the answer" - естественно, 55. Т.е. "0 but true" - в цифровом контексте просто 0.
no subject
Date: 2003-10-18 10:14 am (UTC)no subject
Date: 2003-10-18 10:14 am (UTC)no subject
Date: 2003-10-18 10:14 am (UTC)Есть куча людей, которые его хорошо знают и могут очень эффективно пистаь на нём скрипты для обработки текстов, например.
Конечно сейчас есть альтернативы, и я, когда мне нужно сделать что-нибудь перловское, делаю это на питоне, но перл от этого не перестал быть нужен тем, кто его по-настоящему хорошо знает и любит.
no subject
Date: 2003-10-18 10:16 am (UTC)Это, впрочем, довольно логично - знающих Perl стало довольно много, а писать на нём мелкие вещи значительно проще, чем на C[++].
no subject
Date: 2003-10-18 10:19 am (UTC)Ну да, это просто следствие того, что всё равно вызывается atol() для перевода в числовой контекст, а atol() переводит столько цифр, сколько сможет, пока не встречает незаконный для числа символ. Уникальность "0 but true" не в этом, а в том, что только эта строка не выдаёт warning в режиме -w, при таком переводе.
no subject
Date: 2003-10-18 10:23 am (UTC)В конце концов, в C ведь тоже if(ioctl(...)) было бы багом, т.к. ioctl() может вернуть 0 в случае успеха. В C делать нечего, приходится писать что-то вроде if(ioctl(...)!=-1) , а в Перле разработчики встроенной библиотеки хотели упростить это для программиста на Перле, но зашли слишком далеко в этом своём стремлении.
no subject
Date: 2003-10-18 10:30 am (UTC)no subject
Date: 2003-10-18 10:56 am (UTC)no subject
Date: 2003-10-18 11:03 am (UTC)Ошибка была не в случайном break'е, а в случайном next'е, которого в тех процедурах (после того, как из них убрали цикл) не должно было там быть. Другое дело, что такие вещи надо было отлавливать на этапе компиляции/интерпретации.
На самом деле Перл - офигительно мощный язык, но с очень нетрадиционной логикой. Каждый раз, когда мне надо написать что-то на Перле, у меня уходит 30-60 минут на переключение мозгов, а потом 5 минут на собственно написание.
no subject
Date: 2003-10-18 11:38 am (UTC)Нет. Перл - хорошо продуманный язык, который сознательно идёт на множество "некрасивых" и неэффективных трюков внутри имплементации runtime-среды ради удобства программиста, которое ставится на первое место. Mutability чисел в строки и обратно - исключительно удобное для программиста свойство языка, и хорошо продуманное к тому же; отдельные недочёты, типа этой проблемы с &, этого факта не меняют. В любом языке есть свои недочёты. А проблему с & обещают исправить в следующей версии, кстати.
no subject
Date: 2003-10-18 12:15 pm (UTC)Для строк, возможно, удобнее было бы иметь другой критерий истинности, при котором любая непустая строка истинна, а пустая - ложна
if (strlen($s))
А все остальное - мне просто непонятно, как можно путать undef/0, ==/eq и т.п. Там же под этим лежит ровно одно правило приведения строки к числу, которое надо просто один раз понять и запомнить
no subject
Date: 2003-10-18 01:54 pm (UTC)В C всё-таки надо писать if( ioctl(...) == 0 ) - потому как при позитивной валидации, нужно искать позитивный результат, а не отстутсвие отрицательного. Впрочем POSIX ввел стандарт 0=success по той простой причине, что логичней было бы:
if( ioctrl(...) ) {
error handling
}
и так получается короче, чем if(ioctl(...) != 0)...
А perl, при всём моём к нему уважении, один из тех языков, которые соблазняют на спагетти. Впрочем плохой код, при желании, можно написать на любом языке:)
no subject
Date: 2003-10-18 02:04 pm (UTC)no subject
Date: 2003-10-18 02:05 pm (UTC)no subject
Date: 2003-10-18 02:09 pm (UTC)On success, zero is returned. On error, -1 is returned,
and errno is set appropriately.
Впрочем действительно - с некоторыми устройствами, возвращаться будет совсем другое значение. Я как бы имел ввиду основную конвенцию 0=success и её исторические предпосылки. Не ругайтесь:)
no subject
Date: 2003-10-18 02:36 pm (UTC)no subject
Date: 2003-10-18 02:40 pm (UTC)Вы запостили эти слова, используя код на Перле, который находится в активном девелопменте больше трёх лет, и поддерживается далеко не только его автором (а ещё и мной, например).
no subject
Date: 2003-10-18 02:49 pm (UTC)Это же анафема! Вот я и говорю что это очень большая ошибка допускать в индустрию язык, который ставит на первое место "удобства" программиста. Нужны системы которые на 1ое место ставят читабельность и поддерживаемость кода. Пусть програмист потратит в 2 раза больше времени пиша нечто чтобы другой человек смог на много быстрее разобраться что к чему и, очень важно, был уверен что меняя нечто он не ломает какой-то "хитрый трюк". К сожалению, в индустрии слишком много "хэкеров", готовых изощряться до умопомрачения "для совего удобства". Если ещё и язык этому способствует, то получается тихий ужас.
no subject
Date: 2003-10-18 02:54 pm (UTC)no subject
Date: 2003-10-18 03:06 pm (UTC)Из того что я понимаю, ошибки приведённые выше (и другие, с тем break или next) -- результат плохой семантики языка, позволяющей делать такие "глупости".