Статический анализ: ошибки вмедиаплеере и безглючная аськаАвтор: Андрей КарповДата: 18.11.2010Продолжу экскурсию по ошибка...
V501 There are identical sub-expressions to the left and to the right of the && operator: a -> tsize && a-> tsize media li...
V529 Odd semicolon ; after for operator. settings.c 913Есть еще третий и четвертый фрагмент с ;. Но приводить здесь не буд...
string basewindows_getuserinput(const string title,    const string cap, const string dtxt){    memset(uinput_text, 0, uin...
Возможно, иногда в каких-то программах вы замечали, что диалоги открытия/сохранения файловработают со странностями или в п...
lofn.lpstrFilter = uni("Equalizer Preset (*.feq)0*.feq");    ...    ...    // LOAD    ...    lofn.lpstrFilter = uni("Equal...
Диагностика PVS-Studio и местоположение в коде:V523 The then statement is equivalent to the else statement. media library ...
fhead[13] = 0;    }    ...}Диагностическое сообщение PVS-Studio и местоположение в коде:V525 The code containing the colle...
SettingsWidget *widget = p->modifiedWidgets.takeFirst();        widget->save();        if (widget != c)         widget->de...
Upcoming SlideShare
Loading in …5
×

Статический анализ: ошибки в медиаплеере и безглючная аська

271 views

Published on

Продолжу экскурсию по ошибкам в программах и демонстрацию полезности статического анализа кода.

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
271
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Статический анализ: ошибки в медиаплеере и безглючная аська

  1. 1. Статический анализ: ошибки вмедиаплеере и безглючная аськаАвтор: Андрей КарповДата: 18.11.2010Продолжу экскурсию по ошибкам в программах и демонстрацию полезности статическогоанализа кода.Это мой последний пост про пока недоступную для скачиванию версию PVS-Studio. Планирую, чточерез неделю вы уже сможете попробовать первую beta-версию с новым набором правил общегоназначения.Рассмотрим два проекта. Первый - Fennec Media Project. Это универсальный медиа-плеерориентированный на воспроизведение аудио и видео в высоком разрешении. В комплектисходных кодов входит множество модулей расширения (plugins) и кодеков, но анализироватьсябудет только сам плеер. Исходный код последней на данный момент версии 1.2 Alpha доступенздесь.Второй проект - qutIM. Это кроссплатформенный клиент мгновенного обмена сообщениями соткрытым исходным кодом. Был проанализирован код на момент начала ноября 2010 года. Наборисходных кодов был предоставлен мне одним из разработчиков, но вы также можете скачатьисходный код с официального сайта.Fennec Media Project. Небольшой нормальный проект, содержащий нормальное количествоошибок. Вот первая ошибка. Или первые две ошибки, смотря как считать. В общем, в двух местахвместо переменной b используется переменная a.int fennec_tag_item_compare(struct fennec_audiotag_item *a, struct fennec_audiotag_item *b){ int v; if(a->tsize && a->tsize) v = abs(str_cmp(a->tdata, a->tdata)); else v = 1; return v;}PVS-Studio указал на этот код, так как условие "a->tsize && a->tsize" явно подозрительно.Диагностическое сообщение и местоположение ошибки в коде:
  2. 2. V501 There are identical sub-expressions to the left and to the right of the && operator: a -> tsize && a-> tsize media library.c 1076А теперь родное и милое сердцу каждого программиста - лишние точки с запятой. Вот первыйфрагмент:int settings_default(void){ ... for(i=0; i<16; i++); for(j=0; j<32; j++) { settings.conversion.equalizer_bands.boost[i][j] = 0.0; settings.conversion.equalizer_bands.preamp[i] = 0.0; }}Сообщение PVS-Studio и местоположение коде:V529 Odd semicolon ; after for operator. settings.c 483Второй фрагмент:int trans_rest(transcoder_settings *trans){ ... for(i=0; i<16; i++); { trans->eq.eq.preamp[i] = 0.0; for(j=0; j<32; j++) { trans->eq.eq.boost[i][j] = 0.0; } }}Сообщение PVS-Studio и местоположение коде:
  3. 3. V529 Odd semicolon ; after for operator. settings.c 913Есть еще третий и четвертый фрагмент с ;. Но приводить здесь не буду. Все однотипно инеинтересно.Дальше не совсем ошибка, но почти. Вместо функции _beginthreadex используется CreateThread.Вызовов CreateThread в Fennec несколько, но приведу только один пример:t_sys_thread_handle sys_thread_call(t_sys_thread_function cfunc){ unsigned long tpr = 0; unsigned long tid = 0; return (t_sys_thread_handle) CreateThread(0, 0, cfunc, &tpr, 0,&tid);}Предупреждение PVS-Studio и местоположение коде:V513 Use _beginthreadex/_endthreadex functions instead of CreateThread/ExitThread functions.system.c 331Вдаваться сейчас вглубь вопроса, почему следует использовать _beginthreadex/_endthreadexвместо CreateThread/ExitThread, не буду. Напишу совсем кратко, а подробные обсужденияданного вопроса можно почитать здесь, здесь и здесь.В священном писании (в MSDN) сказано:A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and_endthreadex functions for thread management rather than CreateThread and ExitThread; this requiresthe use of the multi-threaded version of the CRT. If a thread created using CreateThread calls the CRT,the CRT may terminate the process in low-memory conditions.В общем лучше подстраховаться и всегда вызывать именно _beginthreadex/_endthreadex. Кстатиименно так рекомендует поступать и Джеффри Рихтера в шестой главе "Windows дляпрофессионалов: создание эффективных Win32-приложений с учетом специфики 64-разряднойверсии Windows" / Пер. с англ. - 4-е изд.Обнаружилось несколько неудачных использований функции memset. Кстати, я до недавнеговремени думал, что беспокойство, связанное с использованием memset, memcmp, memcpy – делопрошлого. Мол, это раньше так писали, но сейчас все знают про опасности, аккуратны с этимифункциями, используют sizeof(), используют контейнеры из STL и так далее. А сейчас все розовое имягкое. Оказывается, что нет. Я за последний месяц столько ляпов с этими функцияминасмотрелся. Так что все эти ошибки по-прежнему цветут и пахнут.Вернемся в Fennec. Первый memset:#define uinput_size 1024typedef wchar_t letter;letter uinput_text[uinput_size];
  4. 4. string basewindows_getuserinput(const string title, const string cap, const string dtxt){ memset(uinput_text, 0, uinput_size); ...}Диагностика PVS-Studio и местоположение коде:V512 A call of the memset function will lead to a buffer overflow or underflow. base windows.c 151На первый взгляд с "memset(uinput_text, 0, uinput_size);" все хорошо. И возможно даже и былохорошо, в те времена, когда тип letter был char. Но теперь это wchar_t и как результат мычистим только половину буфера.Второй неудачный memset:typedef wchar_t letter;letter name[30];int Conv_EqualizerProc(HWND hwnd,UINT uMsg, WPARAM wParam,LPARAM lParam){ ... memset(eqp.name, 0, 30); ...}Воистину магические числа – это зло. Вроде и не сложно написать "sizeof(eqp.name)". Но упорноне пишем и продолжаем вновь и вновь отстреливать себе ногу :).Диагностика PVS-Studio и местоположение коде:V512 A call of the memset function will lead to a buffer overflow or underflow. base windows.c 2892Ну и еще в одном месте такая шибка есть:V512 A call of the memset function will lead to a buffer overflow or underflow. transcode settings.c588
  5. 5. Возможно, иногда в каких-то программах вы замечали, что диалоги открытия/сохранения файловработают со странностями или в полях доступных расширений присутствует какая-то чушь. Сейчасвы узнаете, откуда у этого растут ноги.В Windows API есть структуры, в которых указатели на строки должны заканчиваться двойнымнулем. Наиболее используемым является член lpstrFilter в структуре OPENFILENAME. Этотпараметр на самом деле указывает на набор строк, разделенных символом 0. А для того чтобыузнать, что строки закончились и нужны два нуля в конце.Вот только это очень просто забыть. Фрагмент кода:int JoiningProc(HWND hwnd,UINT uMsg, WPARAM wParam,LPARAM lParam){ ... OPENFILENAME lofn; memset(&lofn, 0, sizeof(lofn)); ... lofn.lpstrFilter = uni("All Files (*.*)0*.*"); ...}Сообщение PVS-Studio и местоположение коде:V540 Member lpstrFilter should point to string terminated by two 0 characters. base windows.c 5309Будет диалог работать нормально или нет, зависит от того, что будет расположено в памяти послестроки "All Files (*.*)0*.*". По правильному здесь следовало написать "All Files (*.*)0*.*0". Одинноль явно указали мы, еще один ноль добавит компилятор.Аналогичная беда и с другими диалогами.int callback_presets_dialog(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ ... // SAVE OPENFILENAME lofn; memset(&lofn, 0, sizeof(lofn)); ...
  6. 6. lofn.lpstrFilter = uni("Equalizer Preset (*.feq)0*.feq"); ... ... // LOAD ... lofn.lpstrFilter = uni("Equalizer Preset (*.feq)0*.feq"); ...}int localsf_show_save_playlist(void){ OPENFILENAME lofn; memset(&lofn, 0, sizeof(lofn)); ... lofn.lpstrFilter = uni("Text file (*.txt)0*.txt0M3U file0*.m3u"); ...}Диагностика PVS-Studio и местоположение в коде:V540 Member lpstrFilter should point to string terminated by two 0 characters. base windows.c 986V540 Member lpstrFilter should point to string terminated by two 0 characters. base windows.c 1039V540 Member lpstrFilter should point to string terminated by two 0 characters. shared functions.c360Теперь подозрительная функция. Очень подозрительная. Впрочем, действительно тут ошибка илипросто неудачно написано, я не знаю:unsigned long ml_cache_getcurrent_item(void){ if(!mode_ml) return skin.shared->audio.output.playlist.getcurrentindex(); else return skin.shared->audio.output.playlist.getcurrentindex();}
  7. 7. Диагностика PVS-Studio и местоположение в коде:V523 The then statement is equivalent to the else statement. media library window.c 430Я не стал заниматься анализом разнообразный модулей расширений, идущих вместе с Fennec. Нотам не меньше разных грустных мест. Приведу только пару примеров. Фрагмент кода из проектаCodec ACC.void MP4RtpHintTrack::GetPayload(...){ ... if (pSlash != NULL) { pSlash++; if (pSlash != 0) { length = strlen(pRtpMap) - (pSlash - pRtpMap); *ppEncodingParams = (char *)MP4Calloc(length + 1); strncpy(*ppEncodingParams, pSlash, length); }}Как следует из диагностического сообщения PVS-Studio:V528 It is odd that pointer to char type is compared with the 0 value. Probably meant: *pSlash !=0. rtphint.cpp 346здесь забыли разыменовать указатель. Получается, что мы делаем бессмысленное сравнениеуказателя с 0. Должно было быть: "if (*pSlash != 0)".Фрагмент кода из проекта Decoder Mpeg Audio:void* tag_write_setframe(char *tmem, const char *tid, const string dstr){ ... if(lset) { fhead[11] = 0; fhead[12] = 0; fhead[13] = 0;
  8. 8. fhead[13] = 0; } ...}Диагностическое сообщение PVS-Studio и местоположение в коде:V525 The code containing the collection of similar blocks. Check items 11, 12, 13, 13 in lines 716,717, 718, 719. id3 editor.c 716Вот оно зло метода Copy-Paste :).В целом на проекте Fennec Media Project анализ общего назначения в PVS-Studio показал себяочень хорошо. Анализ был выполнен с низким процентом ложных срабатываний. Всего PVS-Studioуказал на 31 фрагмент кода. При этом в 19 местах код действительно следует поправить.Теперь перейдем к проекту qutIM.Вот с эти проектом PVS-Studio потерпел поражение. Несмотря на то, что проект достаточнокрупный (около 200 тысяч сток), анализатор PVS-Studio не смог выявить в нем ошибок. Хотя ониконечно есть. Они везде и всегда есть :). И разработчики qutIM с этим не спорят, так как внекоторых случаях qutIM умудряется падать.Приходится засчитать одно очко "команде ошибок".Что это означает? Это означает:1) Проект qutIM очень качественный проект. И хотя он тоже содержит ошибки, но они весьмаредки и слишком высокого уровня для статического анализа (по крайней мере, для PVS-Studio).2) PVS-Studio предстоит еще долгий путь развития и обучения более высокоуровневымдиагностикам. Теперь стало более очевидно к чему стремиться. Цель - найти в qutIM хотя быпарочку настоящих ошибок.Выдал ли что-то PVS-Studio для проекта qutIM? Выдал. Но немного и почти все ложныесрабатывания. Их представляющего хоть какой-то интерес, можно выделить только следующее.A) Используются функции CreateThread.B) Найдено несколько странных функций. Потом один из авторов qutIM сообщил, что это забытыезаглушки. Странность в том, что одна имеет имя save(), другая cancel(), но их содержимоеидентично:void XSettingsWindow::save(){ QWidget *c = p->stackedWidget->currentWidget(); while (p->modifiedWidgets.count()) {
  9. 9. SettingsWidget *widget = p->modifiedWidgets.takeFirst(); widget->save(); if (widget != c) widget->deleteLater(); } p->buttonBox->close();}void XSettingsWindow::cancel(){ QWidget *c = p->stackedWidget->currentWidget(); while (p->modifiedWidgets.count()) { SettingsWidget *widget = p->modifiedWidgets.takeFirst(); widget->save(); if (widget != c) widget->deleteLater(); } p->buttonBox->close();}Диагностика PVS-Studio:V524 It is odd that the cancel function is fully equivalent to the save function (xsettingswindow.cpp,line 256). xsettingswindow.cpp 268Надеюсь было интересно, и вы скоро захотите попробовать PVS-Studio 4.00 Beta. Конечно, PVS-Studio пока находит мало ошибок общего вида, но ведь это только начало. При этом исправлениедаже одной единственной ошибки на этапе кодирования может сэкономить массу нервовзаказчикам, тестерам и программистам.

×