История однострочных багов

    Компания Apple недавно допустила крупную ошибку, забыв удалить лишнюю строчку с оператором безусловного перехода goto посередине функции SSLVerifySignedServerKeyExchange для проверки серверной подписи при установке SSL-соединения. В результате, функция успешно завершала работу, независимо от результата проверки подписи.

    Однако, это не первый случай в истории, когда критическая ошибка объясняется единственной строчкой кода. Вот ещё несколько таких примеров.

    X Server


    В 2006 году было обнаружено, что X Server проверяет рутовые права у пользователя, но при этом разработчики в реальности забыли вызвать соответствующую функцию.

    --- hw/xfree86/common/xf86Init.c
    +++ hw/xfree86/common/xf86Init.c
    @@ -1677,7 +1677,7 @@
       }
       if (!strcmp(argv[i], "-configure"))
       {
    -    if (getuid() != 0 && geteuid == 0) {
    +    if (getuid() != 0 && geteuid() == 0) {
            ErrorF("The '-configure' option can only be used by root.\n");
            exit(1);
         }


    Debian OpenSSL


    В 2008 году Debian признала, что генератор псевдослучайных чисел в её «фирменной» реализации OpenSSL от 2006 года на самом деле генерирует предсказуемые числа.

    --- openssl-a/md_rand.c
    +++ openssl-b/md_rand.c
    @@ -271,10 +271,7 @@
                    else
                            MD_Update(&m,&(state[st_idx]),j);
    
    -/*             
    - * Don't add uninitialised data.
                    MD_Update(&m,buf,j);
    -*/
                    MD_Update(&m,(unsigned char *)&(md_c[0]),sizeof(md_c));
                    MD_Final(&m,local_md);
                    md_c[1]++; 

    Причина в том, что важную строчку включили в комментарий. Совершенно непонятно, как такой заметный баг попал в финальный релиз, как будто никто вообще не проверял код. Кстати, многие тогда заподозрили Debian в том, что она «поломала» ГПСЧ не случайно.

    Стандартный OpenSSL


    Ещё один баг в OpenSSL, и опять из 2008 года. OpenSSL 0.9.8i и более ранние версии «некорректно проверяли значение, которое возвращает функция EVP_VerifyFinal, что позволяло злоумышленникам обходить валидацию сертификата с помощью поддельных подписей SSL/TLS для ключей DSA и ECDSA».

     --- lib/libssl/src/ssl/s3_srvr.c
    +++ lib/libssl/src/ssl/s3_srvr.c
    @@ -2009,7 +2009,7 @@ static int ssl3_get_client_certificate(S
     	else
     		{
     		i=ssl_verify_cert_chain(s,sk);
    -		if (!i)
    +		if (i <= 0)
     			{
     			al=ssl_verify_alarm_type(s->verify_result);
     			SSLerr(SSL_F_SSL3_GET_CLIENT_CERTIFICATE,SSL_R_NO_CERTIFICATE_RETURNED); 

    Android


    Посмотрите на патч для memset.c от 11 мая 2010 года.

    --- libc-a/memset.c
    +++ libc-b/memset.c
    @@ -1,6 +1,6 @@
     void *memset(void *_p, unsigned v, unsigned count)
     {
         unsigned char *p = _p;
    -    while(count-- > 0) *p++ = 0;
    +    while(count-- > 0) *p++ = v;
         return _p;
     }

    Вместо корректного параметра в память записывается нуль. То есть один из передаваемых в функцию параметров вообще не используется.

    Tarsnap


    Автор программы Tarsnap, онлайн-сервиса защищённого хранения резервных копий на Amazon S3, в 2011 году сообщил о баге в процедуре генерации случайного значения (nonce) при шифровании данных.

    --- tarsnap-autoconf-1.0.27/lib/crypto/crypto_file.c
    +++ tarsnap-autoconf-1.0.28/lib/crypto/crypto_file.c
    @@ -108,7 +108,7 @@
    
            /* Encrypt the data. */
            if ((stream =
    -           crypto_aesctr_init(&encr_aes->key, encr_aes->nonce)) == NULL)
    +           crypto_aesctr_init(&encr_aes->key, encr_aes->nonce++)) == NULL)
                    goto err0;
            crypto_aesctr_stream(stream, buf, filebuf + CRYPTO_FILE_HLEN, len);
            crypto_aesctr_free(stream);

    Новое случайное значение должно было генерироваться для каждых 16 байт шифруемых данных, но в реальности оно вообще не менялось.

    Так что не только Apple допускает ошибки.
    Support the author
    Share post

    Similar posts

    Comments 90

      +58
        +73
        Не всем думаю будет понятно, да и комит не в самом верху страницы.

        -BUMBLEBEEVERSION=1.4.31
        +BUMBLEBEEVERSION=1.4.32
        
        @@ -348,7 +348,7 @@ case "$DISTRO" in
           ln -s /usr/lib/mesa/ld.so.conf /etc/alternatives/gl_conf
           rm -rf /etc/alternatives/xorg_extra_modules
           rm -rf /etc/alternatives/xorg_extra_modules-bumblebee
        -  rm -rf /usr /lib/nvidia-current/xorg/xorg
        


        Пробел после /usr. Меня тогда пронесло, не успел обновится.
          0
          О да, а я тогда голову сломал чего это у меня система сломалась. И понять не мог, на вид как будто ядро не видит :)
            +1
            ИМХО если бы не было дублирования префикса /usr/lib, вероятность такой опечатки была бы меньше… DRY
              +3
              А мне тогда было лениво обновляться, а потом узнал что крупно повезло.
              0
              я вот всегда искренне неудомеваю — а что в open-source продуктах юнит тесты и функциональные тесты никто принципиально не пишет?
                +1
                Вы спрашиваете, чтобы потроллить или просто чтобы в поисковик не ходить? Каким образом связана открытость/закрытость проекта и его тестирование?
                Вот вам пара open-source продуктов, которые тестируют так, как пожалуй ничто в мире.
                SQLite
                Chromium
              +1
              >>> Android <...> То есть один из передаваемых в функцию параметров вообще не используется.
              Очень странно, что такой баг возник, учитывая, что большинство (если не все) IDE на такие вещи истерично ругаются.
                +3
                Не только IDE, но и компиляторы.
                  +23
                  Описание этого бага выдаёт автора (точнее, что тот ни в зуб ногой в том, о чём пишет).
                  Параметр v и не должен возвращаться, это то значение, которым заполняется память по заданному указателю. Возвращается всё правильно в обоих случаях, баг был в том, что память всегда обнуляется.
                  • UFO just landed and posted this here
                      +4
                      Например, «замемсеттить» RGB24 буфер значением 255, чтобы получить белую канву. Но это я так, к слову — обычно канва нужна чёрная :)
                        0
                        В релизе — да, а для отладки забивают 0xCC или 0xDEADBEEF или еще чем-то в этом роде. Так что этот баг слегка затруднял отладку. Промахнулся на один уровень.
                        • UFO just landed and posted this here
                            0
                            Это был просто пример того, что не всегда пишут memset 0, иначе можно ыбло не объявлять bzero устаревшей. Для большинстве платфор никак нельзя memset'ами писать шаблоном шире одного байта, на сколько я знаю. Но на Mac OS X есть в libc функции memset_pattern[4,8,16], относящиеся к семейству memset.
                            • UFO just landed and posted this here
                        0
                        Тоже хотел написать про это.

                        Но раз уж Вы опередили, то вот другой вопрос — неужели memset там реализован через цикл? Небольшое гугление говорит, что

                        The underlying implementation might be in assembler specific to that architecture to take advantage of whatever its native properties are. It might also have internal special cases to handle the case of zeroing (versus setting some other value).
                        • UFO just landed and posted this here
                      +2
                      Потому что vim. В исходниках Android очень много такой фигни.
                        0
                        Использование vim какбэ не отменяет GCC-флаги -Wall -Wextra -Werror. Если человеку наплевать на предупреждения компилятора, то ему и на IDE будет наплевать.
                          –1
                          Работает, только если при компиляции у вас не генерится еще 1000 'unused parameter' warning-ов. Достаточно одного человека в команде, который допускает в коде предупреждения, чтобы проанализировать лог компилятора стало сложно.
                            +2
                            Если у вас есть такой человек, то vim и warning'и — это ещё не самая большая из ваших бед. Устранять надо корень проблемы, а не следствия.
                              0
                              К сожалению, в больших проектах такое бывает. Человек может банально собирать в другой конфигурации, в которой переменные используются.
                                0
                                __attribute__((__unused__))?
                                  0
                                  Для этого надо хотя бы знать, что переменная не используется.

                                  если есть код

                                  int x = bar();
                                  #ifdef EXT_LOG
                                  ext_log(x);
                                  #endif
                                  


                                  И вы всегда собираете с EXT_LOG defined, а ваш товарищ — всегда с EXT_LOG undefined, то тут и возникает проблема.
                                    0
                                    #ifndef EXT_LOG
                                    __attribute__((__unused__)
                                    #endif
                                    int x = bar();

                                    ?

                                    Хотя тогда уже проще само существование переменной x поставить в зависимость от EXT_LOG.

                                    Наконец, если у вас действительно тысячелетний проект и вокруг все программисты — старцы с воооот такими бородами (бывает), то приведите в порядок тот исходник, над которым работаете, а остальные оставьте пока.
                                      0
                                      Да вы поймите, что товарищ, который код правит — не получает предупреждения и потому не замечает ошибки. А вы тоже не хотите лезть со своими правками в чужой код.
                                        0
                                        Практика code review у вас не распространена? Кроме того, можно попросить коллегу внести эти изменения в код, потому что это упростит вам работу и потенциально позволит избежать проблем в релизе. И вообще, не вижу ничего плохого в том, чтобы предложить другому человеку патч, тем более столь тривиальный и лаконичный.
                                0
                                С этими словами он бросил в сумку АКСУ и несколько магазинов к нему, после чего последовал к выходу.
                              –4
                              Регулярно собирая open source софт из исходников, могу сказать, что ВЕСЬ софт написанный в vim просто пестрит ворнингами.

                              В Android штука интересная в том, что большинство кода написано на Java, а компилятор не линтует код. При этом любая IDE для Java гоняет линт автоматом и сразу бьёт по рукам. Не сильно, но надоедливо. Люди, привыкшие к консоли линт не юзают в принципе, а потом их код зарастает тоннами навоза.

                              IDE очень сильно дисциплинирует и пресекает множество мелких косяков на корню. За использование текстовых редакторов в программировании надо выгонять из профессии. Это вам не посты в бложики писать.
                                +7
                                Уважаемый, да вам с таким подходом в Госдуму надо идти вырабатывать требования к профессиональным программистам. Серьёзно, я даже не знаю, что тут ещё можно сказать

                                Если некто не умеет пользоваться неким инструментарием, это ещё не повод навязывать всем окружающим своё мнение. Казалось бы, это банальность, да?
                                  0
                                  Понимаете, если плотник не умеет пользоваться молотком, то это плохой плотник. А если программист не умеет пользоваться своими инструментами, то это уже личное мнение? Вы уж извините, но вы бред пишите.
                                    +7
                                    Плохая аналогия подобна котёнку с дверцей. Это раз.

                                    Если ваш плотник достигнет феноменальной выработки при минимальных затратах, забивая гвозди микроскопом, то последнее, что вы должны делать — отбирать у него микроскоп и совать ему свой проклятый хлипкий молоток с зазубринами и неудобной ручкой. Оценивайте результаты, а не инструменты, чтобы не выяснилось как-нибудь, что вы ненароком половину разработчиков ядра Linux из профессии выгнали. Это два.
                                      –10
                                      Работа над ядром Linux не такая уж и феноменальная работа. Мелкий патчик и в ноутпаде наклепать можно, и в общей архитектуре ядра для этого разбираться не надо.
                                        +5
                                        То есть, на ваш взгляд, работа над ядром сводится к наклёпыванию мелких патчиков?
                                  +5
                                  >Регулярно собирая open source софт из исходников, могу сказать, что ВЕСЬ >софт написанный в vim просто пестрит ворнингами.

                                  Я вот специально скачал последнюю версию GCC из транка и собрал без ключа --disable-bootstrap. Предупреждения:
                                  1) format not a string literal and no format arguments
                                  2) unknown conversion type character ‘X’ in format
                                  3) too many arguments for format

                                  Все они -Wformat* и не ломают бутстрап, ибо признаны некритичными. Однако стоит объявить переменную и не использовать ее, сразу же ошибка бутстрапа.

                                  Так ли вы уверены, что код, написанный без IDE плох?

                                  >IDE очень сильно дисциплинирует и пресекает множество мелких косяков на корню

                                  Посоветуете IDE, которая понимает ВЕСЬ GCC, со всей магией?
                                    –6
                                    К сожалению, я очень давно не пишу на C/C++ и ничего вам посоветовать не могу.
                                    +1
                                    Вот вам код из статьи в Vim с плагином YouCompleteMe.
                                    Скрытый текст

                                      0
                                      Люди, привыкшие к консоли линт не юзают в принципе

                                      Из консоли — это "javac *"? Мсье знает толк в несвежих извращениях.

                                      любая IDE для Java гоняет линт автоматом

                                      1) Не любая.
                                      2) Инспекции для начала надо бы и настроить.
                                      Так что не панацея.
                                        +1
                                        Странно. Пишу код в Vim. И этот самый Vim выдает мне и ворнинги, и ошибки и все-все-все. Комплита нет, да, потому что не настраивал, ибо не люблю.
                                        UPD. Да, gdb я к нему тоже прикручивал ради фана.
                                  –4
                                  >Debian OpenSSL
                                  При использовании нормального IDE это невозможно
                                  А вот поделки типа IWARM — там еще и не такой хрени понаделаешь
                                    +1
                                    И что же нормальная IDE тут сделает-то?
                                      +5
                                      высветит строку как коммент
                                        +5
                                        Насколько я понял, строка была закоментирована намеренно, по причине "Don't add uninitialised data."
                                          –2
                                          … во время отладки, а в релизе забыл раскомментить, потому что смотрелось ок

                                          хотя кто знает
                                            +2
                                            Вообще-то закомментировали намеренно, т.к. кому-то пришла в голову (или родилась на совещании в АНБ) гениальная мысль пройтись по исходникам профилировщиком valgrind, который высветил «использование не инициализированных данных», которое было бы ошибкой в любом проекте, кроме ГПСЧ, где это внезапно оказалось фичей.
                                              +5
                                              Да, и был большой срач между дебиановцами и openssl'щиками. Потому что собирать энтропию из неинциализированной памяти — та ещё эвристика.
                                                +3
                                                Там эта переменная частично инициализирована. Они берут большой буфер и начинают класть в него всё подряд: информацию о процессах, что-то из /dev/random, etc. Но до конца буфер так и не заполняют, в результате чего часть буфера так и остаётся неинициализированной. После чего весь буфер скармливается в ГПСЧ.

                                                Так что неинициализированные данные — это далеко не основной источник энтропии для ГПСЧ — но основной источник ругани valgrind'а. Уж сколько раз твердили миру, что все автоматические тулы — это лишь помощники, для интерпретации их сообщений голова нужна.
                                                  –1
                                                  А что мешало им этот буффер занулить и складывать туда всё остальное? Срач-то был по поводу того, что «не-иницализация буффера» — это дурная-дурная практика.
                                                –1
                                                Вообще-то эта строка вообще является undefined behaviour, gcc и clang могут такую строку вовсе выкинуть из исходников, перенести в другое место с нарушением сайд эффектов и натворить прочие радости.
                                      +34
                                      Я целую минуту всматривался в строчку
                                      if (getuid() != 0 && geteuid() == 0)
                                      пытаясь понять ее смысл. Потом заметил.
                                        +12
                                        Вот что значит неудачный идентификатор…
                                          –1
                                          при подсветке в IDE, обычно видна разница между функцией и переменной.
                                          что не умаляет не смешивать имена оных.
                                            +10
                                            Да нет. Тут беда в том, что имена функций различаются в одну букву. Я тоже тупил секунд 10. А не знал бы — и не заметил бы.
                                              0
                                              эм. а разве разница не в том, что после «geteuid» не было скобок?
                                                +2
                                                Ну это как-бы явный баг, описанный в посте.
                                                В общем проблема в другом: getuid() и geteuid() не мудрено попутать в любом месте кода, и IDE ничем не поможет, так как функции однотипные. И тупить можно не один час, вылавливая такой баг.
                                                +2
                                                Для любого юниксоида разница между euid и uid понятна, и путать их — всё равно, что путать slip() и sleep().
                                                  +8
                                                  get_uid()/get_euid(), getUid()/getEuid() в зависимости от принятых стандартов, и ещё масса вариантов.
                                                  Дело не в разнице или её знании/не знаии, дело просто в зрительном восприятии.
                                                    +1
                                                    Ну так функций, которые на одну букву отличаются в названии очень много (vnsprintf vs vnprintf). Это вполне стандартная практика в Си, и с учётом того внимания, которое надо проявлять при работе с памятью, внимание к имени функции просто неощутимо.
                                                      +4
                                                      Стандартная-то она может и стандартная, но она ужасна.
                                          +9
                                          Минимум 2 из 5 ошибок были бы найдены самым простым статическим анализатором кода.
                                            +8
                                            У меня тут таких ляпов.... Другое дело, что это просто ошибки, а не известные epic fail.
                                            Используйте варнинги компилятора и статический анализ. Часто помогает.
                                              +1
                                              Писать с упором на MISRA-C, собирать с -Wall -Wextra -Wpedantic — да, некоторые так делают (и даже больше).
                                              +2
                                                +2
                                                Баг у xserver вообще некритичный, ни разу. Более того, эта проверка ещё и не нужна, ибо лучше не проверять на рутовость, а просто обламываться там, где не хватает прав, желательно с адекватной диагностикой. Это гораздо более гибкий подход, ведь иногда пользователь может входить в нужную группу/обладать достаточными привилегиями, но при этом EUID не будет root'овым.
                                                  –1
                                                  Этот код не имеет отношения к проверке прав. Это — защита на тот случай, если админ поставил программе флаг setuid. Смысл защиты в том, что в режиме setuid программа может работать так, как была настроена — но не может быть перенастроена заного. Лично я не понимаю, зачем вообще так делать — но, видимо, юзкейсы были.
                                                    +1
                                                    О чём я и говорю — при -configure сервер запишет новый конфиг-файл. Проверка почему-то предполагает, что не-root-пользователь этого сделать не должен, но, что если именно этот пользователь является владельцем конфиг-файла, и, соответственно, по-логике вещей, должен был бы суметь воспользоваться этой опцией. Типичный случай, когда assumption is the mother of all f*ckups.
                                                      –1
                                                      Вглядитесь еще раз в эту строчку: if (getuid() != 0 && geteuid() == 0) {
                                                      Если админ не выставлял программе флага setuid — любой пользователь без проблем настроит свой личный конфиг.
                                                        +1
                                                        Что ж так упорно понимать не хотите?

                                                        www.wikihow.com/Configure-X11-in-Linux:
                                                        A new file has been created in /etc/X11/ called xorg.conf


                                                        + man:
                                                        -configure
                                                        When this option is specified, the Xorg server loads all video driver modules, probes for available hardware, and writes out an initial xorg.conf(5) file based on what was detected. This option currently has some problems on some platforms, but in most cases it is a good way to bootstrap the configuration process. This option is only available when the server is run as root (i.e, with real-uid 0).
                                                        Суть моего коммента в том, что пользователи могут что-то вполне административно-легально (права-членства в группах, или собственность) делать вещи, которые обычно может делать root. Данная проверка этой возможности его лишает. Именно поэтому нужно «… не проверять на рутовость, а просто обламываться там, где не хватает прав, желательно с адекватной диагностикой. …».
                                                          –2
                                                          Данная проверка — не лишает! Что же вы вчитаться в булево условие не желаете так упорно?..

                                                          PS и да, хотелось бы посмотреть на пользователя, имеющего права на загрузку драйверов…
                                                            +1
                                                            То есть выдержка из man'а, где просто по-английски написано, что -configure можно выполнить только будучи root'ом, не смущает. Я даже курсивом выделил.

                                                            > PS и да, хотелось бы посмотреть на пользователя, имеющего права на загрузку драйверов…

                                                            Наслаждайтесь, чё…
                                                              –1
                                                              Меня смущает не выдержка из мана, а тот факт, что обвиняют совершенно постороннюю проверку.

                                                              PS спасибо.
                                                                0
                                                                Пожалуйста. А теперь попробуйте подумать логически о том, что я выше написал.

                                                                -configure даёт возможность получить текстовое представление рабочего конфига сервера. Автоматически. root-пользователь просто запускает с этим ключом сервер, тот сохраняет рабочий конфиг.

                                                                Проверка «если root не настоящий» (а только SETUID'ный) -configure делать запрещает
                                                                ErrorF("The '-configure' option can only be used by root.\n");

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

                                                                А теперь — что если Вася Пупкин как раз и есть владелец /etc/X11/xorg.conf? Почему Вася, не должен смочь записать новый конфиг?
                                                                  –1
                                                                  Потому что для этого нужна более сложная проверка. Зачем тратить силы на те юзкейсы, которыми никто не пользуется?

                                                                  Васе же Пупкину никто не запрещает сделать так (если у него есть права грузить драйвера — то и куда исполнимый файл записать найдется):
                                                                  cp /path/to/Xorg ./Xorg
                                                                  chmod +x ./Xorg
                                                                  ./Xorg -configure
                                                                  


                                                                  И еще есть тот вариант, в котором рут снимает флаг setuid с файла Xorg — и Васе Пупкину ничего не надо никуда копировать.
                                                                    +1
                                                                    Потому что для этого нужна более сложная проверка. Зачем тратить силы на те юзкейсы, которыми никто не пользуется?

                                                                    Во-1-х, потому что идеологически правильнее проверять должна операционная система. Например, из-за появления тех же capabilites в Linux, кучу таких проверок пришлось найти, и выбросить…
                                                                    Во-2-х, «никто не пользуется» — да-да. Расскажите UNIX-администраторам, что они не должны больше пользоваться стандартной моделью прав доступа, а должны каждому Васе сразу выдавать права root.

                                                                    если у него есть права грузить драйвера — то и куда исполнимый файл записать найдется):

                                                                    Если SETUID на данной платформе был реально необходим для штатной работы X-сервера, то после копирования, файл X превратится в тыкву.

                                                                    (Думаю теперь можно поставить точку в этом диалоге.)
                                                                      –1
                                                                      Вы сами себе противоречите. Все-таки, есть у Васи Пупкина необходимые права — или нет?

                                                                      Во-1-х, потому что идеологически правильнее проверять должна операционная система. Например, из-за появления тех же capabilites в Linux, кучу таких проверок пришлось найти, и выбросить…
                                                                      Скажите мне, КАК должна операционная система проверять права Васи Пупкина, если программа работает под euid 0? Проблема именно в том, что setuid root выключает все проверки со стороны операционной системы — и прикладная программа должна проводить их самостоятельно.

                                                                      Конечно же, можно вместо сообщения об ошибке написать следующее:
                                                                      if (getuid() != 0 && geteuid() == 0)
                                                                          setuid(getuid());
                                                                      

                                                                      Это вернет проверку прав на уровень ОС — но такое действие эквивалентно флага setuid у файла. То есть возвращаемся к варианту
                                                                      Если SETUID на данной платформе был реально необходим для штатной работы X-сервера, то после копирования, файл X превратится в тыкву.


                                                                      PS хорошие админы UNIX давно уже усвоили, что setuid — зло. Поэтому хорошим админам эта проверка совсем не мешает.

                                                                      (Думаю, да, теперь можно и точку поставить)
                                                                        0
                                                                        Скажите мне, КАК должна операционная система проверять права Васи Пупкина, если программа работает под euid 0?

                                                                        В UNIX это называется Saved UID. На этом позволю себе таки окончательно закончить сеанс вашего бесплатного образования.
                                                  +11
                                                  Автор, а почему вы не указываете что это перевод?
                                                    +8
                                                    Потому что он никогда не указывает этого.
                                                    0
                                                    Забавно, как раз сегодня отловил такой баг:
                                                    $a = true;
                                                    $b = false;
                                                    $c = $a and $b;
                                                    var_dump($c); // bool (true)
                                                      0
                                                      Прошу прощения за мое невежество, но это на каком языке?
                                                        0
                                                        PHP. Дополнение:
                                                        $a = true;
                                                        $b = false;
                                                        $c = $a and $b;
                                                        $d = $a && $b;
                                                        var_dump($c); // bool (true)
                                                        var_dump($d); // bool (false)
                                                        
                                                        0
                                                        В чем баг?
                                                          +2
                                                          Это не баг. Это, немного неожиданное для рядового программиста, нормальное поведение.

                                                          $c = $a and $b;

                                                          читается как

                                                          ($c = $a) and $b;

                                                          ua1.php.net/operators.precedence
                                                            +11
                                                            А я-то думал, чего все так не любят PHP?
                                                              +1
                                                              Вообще это фича Perl.
                                                                0
                                                                Да и Perl, кажется, не все любят :)
                                                              0
                                                              Это, немного неожиданное для рядового программиста, нормальное поведение.

                                                              — “Normal everyday psychosis” © Bruce Almighty
                                                                0
                                                                Да, в документации описано. Но все же…
                                                                0
                                                                (уже написали)

                                                              Only users with full accounts can post comments. Log in, please.