Pull to refresh
612
17
Андрей Карпов @Andrey2008

Директор по развитию бизнеса

Send message
Пока не изменилась. Использование машинного обучения в статическом анализе исходного кода программ.
Мы посмотрели на этот ControlFlag, думали может по него какую-то статью обзорную написать. Но там писать не про что. Для кода анализатора PVS-Studio (C++ ядро) выдал всего два бессмысленных сообщения вида:
template <typename F, std::enable_if_t<std::is_invocable_r_v<bool, F, const Ptree*> , int> = 0>
[[nodiscard]] bool FindIf(const Ptree* tree, F f)
{
  while (tree != nullptr)
  {
    if (f(tree))
      return true;
    if (tree->IsLeaf())
      return false;
    if (FindIf(tree->Car(), f)) 
      return true;              // <=
    tree = tree->Cdr();
  }
  
  return false;
}

Ему подозрительно, что 2 соседних if'а имеют return true, а посередине return false. Хм…

Для сравнения, от того-же ASan, на практике гораздо больше прока: Зачем нужен динамический анализ кода, на примере проекта PVS-Studio.

PVS-Studio не ориентируется на ФСТЭК. Svace ориентирован на ФСТЭК. Актуален ФСТЭК - берите Svace.

Подразумевается, что каждый подписанный на C \ С++ хаб, слышал про Cppcheck :)

Кстати, ещё одна интересная история сегодня про поддержку появилась: Слава баг-репортам, или как мы сократили время анализа проекта пользователя с 80 до 4 часов.
За основу возьмём реальное общение: больше 100 писем в переписке, исключения, анализ, который не завершается за трое суток…
Просто не получится :). Это ведь ещё и разные диагностики :). Изначально речь шла про V634. Она именно оценивает выражения со сдвигами.

Паттерн вида x << y + z.
Забывают, что у сложения/вычитания/умножения/деления более высокий приоритет, чем у сдвига. Лучше всегда явно ставить скобки.
Пример:
int x = a<<4 + b;

Исключения:
  1. Операция с высоким приоритетом находится в макросе, а сдвиг нет.
  2. Операция с высоким приоритетом ('-','/','%') выполняется над числом 64(int64), 32(int), 16(short), 8(char).
  3. Разделение на строки выражения свидетельствует о преднамеренном использовании:
     a = 1 <<
          2 * 8; //ok
     b = 2 
          << 2 * 8 //ok
     c = 3 << 2
              * 8 //err
    
  4. Нет смысл сдвигать вправо константу на константу или влево константу (кроме 1) на константу.
  5. Сдвиг является условием. В этом случае нет смысла результат сдвига умножать. Пример: if (value >> (i — 1) * 8)


А если речь заходит про && и ||, то это уже V648.

Странно, когда логические операции '&&' и '||' используются вместе и не разделены скобками. Часто их приоритеты путают. Приоритет оператора && выше, чем у ||.

Следует предупреждать в том случае, если написано так: A || B && C.
Исключение:
  1. Если имеется выражение вида A || !A && B, т.е. оператор || разделяет два противоположных по смыслу высказывания.
  2. Если выражение вида… || p != nullptr && f(p) ...
  3. Имеется выражение вида x == 0 || A != 123 && A != 321, т.е. оператор &&
    разделяет сравнение одной и той же переменной с разными константами.
    При этом, с одной стороны от последовательности || && ничего не должно быть.
       Т.е. здесь не ругаемся: b || A != 0 && A != 1
             а здесь ругаемся: b || A != 0 && A != 1 || c
             и здесь ругаемся: b || A != 0 && (A != 1 || c)
    

if (a && b || c && d) // нет
if (a || b && c || d) // да
x = 1 + 3 * y; // нет

  1. Первая строка. Тот, кто писал, скорее всего понимает, что хочет объединить операцией || два подвыражения. Типовой код. Ошибки скорее всего нет.
  2. Вторая строка. Возможно, код работает не так, как ожидает программист. Возможно, ошибки нет, но код лучше перепроверить.
  3. Третья строка. Невероятно, что кто-то спутает приоритет операций + и *. Ошибки скорее всего нет.
Анализатор отталкивается не от теоретических, а практических типов :). В данном режиме сборки int 32-битный. А значит может быть переполнение.
Не будет из 100500. Не верится, что такие выражения часто требуются. А если это особый проект, где их много, то можно отключить диагностику.
И правильно что ругается, ибо каждый раз нужно вспоминать (или подглядывать) приоритет операций. Если лишнее — эту диагностику можно отключить. В данном случае ситуация ещё усугубляется повторяющимися скобками.
gcc и так понял
Да. Но нашли ошибку мы. :) Мощь PVS-Studio.
Как я понимаю, варианты бесплатного лицензирования, описанные в этой статье, не подходят по той причине, что Вы делаете маленькие проекты на заказ? Но тогда и проблемы как таковой нет. В маленьких проектах низкая плотность ошибок, и статический анализ не столь актуален, как в больших проектах. Ну или по крайней мере можно использовать более простыми бесплатными анализаторами, такими как Cppcheck.

Если проект большой и над ним работает команда, то не вижу препятствия запросить у заказчика приобретение лицензии.
Или узкая специализация это линия партии?
Пожалуй, да, хотя мы именно так это никогда не формулировали :)
Клиенты довольны. Хейтеры хейтят. Всё как везде и всегда.

Из статьи "Интеграция PVS-Studio в uVision Keil":
И началась эпическая переписка с техподдержкой, которая – исключительно по моей вине – растянулась почти на год (!). Вот честное слово — техподдержка у PVS-Studio – натурально лучшая из всех, с кем я общался, а общался я со многими, от российских производителей микросхем, где человек поздравлял меня с «днём пирожков с малиновым вареньем» (нет, это не шутка) до крупнейших зарубежных компаний, где меня месяцами футболили от человека к человеку :)
Тут же я со стыдом признаюсь, что отвечал существенно медленнее, чем отвечали мне… частично меня оправдывает необходимость заниматься основными рабочими задачами, но только частично.

напишу первую часть статьи, где все ваши мифы разрушу раз и навсегда. Заслужили, так заслужили.

Уж 10 лет как пишут: из 2011 - Народ против PVS-Studio: дубль первый (и последний :)

Аргументировать прочитанной (одной?) книгой, это конечно прикольно... Но к теме выявления потенциальных ошибок, это не имеет никакого отношения.

Приведу в свою очередь описание предупреждения V1053.

Анализатор обнаружил вызов виртуальной функции в конструкторе или деструкторе класса.

Рассмотрим пример:

struct Base
{
  Base()
  {
    foo();
  }
  
  virtual ~Base() = default;
  virtual void foo() const;  
};

Сам по себе вызов виртуального метода 'foo' в конструкторе класса 'Base' может не являться ошибкой, однако проблемы могут проявить себя в производных классах.

struct Child : Base
{
  Child() = default;

  virtual ~Child() = default;
  virtual void foo() const override;
};

Во время создания объекта типа 'Child' будет вызван метод 'Base::foo()' из конструктора базового класса, но не переопределенный метод 'Child::foo()' из производного класса. Отметим, что в некоторых других языках программирования (C#, Java, ...) аналогичный код будет работать иначе: во время создания объекта 'Child' конструктором по умолчанию будет вызван конструктор по умолчанию базового класса 'Base', который затем вызовет переопределенный метод 'Child::foo()'.

Для исправления этой проблемы нужно уточнить вызов метода. Например, для класса 'Base':

struct Base
{
  Base()
  {
    Base::foo();
  }
  
  virtual ~Base() = default;
  virtual void foo() const;  
};

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

Также отметим, что использование указателя на себя 'this' при вызове виртуального метода не решает исходную проблему, и при использовании 'this' все также необходимо уточнить, из какого класса следует позвать виртуальную функцию:

struct Base
{
  Base()
  {
    this->foo();       // bad
    this->Base::foo(); // good
  }
  virtual ~Base() = default;
  virtual void foo() const;  
};

Данная диагностика классифицируется как: CERT-OOP50-CPP.

Мне совершенно не нравится трактовка "потенциально опасны". В чем же опасны, если конвертация идет только в 0 или 1?

Опасна не сама конвертация. Опасна аномальность этого действия.

Окей, допустим это ошибка, и хотели логическое ИЛИ - известный прием записи в булевые переменные результатов условий.

Любой практикующий С или С++ программист понимает, что здесь совсем другое. Видно, что хотели использовать именно побитовое ИЛИ, чтобы сформировать маску из разных бит. Обратите внимание, что именованные константы в enum являются степенями двойки.

Итак, с помощью | составляется маска из битов. Далее эту маску хотят записать в переменную типа bt_proximity_property_t. Это не очень красиво, но допустимо, так как для представления enum используется достаточное количество бит для вмещения значения 3. Подробнее про тонкости.

Но точно нет никакого смысла запихивать маску b0..0011 в переменную типа bool. Это аномалия. Да, этот код компилируется. Да, понятно, как работает неявное приведение типов. Даже можно придумать какую-то ситуацию, когда такой код оправдан. Но всё равно это аномалия, о которой сообщает анализатор. И действительно - перед нами не какая-то хитрая задумка, а опечатка.

Information

Rating
438-th
Works in
Date of birth
Registered
Activity

Specialization

Specialist
C++
C
Software development