Интеграция PVS-Studio в PlatformIO

    Picture 5

    Недавно в среде разработки встраиваемых систем PlatformIO появилась поддержка PVS-Studio. В этой статье вы узнаете, как проверить свой код статическим анализатором на примере открытого проекта.

    Что такое PlatformIO?


    PlatformIO – это кроссплатформенный инструмент для программирования микроконтроллеров. Ядром PlatformIO является инструмент с интерфейсом командной строки, однако рекомендуется использовать его в виде плагина для Visual Studio Code. Поддерживается большое количество современных чипов и плат на их основе. Умеет автоматически загружать подходящие системы сборки, а на сайте собрана большая коллекция библиотек для управления подключаемыми электронными компонентами. Есть поддержка нескольких статических анализаторов кода, в том числе и PVS-Studio.

    Импорт проекта


    Для демонстрации возьмем программу управления гексаподом ArduPod на плате Arduino Mega.


    Создадим новый проект для подходящей платы и скопируем исходный код:

    Picture 2

    В папке /arduino/AP_Utils/examples/ находятся несколько примеров программ для настройки и запуска гексапода, воспользуемся servo_test.ino. Программа для Arduino, как правило, создается в виде скетчей в формате INO, который в данном случае не совсем подходит. Для того чтобы сделать из него правильный .cpp файл обычно достаточно поменять расширение имени файла, добавить в начало заголовок #include <Arduino.h>, и убедиться, что функции и глобальные переменные объявлены до обращения к ним.

    Picture 3

    В процессе сборки могут возникнуть ошибки об отсутствии необходимых сторонних библиотек. Однако PlatformIO поможет найти их в своём репозитории.

    In file included from src\servo_test.cpp:20:0:
    src/AP_Utils.h:10:37: fatal error: Adafruit_PWMServoDriver.h:
    No such file or directory
    *******************************************************************************
    * Looking for Adafruit_PWMServoDriver.h dependency? Check our library registry!
    *
    * CLI> platformio lib search "header:Adafruit_PWMServoDriver.h"
    * Web> https://platformio.org/lib/search?query=header:Adafruit_PWMServoDriver.h
    *
    *******************************************************************************
    compilation terminated.

    По ссылке будут показаны подходящие варианты, а установка зависимости производится одной командой в терминале:

    pio lib install "Adafruit PWM Servo Driver Library"

    Настройка анализаторов и запуск проверки


    Для настройки анализаторов нужно отредактировать конфиг platformio.ini примерно таким образом:

    [env:megaatmega2560]
    platform = atmelavr
    board = megaatmega2560
    framework = arduino
    check_tool = pvs-studio
    check_flags =
      pvs-studio:
        --analysis-mode=4 ; General analysis mode. Set to 32 for MISRA
        --exclude-path=/.pio/libdeps ; Ignore dependency libraries

    Параметр check_tool указывает, какие анализаторы кода применять, а их настройка производится в параметре check_flags. Более подробные инструкции находятся в документации на официальном сайте: https://docs.platformio.org/en/latest/plus/check-tools/pvs-studio.html

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

    pio check

    Результат проверки программы гексапода


    В этот раз целью статьи является демонстрация интеграции PVS-Studio с PlatformIO, а не демонстрация диагностических возможностей анализатора. Однако, раз проект проверен, рассмотрим парочку найденных ошибок, чтобы показать, что проект удалось успешно проанализировать.

    V519 There are identical sub-expressions to the left and to the right of the '-' operator: pow(t, 2) — pow(t, 2). AP_Utils.cpp 176

    pointLeg* AP_Utils::traceLeg(uint8_t leg, float phi, float z,
      int resolution, uint8_t shape) {
      ....
      if(shape == ELLIPTIC) {
        ....
        float v = sqrt(pow(phi - legs[leg].phi, 2) + pow(z - legs[leg].z, 2));
        float u = sqrt(pow(phi - phi0, 2) + pow(z - z0, 2));
        float t = sqrt(pow(phi0 - legs[leg].phi, 2) + pow(z0 - legs[leg].z, 2));
        theta = acos((pow(t, 2) - pow(t, 2) - pow(v, 2))/(-2.0*t*u));
        ....
      }
      ....
    }

    Два одинаковых выражения вычитаются одно из другого. Непонятно, каков математический смысл этой разности. Возможно, программист просто не стал сокращать выражение. А возможно, допущена опечатка, и на месте одной из этих t должен находиться другой аргумент.

    V550 An odd precise comparison: value != — 1. It's probably better to use a comparison with defined precision: fabs(A — B) > Epsilon. AP_Utils.cpp 574

    float AP_Utils::sr04_average(uint8_t trig, uint8_t echo,
      int unit, int samples, int time) {
      ....
      float average, pause, value;
      ....
      for(int i=0; i<samples; i++) {
        value = sr04(trig, echo, unit);
        if(value != -1) { // <=
          total += value;
          delay(pause);
        } else {
          i--;
        }
      }
      average = total/samples;
      ....
      return average;
    }
    

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

    bool is_equal(double x, double y) {
      return std::fabs(x - y) < 0.001f;
    }

    Единственным безопасным вариантом прямого сравнения нецелых чисел является присвоение переменным значений констант, и последующее сравнение их значений с этими константами. В данном случае значению переменной value нигде конкретно не присваивается -1. Вот как устроен вызываемый метод AP_Utils::sr04, который и возвращает проверяемое значение:

    float AP_Utils::sr04(uint8_t trig, uint8_t echo, int unit) {
      ....
      float duration, distance;
      ....
      duration = pulseIn(echo, HIGH);
      distance = (346.3*duration*0.000001*unit)/2; // <=
      
      if((distance >= 0.02*unit) && (distance <= 4*unit)) {
        ....
        return(distance);
      } else {
        ....
        return 0;
      }
    }

    Как видно, в value запишется результат некоторых вычислений. Присваивания -1 нигде не видно, зато AP_Utils::sr04 может вернуть 0, и это наводит на мысль, что сравнение производится не с тем результатом.

    Заключение


    В этой статье мы рассмотрели процесс проверки проектов на микроконтроллерах статическим анализатором кода в среде для программирования встраиваемых систем PlatformIO. Напомню, что все желающие опробовать PVS-Studio могут воспользоваться ознакомительным режимом, а для открытых проектов есть возможность получить бесплатную лицензию.

    Тем, кто желает узнать о возможностях PVS-Studio более подробно, советую посмотреть следующие статьи:




    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Alexey Govorov. PVS-Studio Integration in PlatformIO.
    PVS-Studio
    Static Code Analysis for C, C++, C# and Java

    Комментарии 7

      +4
      Ждем плагин для VSC без привязки к платформио и чему либо еще))
        +1

        Согласен на 100% :)

        0
        del
          0

          А что с лицензией? Перед проверкой в исходники добавляли комментарий, требуемый PVS-Studio? Или запуск через PlatformIO — особый случай, не требующий изменения исходников?

            0
            Прошу уточнить вопрос. Если хотите попробовать PVS-Studio, то просто запросите демонстрационную лицензию.

            Если хотите использовать бесплатно для открытого проекта, то сюда — Бесплатный PVS-Studio для тех, кто развивает открытые проекты.

            Если для закрытого или академического, то сюда.

            Про все варианты бесплатного лицензирования — Бесплатные варианты лицензирования PVS-Studio.
              0

              Как я понял статью, перед проверкой PlatformIO сама скачивает дистрибутив PVS-Studio. В какой момент ей указывается лицензия?

                +3
                PlatformIO на Windows распаковывает PVS-Studio в
                %USERPROFILE%\.platformio\packages\tool-pvs-studio
                Чтобы сгенерировать файл лицензии там нужно вызвать утилиту CompilerCommandsAnalyzer:
                CompilerCommandsAnalyzer.exe credentials NAME XXXX-XXXX-XXXX-XXXX
                Полученный лицензионный файл будет размещен по пути %APPDATA%\PVS-Studio\PVS-Studio.lic, который PlatformIO использует по умолчанию.
                При необходимости можно указать другой путь параметром '--lic-file' в конфигурационном файле проекта (platformio.ini)
                Подробнее о параметрах написано в документации PlatformIO

                Для Linux лицензионный файл генерируется командой: pvs-studio-analyzer credentials NAME XXXX-XXXX-XXXX-XXXX
                Подробнее здесь.

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

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