http://habrahabr.ru/company/pvs-studio/blog/226247/
Три года назад мы уже проверяли Mozilla Firefox с помощью анализатора PVS-Studio. Тогда это было неудобно и затруднительно. Для Firefox отсутствует проектный файл для Visual Studio. Сборка осуществляется с помощью make-файлов. Поэтому просто взять и проверить проект нельзя. Требовалось интегрировать PVS-Studio в систему сборки, что оказалось трудной задачей. В результате, как мне помнится, была проанализирована только часть проекта. Но всё поменялось, когда появился PVS-Studio Standalone. Теперь можно отследить все запуски компиляторов и легко проверить проект.
Mozilla Firefox
Думаю, рассказывать про Firefox нет необходимости. Однако, формат статьи требует всё-таки написать немного о проверенном проекте. Поленюсь и воспользуюсь
описанием из Wikipedia:
Mozilla Firefox — свободный браузер на движке Gecko, разработкой и распространением которого занимается Mozilla Corporation. Третий по популярности браузер в мире и первый среди свободного ПО — в августе 2013 года его рыночная доля составила 19,26%. В браузере присутствует интерфейс со многими вкладками, проверка орфографии, поиск по мере набора, «живые закладки», менеджер закачек, поле для обращения к поисковым системам. Новые функции можно добавлять при помощи расширений.
Мы уже пытались проверить Firefox. Частично это нам даже удалось. По результатам проверки была написана статья "
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N4". Сложность заключалась в том, что нужно вставить вызов command-line версии PVS-Studio в make-файлы. Сделать это в большом незнакомом проекте бывает затруднительно. Именно поэтому мы в дальнейшем не делали попыток перепроверить Firefox. Ситуация изменилась с появлением PVS-Studio Standalone.
PVS-Studio Standalone
PVS-Studio Standalone может быть использован в 3 режимах:
- Удобная работа с файлом отчета (*.plog), который содержит информацию о найденных ошибках на компьютере, где не установлен Visual Studio.
- Проверка каким-либо образом препроцессированных *.i файлов.
- Отслеживание запуска компилятора и сбор всей необходимой информации для дальнейшей проверки файлов. Нам сейчас интересен именно этот режим работы.
Теперь не обязательно интегрировать command-line версию PVS-Studio в make-файлы. Можно проверить Firefox намного проще. Мы именно так и сделали. Последовательность действий:
- Запускаем программу PVS-Studio Standalone;
- Выполняем команду «Compiler Monitoring»;
- Компилируем проект Firefox;
- Останавливаем процедуру слежения («Stop Monitoring»);
- Запускается проверка файлов;
- Изучаем предупреждения, выданные анализатором кода.
Более подробно, как использовать этот режим, можно прочитать
здесь.
Примечание
Помимо PVS-Studio мы предлагаем упрощенный вариант анализатора под названием
CppCat. В случае c Firefox, он не сможет выполнить анализ проекта. В нём нет Standalone версии, а также нет command-line версии для интеграции в make-файлы. Однако, CppCat намного проще в изучении и хорошо подходит индивидуальным разработчикам и небольшим командам. См. также: "
Сравнение возможностей статических анализаторов кода PVS-Studio и CppCat".
Результаты проверки Mozilla Firefox
Проект Firefox очень качественный. В добавок, как я понимаю, при его разработке уже применяются инструменты статического анализ кода: Coverity и Klocwork. По крайней мере, я встречал упоминания этих анализаторов в некоторых файлах.
Поэтому найти хоть что-то — это уже достижение. Давайте посмотрим, какие предупреждения анализатора PVS-Studio показались мне интересными.
Опечатка N1 NS_IMETHODIMP
nsNativeThemeWin::WidgetStateChanged(....)
{
....
if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
aWidgetType == NS_THEME_WINDOW_FRAME_LEFT ||
aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT ||
aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM ||
aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE ||
aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE || <<<===
aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE || <<<===
aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
*aShouldRepaint = true;
return NS_OK;
....
}
Предупреждение PVS-Studio: V501 There are identical sub-expressions 'aWidgetType == 237' to the left and to the right of the '||' operator. nsnativethemewin.cpp 2475
Переменная 'aWidgetType' два раза сравнивается с константой NS_THEME_WINDOW_BUTTON_MINIMIZE. Это опечатка. Второй раз переменную нужно сравнить с константой NS_THEME_WINDOW_BUTTON_MAXIMIZE.
Опечатка N2 bool nsHTMLCSSUtils::IsCSSEditableProperty(....)
{
....
if (aAttribute && aAttribute->EqualsLiteral("align") &&
(nsEditProperty::ul == tagName <<<<====
|| nsEditProperty::ol == tagName
|| nsEditProperty::dl == tagName
|| nsEditProperty::li == tagName
|| nsEditProperty::dd == tagName
|| nsEditProperty::dt == tagName
|| nsEditProperty::address == tagName
|| nsEditProperty::pre == tagName
|| nsEditProperty::ul == tagName)) { <<<<====
return true;
}
....
}
Предупреждение PVS-Studio: V501 There are identical sub-expressions 'nsEditProperty::ul == tagName' to the left and to the right of the '||' operator. nshtmlcssutils.cpp 432
Переменная 'tagName' два раза сравнивается с nsEditProperty::ul. Возможно, одна проверка лишняя. Или забыли сравнить с чем-то ещё.
Опечатка N3 void Reverb::process(....)
{
....
bool isCopySafe =
destinationChannelL &&
destinationChannelR &&
size_t(destinationBus->mDuration) >= framesToProcess &&
size_t(destinationBus->mDuration) >= framesToProcess;
....
}
Предупреждение PVS-Studio: V501 There are identical sub-expressions 'size_t (destinationBus->mDuration) >= framesToProcess' to the left and to the right of the '&&' operator. reverb.cpp 192
Переменная 'framesToProcess' два раза сравнивается с 'size_t(destinationBus->mDuration)'.
Опечатка N4 float
PannerNode::ComputeDopplerShift()
{
....
double scaledSpeedOfSound = listener->DopplerFactor() /
listener->DopplerFactor();
....
}
Предупреждение PVS-Studio: V501 There are identical sub-expressions 'listener->DopplerFactor()' to the left and to the right of the '/' operator. pannernode.cpp 529
Очень подозрительное выражение, которое стоит проверить.
Опечатка N5bool DataChannelConnection::SendDeferredMessages()
{
....
if ((result = usrsctp_sendv(mSocket, data, ...., 0) < 0)) {
....
}
Предупреждение PVS-Studio: V593 Consider reviewing the expression of the 'A = B < C' kind. The expression is calculated as following: 'A = (B < C)'. datachannel.cpp 1105
Не там поставлена скобка. Упростим выражение, чтобы ошибка была лучше заметна:
if ((result = foo() < 0))
Это выражение вычисляется так. Результат, который вернула функция, сравнивается с 0. Затем true или false записывается в переменную 'result'. Ошибка в том, что не там закрывается одна из скобок. На самом деле, программист хотел написать выражение:
if ((result = foo()) < 0)
Теперь значение, которое вернула функция, записывается в переменную 'result'. И только потом это значение сравнивается с нулём.
Опечатка N6 void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold)
{
....
topRects = destRect;
bottomRects = bottomRectsEnd;
destRect = topRects;
....
}
Предупреждение PVS-Studio: V587 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 358, 360. nsregion.cpp 360
Код подозрителен. Наверное, здесь есть какая-то опечатка.
Некорректная проверка N1enum nsBorderStyle {
eBorderStyle_none = 0,
....
};
....
NS_IMETHODIMP
nsWindow::SetNonClientMargins(nsIntMargin &margins)
{
if (!mIsTopWidgetWindow ||
mBorderStyle & eBorderStyle_none ||
mHideChrome)
return NS_ERROR_INVALID_ARG;
....
}
Предупреждение PVS-Studio: V616 The 'eBorderStyle_none' named constant with the value of 0 is used in the bitwise operation. nswindow.cpp 2278
Выражение «mBorderStyle & eBorderStyle_none» не имеет смысла. Отсутствие стилей (eBorderStyle_none) кодируется значением 0. По все видимости, код условие следует записать так:
if (!mIsTopWidgetWindow ||
mBorderStyle != eBorderStyle_none ||
mHideChrome)
Некорректная проверка N2 NS_IMETHODIMP nsWindowsRegKey::ReadStringValue(....)
{
....
DWORD type;
....
if (type != REG_SZ && type == REG_EXPAND_SZ &&
type == REG_MULTI_SZ)
return NS_ERROR_FAILURE;
....
}
Предупреждение PVS-Studio: V547 Expression is always false. Probably the '||' operator should be used here. nswindowsregkey.cpp 292
Переменная 'type' не может быть одновременно равна двум разным значениям. Упростим код, чтобы легче понять, что не нравится анализатору:
if (... && type == 2 && type == 7)
Это условие всегда ложно.
Скорее всего, код должен быть таким:
if (type != REG_SZ && type != REG_EXPAND_SZ &&
type != REG_MULTI_SZ)
Некорректная проверка N3 const SafepointIndex *
IonScript::getSafepointIndex(uint32_t disp) const
{
....
size_t minEntry = 0;
....
size_t guess = ....;
....
while (--guess >= minEntry) {
guessDisp = table[guess].displacement();
JS_ASSERT(guessDisp >= disp);
if (guessDisp == disp)
return &table[guess];
}
....
}
Предупреждение PVS-Studio: V547 Expression '-- guess >= minEntry' is always true. Unsigned type value is always >= 0. ion.cpp 1112
Цикл остановится только тогда, когда будет найден нужный элемент. Если такого элемента нет, условие остановки цикла никогда не выполнится, и произойдёт выход за границы массива.
Причина в том, что переменная 'guess' имеет беззнаковый тип. Это значит, что условие (--guess >= 0) всегда истинно.
Невнимательность N1void WinUtils::LogW(const wchar_t *fmt, ...)
{
....
char* utf8 = new char[len+1];
memset(utf8, 0, sizeof(utf8));
....
}
Предупреждение PVS-Studio: V579 The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. winutils.cpp 146
Выражение 'sizeof(utf8)' возвращает размер указателя, а не размер выделенного буфера памяти. Правильный вариант кода:
memset(utf8, 0, sizeof(*utf8) * (len+1));
Невнимательность N2
Как всегда встречается код, где указатель в начале используется, а только затем проверяется на равенство нулю. Приведу в статье только один такой случай. Остальные ошибки авторы Firefox смогут найти, запустив анализатор.
void
nsHttpTransaction::RestartVerifier::Set(
int64_t contentLength, nsHttpResponseHead *head)
{
if (mSetup)
return;
if (head->Status() != 200) <<<<====
return;
mContentLength = contentLength;
if (head) { <<<<====
....
}
Предупреждение PVS-Studio: V595 The 'head' pointer was utilized before it was verified against nullptr. Check lines: 1915, 1920. nshttptransaction.cpp 1915
В начале указатель 'head' разыменовывается в выражении «head->Status()». А затем он проверяется на равенство нулю.
Невнимательность N3NPError NPP_New(....)
{
....
InstanceData* instanceData = new InstanceData;
....
NPError err = pluginInstanceInit(instanceData);
if (err != NPERR_NO_ERROR) {
NPN_ReleaseObject(scriptableObject);
free(instanceData);
return err;
}
....
}
Предупреждение PVS-Studio: V611 The memory was allocated using 'new' operator but was released using the 'free' function. Consider inspecting operation logics behind the 'instanceData' variable. nptest.cpp 1029
Память выделяется с помощью оператора 'new', а освобождается вызовом функции 'free'. Результат — неопределённое поведение программы. Впрочем, это не страшно, так как код относится к тестам.
Невнимательность N4
Ещё один фрагмент кода, относящийся к тестам. Переменная 'device' может остаться неинициализированной:
static ID3D10Device1* getD3D10Device()
{
ID3D10Device1 *device;
....
if (createDXGIFactory1)
{
....
hr = createD3DDevice(...., &device);
....
}
return device;
}
Предупреждение PVS-Studio: V614 Potentially uninitialized pointer 'device' used. nptest_windows.cpp 164
Более тщательная проверка
Целью статьи не ставилось описать все ошибки, которые может выявить PVS-Studio. Наверняка, я что-то пропустил. Про кое что не стал писать сознательно. Например, анализатор выдавал много предупреждений
V610, относящихся к сдвиговым операциям, которые приводят к неопределённому поведению. Эти предупреждения однотипны, и писать про них не интересно.
Цель статьи показать возможности статического анализа, и привлечь внимание людей к нашему инструменту. Более тщательный анализ Firefox могут осуществить сами разработчики. Им будем намного легче понять, является что-то ошибкой или нет.
Примечание для разработчиков Firefox. Проект весьма большой, поэтому анализатор PVS-Studio выдаёт много ложных срабатываний. Однако, большинство из них относятся к специфическим макросам. Можно очень быстро сократить количество ложных предупреждений в несколько раз, расставив соответствующие комментарии в коде. Как подавить предупреждения, относящиеся к определённым макросам, описано в документации (см. раздел "
Подавление ложных предупреждений"). Если разработчики Firefox заинтересуются приобретением лицензии на PVS-Studio, мы со своей стороны также готовы принять участие в сокращении ложных срабатываний.
Заключение
Подозрительных участков кода нашлось мало. Причина в том, что ошибки уже были найдены с помощью других методов тестирования и других статических анализаторов. Статические анализаторы кода наиболее полезны при регулярном использовании, так как позволяют выявить ошибку ещё на этапе написания кода. Подробнее это рассматривается в статье "
Лев Толстой и статический анализ кода".
Желаю всем успехов в программировании и поменьше ошибок.
Дополнительные ссылки
- Анализатор PVS-Studio. Найди массу глупых ошибок в процессе написания кода. Сэкономь время команды. У вас не делают глупых ошибок? Ха-ха!
- Анализатор CppCat для индивидуальных разработчиков и небольших проектов. Цена $250 за лицензию. Скидки при продлении лицензии или при покупки нескольких лицензий.
- Приглашаем вас подписаться на наш твиттер: @Code_Analysis. Регулярно публикуем ссылки на интересные статьи по программированию и о проверке новых проектов.
Эта статья на английском
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov.
Firefox Easily Analyzed by PVS-Studio Standalone.