PVS-Studio — это программа, которая ищет ошибки в исходном коде проектов C++ и C#, которые компилятор не видит, но которые почти наверняка являются ошибками программирования.
Примечание. Эта статья изначально была опубликована на русском языке в блоге blog.harrix.org. Оригинальная и переведенная версии размещены на нашем сайте с разрешения автора.
Введение
Ко мне обратились ребята из команды PVS-Studio с предложением о сотрудничестве. Я много читал об их продукте на страницах Хабрахабра, но ни разу не пробовал. Итак, я предложил следующее: мне дадут лицензию на продукт, а я просканирую свои программы и напишу обзор инструмента, где расскажу, как использую анализатор, как проверяются коды и так далее. . Они сказали да.
Итак, то, что вы увидите в этой статье, — это, как сейчас модно называть, честный обзор без всяких выдумок от обычного среднестатистического программиста, который занимается «академическим» программированием, а не прикладным программированием. Другими словами, я не гуру из какой-то крупной компании, работающей над сложными проектами, который разбирается в тоннах утилит, разбирается в оптимизации компиляторов и тому подобном.
Еще одна вещь, которую вы должны знать, это то, что всего несколько лет назад я был преданным поклонником функционального программирования. Я не поддерживал ООП, никогда не использовал пространства имен, много раз изобретал велосипед и так далее. Сейчас я вспоминаю тот период своей жизни как кошмарный и активно переписываю многие свои программы тех времен, хотя они еще не готовы к статическому анализу. По этой причине я возьму для анализа проекты из своего прошлого, связанного с функциональным программированием (их все можно найти на GitHub). Хотя там царит функционализм, я очень аккуратно относился к кодированию, тестированию и документированию при их написании, поэтому я не думаю, что в этих проектах должно быть много серьезных ошибок.
Вот так.
Установка
Установка не вызвала никаких проблем. На домашней странице сайта PVS-Studio есть большая кнопка Скачать и попробовать, которая ведет на страницу со ссылкой для скачивания, которую вы точно не пропустите.
Установка полностью стандартна; даже особых опций на выбор нет. Однако в своих статьях я всегда стараюсь описать даже самые простые шаги. Итак, вот вам скриншоты:
Процесс установки PVS-Studio
Шаг 1.
Шаг 2.
Шаг 3.
Шаг 4.
Шаг 5.
Шаг 6.
Шаг 7.
Как все это не удалось
Сразу говорю, никакой документации я сначала не читал. Я просто установил программу и подумал: «Ну и что дальше?» Я обнаружил следующие новые пункты в меню «Пуск»:
Интуиция подсказывала мне, что нужный мне предмет должен иметь то же имя, что и программа. Нажмите. И здесь он подвел меня и показал это сообщение:
Честно говоря, я немного забеспокоился. Видите ли, я в основном работаю в Qt и держу Visual Studio скорее как обучающую программу для своих студентов.
В ПОРЯДКЕ. Возможно, мне следует попробовать другой пункт меню, Автономный?
Так-то лучше. Теперь важное замечание. Алгоритм работы с анализатором ожидал, таков: я открываю его, загружаю исходники моего проекта, и он находит за меня ошибки. Это предположение оказалось совершенно неверным, но об этом мы поговорим позже.
Итак, сначала я попытался загрузить один из своих файлов (беспокоил тот факт, что он позволял выбрать только один файл за раз).
Вот оно, а что дальше? Нет больше больших или красочных кнопок.
В главном меню есть только один пункт, похожий на то, что мне нужно:
При нажатии на нее открывается следующее окно.
И вот тут я поступил глупо. Вместо того, чтобы читать текст, я начал нажимать на кнопки. Когда я нажал Выбрать, программа запросила несколько файлов *.suppress, что явно не то, что мне было нужно. Слово Компилятор привлекло мое внимание. Хорошо, поэтому я должен нажать Начать мониторинг.
Я думал, что программа сканирует мой компьютер на наличие компиляторов, так что это должно занять довольно много времени. И это действительно произошло (я ждал несколько часов), но я был рад видеть, что он наконец начал что-то находить:
Только через некоторое время я понял причину: я работал со своими проектами и компилировал их, пока шел процесс мониторинга.
Пару часов спустя я почувствовал, что инструмент нашел достаточное количество компиляторов и остановил его. Однако никаких результатов это не дало. Что мне делать тогда? Блин, теперь надо документацию читать(
Релевантная ссылка оказалась не совсем заметной.
Прочитав статью, я наконец понял, что делать.
Как все получилось
Вот как анализатор на самом деле работает.
Вы запускаете процесс мониторинга в PVS-Studio, а затем запускаете компилятор на своем проекте. По завершении компиляции остановите процесс мониторинга и подождите некоторое время, пока программа выведет журнал анализа.
Я покажу вам, как это работает, на примере тестового приложения Qt 5.7 с MinGW, в котором используется моя библиотека Harrix MathLibrary.
Зайдите в меню анализа.
Начать мониторинг запуска компилятора.
Процесс мониторинга может работать в фоновом режиме.
Скомпилируйте проект:
PVS-Studio обнаружил запущенный экземпляр нашего компилятора.
Остановить мониторинг.
И тут PVS-Studio высыпала кучу предупреждений. Черт. Я надеялся на лучший результат((
Двойной щелчок по предупреждению приведет вас к соответствующему исходному файлу, в котором была обнаружена ошибка.
Когда вы, наконец, уловили идею программы, работать с ней становится легко, но не совсем интуитивно для новичка.
Теперь давайте посмотрим, какие ошибки у нас есть. Они вообще баги?
Предупреждение. При запуске компилятора перестройте весь проект. Совсем недавно меня огорчило 71 предупреждение, но после того, как я их исправил и пересобрал проект, инструмент выдал более 1900 предупреждений.
Теперь мне хочется ругаться.
Анализ ошибок
Мы прошли путь моего вознесения к пониманию того, как пользоваться программой. Теперь посмотрим на результаты анализа.
Баги, найденные в самом Qt, меня мало интересуют — за них несут ответственность те, кто разрабатывал компилятор.
А как насчет моих собственных ошибок?
Большинство из более чем 1900 предупреждений относятся к V550:
V550. Странное точное сравнение. Вероятно, лучше использовать сравнение с определенной точностью: fabs(A – B) ‹ Epsilon или fabs(A – B) › Epsilon
И я согласен с этим предупреждением в большинстве случаев. Например, следующий код с (F[i]==F[i+1]) может вызвать проблемы:
//identical elements //are assigned identical ranks as arithmetic mean for (i=0;i<VHML_N-1;i++) { if (F[i]==F[i+1]) { j=i+1; while ((F[i]==F[j])&&(j<VHML_N)) j++; Sn=HML_SumOfArithmeticalProgression(i+1,1,j-i); Sn/=double(j-i); for (k=0;k<VHML_N;k++) if (Fitness[k]==F[i]) VHML_ResultVector[k]=Sn; i=j-1; } }
Еще хуже идея — проверять крайние положения колеса Максвелла, как это делается в следующем ужасном коде:
//if the wheel is in extreme positions, if (((x==R)&&(v<0))||((x==l)&&(v>0))) v=-v*(1.-k);
И вот что у меня получилось на следующем фрагменте.
//Calculating arithmetic mean of two samples xn=HML_Mean(x,VHML_N); yn=HML_Mean(x,VHML_N);
V656 Переменные «xn», «yn» инициализируются вызовом одной и той же функции. Вероятно, это ошибка или неоптимизированный код. Рассмотрите возможность проверки выражения «HML_Mean(x, VHML_N)». Контрольные строки: 3712, 3713. harrixmathlibrary.h 3713
Это довольно разочаровывающая ошибка. Должно быть, я скопировал фрагмент кода, но забыл изменить некоторые токены.
Еще одна глупая ошибка.
int VHML_Result=0; if (VHML_N1==VHML_N2) for (int i=0;i<VHML_N1;i++) if (a[i]!=b[i]) VHML_Result=-1; else VHML_Result=-1;
V523 Оператор "then" эквивалентен оператору "else". harrixmathlibrary.h 695
Эта функция всегда даст положительный ответ о существующем решении. Я так и не понял, что заставило меня прервать все вычисления переменной solutionis в конце функции.
double HML_LineTwoPoint(double x, double x1, double y1, double x2, double y2, int *solutionis) { /* This function is a two-point linear equation. Value of y is returned for given x. Input parameters: x - abscissa of point in question; x1 - abscissa of first point; y1 - ordinate of first point; x2 - abscissa of second point; y2 - ordinate of second point; solutionis - stores the returned solution: 0 - no solution; 1 - solution found; 2 - any number is a solution (the line is parallel to y-axis). Return value: Value of y for given x. */ double y=0; if ((x1==x2)&&(y1==y2)) { //this is the same point, so any number is a solution y=y1; *solutionis=2; } else { if (y1==y2) { //this line is parallel to x-axis y=y1; *solutionis=1; } else { if (x1==x2) { //this line is parallel to y-axis if (x==x1) { y=y1; *solutionis=2; } else { y=0; *solutionis=0; } } else { y=(x-x1)*(y2-y1)/(x2-x1)+y1; } } } *solutionis=1; return y; }
V519 Переменной ‘*solutionis’ дважды подряд присваиваются значения. Возможно, это ошибка. Контрольные строки: 1788, 1821. harrixmathlibrary.cpp 1821
Следующее предупреждение касается моей излишней осторожности, а не реальной ошибки: я сначала устанавливаю сводную переменную в ноль, на всякий случай:
if (VHML_N>0) VHML_Result=0; ... //Evaluating real-vector objective function VHML_Result=VHML_TempFunction(VHML_TempDouble3,RealLength); return VHML_Result;
V519 Переменной VHML_Result дважды подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 385, 395. harrixmathlibrary.cpp 395
PVS-Studio тоже нашел в моем коде две одинаковые функции (тогда я не жаловал std). Помимо этих двух, он также нашел еще несколько, что очень полезно, когда у вас большой проект с большим количеством функций, и вы не можете вспомнить, использовали ли вы уже ту или иную функцию или нет.
template <class T> void HML_Swap(T &a, T &b) { /* This function swaps values of two numbers. Input parameters: a - first number; b - second number. Return value: None. */ T x; x = b; b = a; a = x; } template <class T> void HML_NumberInterchange(T &a, T &b) { /* This function swaps values of two numbers. Input parameters: a - first number; b - second number. Return value: None. */ T x; x = b; b = a; a = x; }
V524 Странно, что тело функции HML_Swap полностью эквивалентно телу функции HML_NumberInterchange. harrixmathlibrary.h 2349
А вот классическая ошибка, связанная с отсутствующим преобразованием типов.
double HML_TestFunction_HyperEllipsoid(double *x, int VHML_N) { /* Function of multiple variables: Hyperellipsoid. Test function for real optimization. Input parameters: x - pointer to original array; VHML_N - size of array x. Return value: Value of test function at point x. */ double VHML_Result=0; for (int i=0;i<VHML_N;i++) VHML_Result += (i+1)*(i+1)*x[i]*x[i]; return VHML_Result; }
V636 Выражение «(i + 1) * (i + 1)» было неявно преобразовано из типа «int» в тип «double». Рассмотрите возможность использования явного приведения типа, чтобы избежать переполнения. Пример: двойной A = (двойной)(X) * Y;. harrixmathlibrary.cpp 10509
Что касается этого кода, то анализатор выдал ложное предупреждение, так как HML_ProportionalSelectionV2 возвращает случайное значение:
NumberOfParent1=HML_ProportionalSelectionV2(....); NumberOfParent2=HML_ProportionalSelectionV2(....);
Переменные V656 «NumberOfParent1», «NumberOfParent2» инициализируются посредством вызова одной и той же функции. Вероятно, это ошибка или неоптимизированный код. Контрольные строки: 1106, 1107. harrixmathlibrary.cpp 1107
В библиотеке Harrix QtLibrary обнаружен ряд проблем.
Например, в нем есть функция разбиения строки на слоги. Инструмент дал хороший совет, что я должен объединить условия.
if ((i>=1)&&(i!=N-1)) { if ((HQt_GetTypeCharRus(S.at(i-1))==3) && (HQt_GetTypeCharRus(S.at(i))!=0) && (HQt_GetTypeCharRus(S.at(i+1))!=0)) cut=true; } if ((i>=1)&&(i!=N-1)) { if ((HQt_GetTypeCharRus(S.at(i-1))==1) && (HQt_GetTypeCharRus(S.at(i))==1) && (HQt_GetTypeCharRus(S.at(i+1))!=0)) cut=true; }
V581 Условные выражения операторов if, расположенных рядом друг с другом, идентичны. Проверить строки: 1140, 1147. harrixqtlibrary.cpp 1147
Цикл в следующем фрагменте содержит логическую переменную in, которая всегда будет true.
int VHQt_Result = -1; bool in=false; int i=0; while ((i<StringList.count())&&(in!=true)) { if (StringList.at(i)==String) VHQt_Result=i; i++; } return VHQt_Result;
V560 Часть условного выражения всегда истинна: (in != true). harrixqtlibrary.cpp 2342
Также встречаются фрагменты с дублирующимся кодом при заполнении моделей предметами:
item = new QStandardItem(QString("HML_RealGeneticAlgorith....")); model->appendRow(item); item = new QStandardItem(QString("HML_RealGeneticAlgorith....")); model->appendRow(item);
V760 Обнаружены два идентичных блока текста. Второй блок начинается со строки 86. mainwindow.cpp 83
Вердикт
Минусы:
- Программа не интуитивно понятна; это не легко начать с. Если бы я просто зашел на их сайт, скачал демо-версию и попробовал, то, скорее всего, удалил бы ее, неважно.
- «Старомодный» дизайн.
- Подсветка синтаксиса похожа на Notepad++ (и это плюс), но я также привык, что Notepad++ подсвечивает все остальные экземпляры выбранного ключевого слова, а также выделяет соответствующую закрывающую скобку при выборе открывающей.
Плюсы:
- Программа умеет делать свою работу, а это самое главное. Он может обнаружить множество скрытых ошибок или предупреждений, которые вы вряд ли когда-нибудь заметите.
- Как только вы разберетесь, как им пользоваться, работа с анализатором станет легкой и комфортной.
- Инструмент поддерживает несколько компиляторов, в том числе используемые в сборках Qt.
Заключительный вывод: эта программа, безусловно, является обязательной. Очень удобный инструмент для управления качеством вашего кода.
P.S. А я надеялся, что багов не будет(
P.S.S. Более 1900 предупреждений!