Релиз PVS-Studio 6.26

    PVS-Studio 6.26

    Обычно мы не пишем заметки про выход новой версии анализатора PVS-Studio. Однако в новый релиз вошло много интересных изменений, касающихся анализа C и C++ кода, о которых хочется рассказать нашим пользователям.

    Скоро Java


    Если честно, самые последние и интересные нововведения в PVS-Studio пока всё ещё скрыты. Я имею в виду поддержку в анализаторе языка Java. Пока ещё нет публичной beta-версии PVS-Studio for Java, но она очень скоро появится. Если есть желание принять участие в её тестировании, то можно написать нам в поддержку (выбрать: Хочу анализатор для Java).

    Новые диагностики для C и C++


    В новой версии мы немного увлеклись и добавили сразу 15 диагностик общего назначения для C и C++ (V1021-V1035). В минорном релизе ещё никогда не добавлялось сразу столько диагностик. Подробнее с каждой из диагностик можно ознакомиться в документации. На мой взгляд, наиболее интересными среди новых диагностик являются:

    • V1026. The variable is incremented in the loop. Undefined behavior will occur in case of signed integer overflow.
    • V1033. Variable is declared as auto in C. Its default type is int.

    Диагностика V1026 создана по мотивам дискуссии на форуме linux.org.ru. Программист жаловался на глюк в компиляторе GCC 8, но, как затем выяснилось, виной всему является некорректный код, приводящий к неопределённому поведению. Давайте рассмотрим этот случай.

    Примечание. В оригинальной дискуссии переменная s имеет тип const char *s. При этом на целевой платформе тип char является беззнаковым. Поэтому для наглядности я сразу написал в примере, что тип указателя — это const unsigned char *.

    int foo(const unsigned char *s)
    {
      int r = 0;
      while(*s) {
        r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1);
        s++;
      }
      return r & 0x7fffffff;
    }

    Компилятор не генерирует код для оператора побитового И (&). Из-за этого функция возвращает отрицательные значения, хотя по задумке программиста этого происходить не должно.

    Разработчик считает, что это глюк в компиляторе. Но на самом деле неправ программист, который написал такой код. Функция работает неправильно из-за того, что в ней возникает неопределённое поведение.

    Компилятор видит, что в переменной r считается некоторая сумма. Переполнения переменной r произойти не должно. Иначе это неопределённое поведение, которое компилятор никак не должен рассматривать и учитывать. Итак, компилятор считает, что раз значение в переменной r после окончания цикла не может быть отрицательным, то операция r & 0x7fffffff для сброса знакового бита является лишней и компилятор просто возвращает из функции значение переменной r.

    Диагностика V1026 как раз и предназначена для выявления подобных ошибок. Чтобы исправить код, достаточно считать хеш, используя для этого беззнаковую переменную. Исправленный вариант кода:

    int foo(const unsigned char *s)
    {
      unsigned r = 0;
      while(*s) {
        r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1);
        s++;
      }
      return (int)(r & 0x7fffffff);
    }

    Теперь давайте рассмотрим другую диагностику V1033. Она интересна тем, что причиной возможных ошибок стало новое ключевое слово auto, появившееся в C++11. Причём виновато не само нововведение языка C++11, а нюансы психологического плана :). Сейчас поясню. Взгляните на этот код:

    float d = 3.14f;
    int i = 1;
    auto sum = d + i;

    Видите в нём ошибку? Подумайте. Вот картинка, чтобы сразу не читать текст дальше.

    Время подумать

    Догадались, что может быть не так? Если нет, вот ещё интересная информация. Переменная sum будет равна 4, а не 4.14. Почему?

    Хз

    Сейчас читатель скажет, что это была нечестная загадка! Всё дело в том, что это не C++, а C.

    Бывает, что в проекте одновременно используется и C++, и старый добрый C. Программист привыкает к использованию auto в C++ и случайно может воспользоваться этим словом в C. Вот только там оно означает совсем другое:

    auto

    Defines a local variable as having a local lifetime. Keyword auto uses the following syntax:

    [auto] data-definition;

    As the local lifetime is the default for local variables, auto keyword is extremely rarely used.

    Получается, что переменная sum имеет тип int, и именно поэтому её значение будет равно 4.

    Единорог смеётся

    Хоть ошибка может показаться экзотичной, на самом деле в проекте, где используется смесь из C и C++ файлов, её сделать очень легко. Соответственно, PVS-Studio при анализе C-файлов предупреждает о подобных подозрительных конструкциях.

    Другие нововведения


    Добавлена возможность проверять проекты для сборочной системы Waf.

    Мы продолжаем развивать анализатор в сторону встроенных систем. В этой версии добавлена поддержка проверки проектов для GNU Arm Embedded Toolchain, Arm Embedded GCC compiler.

    При анализе проектов для Visual C++ компилятора (cl.exe, проекты vcxproj для Visual Studio/Standalone), в отчёте анализатора теперь сохраняется регистр в путях до проверенных файлов. Доработка со стороны выглядит проще, чем является на самом деле. При препроцессировании файлов компилятор cl.exe портит регистр в именах файлов. И приходится в анализаторе восстанавливать их обратно.

    Добавлена возможность использовать pvsconfig файлы с CLMonitor/Standalone на Windows.

    Добавлен режим инкрементального анализа для pvs-studio-analzyer/CMake модуля. PVS-Studio CMake модуль можно теперь использовать на Windows для проектов, использующих компилятор Visual C++ (cl.exe).

    Добавлена поддержка инкрементального анализа для .NET Core/.NET Standard Visual Studio проектов.

    Дополнительные ссылки


    1. PVS-Studio. История версий.
    2. Андрей Карпов. Undefined behavior ближе, чем вы думаете.
    3. Will Dietz, Peng Li, John Regehr, and Vikram Adve. Understanding Integer Overflow in C/C++.
    4. Егор Бредихин. Разработка нового статического анализатора: PVS-Studio Java.

    PVS-Studio

    455,00

    Ищем ошибки в C, C++ и C# на Windows, Linux, macOS

    Поделиться публикацией
    Комментарии 23
      +1
      С релизом! Картинки в посте отменные, особенно единорожий троллфейс.
      Разработчик считает, что это глюк в компиляторе.

      Неужели UBSan настолько непопулярен? Он ведь нашёл бы такое за секунды.
        0
        void main(void){
            float d = 3.14f;
            int i = 1;
            auto sum = d + i;
        }


        pvs.c: In function ‘main’:
        pvs.c:6:6: error: type defaults to ‘int’ in declaration of ‘sum’ [-Werror=implicit-int]
        auto sum = d + i;
        ^~~


        но пасаран!

        // ладно, я тоже слегка потроллил :), там по дефолту ворнинг, но не молча же всё это происходит!
          +7
          Компилятор не читает мои комментарии, в отместку я не читаю его варнинги.
            +1
            если только :)
            «назло маме отморожу уши!»
              +3
              А PVS-Studio читает комментарии)
                +4
                угу, особенно те, где написано «я бедный пакистанский вирус, протестируйте меня бесплатно, нупазязя!!» :)
            0
            У меня возник вопрос: А что говорит PVS-Studio 6.26 о чистоте кода PVS-Studio 6.26?
              +2
              Говорит, что всё чисто. У разработчиков, использующих Visual Studio, включен анализ после сборки. И, естественно, анализатор запускается ночью и рассылает письма счастья тем, кто заложил подозрительный код и тимлидам (см. BlameNotifier).
                +5
                А у разработчиков, использующих Linux, все тоже нормально. Так как статический анализ также интегрирован в рабочий процесс, по коммитамм запускаются проверки и все работает как надо.
                  +5
                  Разработчиков на macOS тоже не обидели :D Там всё работает как в версии для Linux.
              +1
              В очередной раз посмеялся с горе-программистов, которые чуть что винят компилятор (это к дискуссии про V1026).
                0
                Предложите свой продукт robertsspaceindustries.com, разработчикам Star Citizen. Может мы раньше игру получим… эх мечты.
                  –2
                  Компилятор видит, что в переменной r считается некоторая сумма. Переполнения переменной r произойти не должно. Иначе это неопределённое поведение, которое компилятор никак не должен рассматривать и учитывать.
                  Это, к сожалению, распространённое заблуждение, в которое, увы, верят и разработчики трансляторов. Поведение, не определённое в стандарте означает, что ответственность за его определение переносится с создателей стандарта на создателей транслятора. Это совсем не означает, что оно не должно учитываться, но именно так многие считают и распоряжаются своей отвественностью так, как Вы описали.
                  Претензия к компилятору вполне обоснована, но расчёт на то, что тут кто-то кому-то должен был бы наивен.
                  Тем не менее, прочувствовать правильный учёт ошибочной ситуации можно, включив опции -fsanitize=undefined -fsanitize-undefined-trap-on-error.
                    +4
                    Вы путаете undefined behaviour и unspecified. Последнее как раз должно быть определено компилятором (но не обязательно документировано). А при undefined никто ничего никому не должен. Программа, его содержащая более не считается программой на языке С++. Что и используют разработчики компиляторов для проведения жёстких оптимизаций.
                      0
                      Я не путаю. В компиляторе нет неопределённых мест по природе программ на цифровых вычислительных устройствах(с поправкой на точность определения машинного языка). Неопределённость может быть только в спецификации. Вопрос лишь в том, как именно определяется поведение в компиляторе — наиболее разумным способом или абы как ради баллов в очках оптимизации, задано один раз или меняется от версии к версии. Unspecified же поведение как раз-таки определено на уровне стандарта, но подразумевает возможность выбора. В этом и суть названия, которое плохо передаётся на русском, — оно определено, но не специфицировано.
                    0
                    А планируете ли что-нибудь подобное для Python?
                    Настоящий такой, статический анализ?
                    Если нет, то какие сейчас сложности основные есть?
                    Если да, то когда ждать эту прелесть?
                    // естественно, будет максимально круто написать его на питоне :)
                      +2
                      Про Python пока вообще не думали. Нам ещё Java делать и делать, продвигать и продвигать. Возможно потом будет смотреть в сторону PHP или JavaScript.
                        0
                        Подумайте пожалуйста, очень трудно. Сам Гвидо сказал что проверку для рекурсивных типов он не осилил и ему и так хорошо:

                        IIRC Brett and I tried and failed to come up with a recursive definition that worked in mypy
                        и далее:
                        In my own code I use this:
                        JsonDict = Dict[str, Any]
                        which happens to cover perfectly what I'm doing

                        © github.com/python/typing/issues/182

                        Ещё очнь нехватает возможности для произволной строчки в коде узнать какие эксепшены там вообще могут быть. :)
                          0
                          Мне кажется, что это потенциально нерешаемая задача :)
                          Какое-то количество мы можем найти, но гарантировать, что нашла все не можем
                        0
                        Ну на Питоне очень многое из «выстрели себе в ногу на С/С++» в принципе невозможно. Очень маленький простор для статического анализа. Хотя конечно копи-пасты и а != 2 || a != 3 возможны и там…
                        +1
                        А насколько сложно создать версии для Rust на основе уже имеющейся?
                          0

                          версия для rust называется rustc

                            0
                            Спасибо!
                            Планирую изучить Rust, вместе с Vulcan (надеюсь под него есть либа).

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

                        Самое читаемое