• Like
PVS-Studio vs Chromium
Upcoming SlideShare
Loading in...5
×

PVS-Studio vs Chromium

  • 312 views
Uploaded on

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

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

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
312
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
1
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. PVS-Studio vs ChromiumАвтор: Андрей КарповДата: 23.05.2011АннотацияВ этот раз победу одержало добро. А вернее, исходные коды проекта Chromium Chromium - один Chromium.из лучших проектов, который мы проверяли с помощью PVS-Studio.Chromium - веб-браузер с открытым исходным кодом, разработанный компанией Google и браузерпредназначенный для предоставления пользователям быстрого и безопасного доступа вИнтернет. На основе Chromium создаётся браузер Google Chrome. При этом Chromium является рипредварительной версией Google Chrome, а также ряд других альтернативных веб ряда веб-обозревателей.С точки зрения программиста Chromium - это решение (solution), состоящее из 473 проектов.Общий объем исходного кода на я языке C/C++ составляет около 460 мегабайт Количество строк мегабайт.посчитать затрудняюсь.В эти 460 мегабайт входит большое количество различных библиотек. Если их отделить, тоостанется около 155 мегабайт. Существенно меньше, но всё равно много. Тем более всёотносительно. Многие из эти библиотек сделаны разработчиками Chromium в рамках задачи ногие этихсоздания Chromium. Хотя такие библиотеки живут сами по себе, их можно вполне отнести к отясамому браузеру.Chromium стал самым большим и самым качественным проектом с которы я познакомился в проектом, которымходе испытаний PVS-Studio. При работе с проектом Chromium было на самом деле не оченьпонятно, кто кого проверяет. Мы нашли и исправили в PVS-Studio несколько ошибок, связанных санализом Си++ файлов и с поддержкой специфичной структуры проекта проекта.О качестве исходного кода Chromium говорит множество моментов и используемых приемов.Например, большинство программистов для определения количества элементов в массивеиспользуют следующую конструкцию конструкцию:
  • 2. int XX[] = { 1, 2, 3, 4 };size_t N = sizeof(XX) / sizeof(XX[0]);Обычно это оформляют в макрос следующего вида:#define count_of(arg) (sizeof(arg) / sizeof(arg[0]))Это вполне работоспособный и полезный макрос. Я и сам, если честно, всегда именно такиммакросом и пользовался. Однако он может послужить причиной ошибки, так как ему случайноможно подсунуть простой указатель, и он не будет возражать по этому поводу. Поясню напримере:void Test(int C[3]){ int A[3]; int *B = Foo(); size_t x = count_of(A); // Ok x = count_of(B); // Error x = count_of(C); // Error}Конструкция count_of(A) работает корректно и возвращает количество элементов в массиве A,равное трём.Но если случайно применить count_of() к указателю, то результатом будет малоосмысленноезначение. Беда в том, что макрос никак не предупредит программиста о странной конструкциивида count_of(B). Происходит деление размера указателя на размер элемента массива. Подобнаяситуация кажется надуманной и искусственной, но я встречал её в реальных приложениях. Вкачестве примера приведу код из проекта Miranda IM:#define SIZEOF(X) (sizeof(X)/sizeof(X[0]))int Cache_GetLineText(..., LPTSTR text, int text_size, ...){ ... tmi.printDateTime(pdnce->hTimeZone, _T("t"), text, SIZEOF(text), 0); ...}Так что подобные ошибки вполне имеют место быть и хорошо бы иметь возможность защититьсяот них. Ещё проще ошибиться, если попробовать вычислить размер массива, переданного вкачестве аргумента:
  • 3. void Test(int C[3]){ x = count_of(C); // Error}Согласно стандарту языка Си++, переменная C представляет собой обыкновенный указатель, авовсе не массив. В результате в программах нередко можно встретить обработку только частипереданного массива.Раз уж зашла речь о таких ошибках, подскажу приём, как же можно узнать размер переданногомассива. Для этого необходимо передать его по ссылке:void Test(int (&C)[3]){ x = count_of(C); // Ok}Теперь результатом выражения count_of(C) будет значение 3.Вернемся к Chromium. В нем используется такой макрос, который позволяет избежать описанныхвыше ошибок. Вот его реализация:template <typename T, size_t N>char (&ArraySizeHelper(T (&array)[N]))[N];#define arraysize(array) (sizeof(ArraySizeHelper(array)))Идея этого магического заклинания в следующем. Шаблонная функция ArraySizeHelper принимаетна вход массив произвольного типа длиной N. При этом функция возвращает ссылку на массивдлиной N, состоящий из элементов типа char. Реализация функции отсутствует, так как она ненужна. Для оператора sizeof() достаточно только объявления функции ArraySizeHelper. В макросеarraysize вычисляется размер возвращаемого функцией ArraySizeHelper массива байт. Этотразмер и есть количество элементов в массиве, длину которого мы хотим вычислить.Если у вас опухла голова, то можете поверить мне на слово, что это работает. Причем работаетгораздо лучше, чем рассмотренный ранее макрос count_of(). Так как функция ArraySizeHelperпринимает массив по ссылке, то в неё невозможно передать простой указатель. Напишемтестовый код:template <typename T, size_t N>char (&ArraySizeHelper(T (&array)[N]))[N];#define arraysize(array) (sizeof(ArraySizeHelper(array)))
  • 4. void Test(int C[3]){ int A[3]; int *B = Foo(); size_t x = arraysize(A); // Ok x = arraysize(B); // Ошибка компиляции x = arraysize(C); // Ошибка компиляции}Ошибочный код просто не будет скомпилирован. По-моему это великолепно, если можнопредупредить потенциальную ошибку ещё на этапе компиляции. Это замечательный пример,отражающий качество подхода к программированию. Моё уважение к разработчикам в Google.Приведу еще один пример, совершенно другого плана, но также говорящий о качестве кода.if (!file_util::Delete(db_name, false) && !file_util::Delete(db_name, false)) { // Try to delete twice. If we cant, fail. LOG(ERROR) << "unable to delete old TopSites file"; return false;}Этот код может показаться многим программистам странным. Какой смысл два раза пробоватьудалить файл? А смысл есть. Тот, кто его писал, достиг просветления и суть бытия программ. Файлоднозначно удаляется или не удаляется только в учебниках и абстрактном мире. В реальнойсистеме бывает так, что файл только что нельзя было удалить и мгновением позже - можно.Причиной тому могут быть антивирусы, вирусы, системы контроля версий, и бог весть что ещё.Программисты часто не задумываются о подобных ситуациях. Они мыслят так, раз не удалостьудалить файл, то значит и не получится. Но, если хочется сделать хорошо и не мусорить вкаталогах, нужно учитывать эти посторонние воздействия. Я сталкивался с ровно такой жеситуацией, когда файл не удаляется один раз на 1000 запусков. И решение было ровно таким же.Ну, разве что я ещё на всякий случай Sleep(0) вставил посередине.А что же проверка с помощью PVS-Studio? Код Chromium, пожалуй, самый качественный код,который я видел. Это подтверждается очень низкой плотностью ошибок, которые мы смоглинайти. Если брать количественно, то ошибок конечно много. А вот если поделить это количествоошибок на объем кода, то, получается, что их практически нет. Какие это ошибки? Самыеобыкновенные. Несколько примеров:V512 A call of the memset function will lead to underflow of thebuffer (exploded). platform time_win.cc 116
  • 5. void NaCl::Time::Explode(bool is_local, Exploded* exploded) const { ... ZeroMemory(exploded, sizeof(exploded)); ...}Опечатки делают все. Здесь просто забыли звёздочку. Должно было быть: sizeof(*exploded).V502 Perhaps the ?: operator works in a different way than it wasexpected. The ?: operator has a lower priority than the -operator. views custom_frame_view.cc 400static const int kClientEdgeThickness;int height() const;bool ShouldShowClientEdge() const;void CustomFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) { ... int edge_height = titlebar_bottom->height() - ShouldShowClientEdge() ? kClientEdgeThickness : 0; ...}Коварный оператор "?:" имеет более низкий приоритет, чем вычитание. Здесь нужныдополнительные скобочки:int edge_height = titlebar_bottom->height() - (ShouldShowClientEdge() ? kClientEdgeThickness : 0);Проверка, которая не имеет смысла.V547 Expression count < 0 is always false. Unsigned type valueis never < 0. ncdecode_tablegen ncdecode_tablegen.c 197
  • 6. static void CharAdvance(char** buffer, size_t* buffer_size,size_t count) { if (count < 0) { NaClFatal("Unable to advance buffer by count!"); } else { ...}Условие "count < 0" всегда ложно. Защита не сработает и потенциально какой-то буфер можетбыть переполнен. Это, кстати, пример, как статические анализаторы могут быть использованы дляпоиска уязвимостей. Злоумышленник может быстро выделить для себя те участки кода, которыесодержат ошибки для их дальнейшего, более внимательного анализа. А вот еще один фрагменткода, который может быть интересен с точки зрения безопасности:V511 The sizeof() operator returns size of the pointer, and notof the array, in sizeof (salt) expression. commonvisitedlink_common.cc 84void MD5Update(MD5Context* context, const void* buf, size_t len);VisitedLinkCommon::FingerprintVisitedLinkCommon::ComputeURLFingerprint( ... const uint8 salt[LINK_SALT_LENGTH]){ ... MD5Update(&ctx, salt, sizeof(salt)); ...}Функция MD5Update() обработает столько байт, сколько занимает указатель. Не потенциальная лидыра в плане шифрования данных? Я не знаю есть ли во всём этом какая-то опасность или нет. Нос точки зрения злоумышленников - это однозначно интересное место для более подробногоизучения.Корректный код должен выглядеть так:
  • 7. MD5Update(&ctx, salt, sizeof(salt[0]) * LINK_SALT_LENGTH);Или вот так:VisitedLinkCommon::FingerprintVisitedLinkCommon::ComputeURLFingerprint( ... const uint8 (&salt)[LINK_SALT_LENGTH]){ ... MD5Update(&ctx, salt, sizeof(salt)); ...}Еще пример про опечатку:V501 There are identical sub-expressions host !=buzz::XmlConstants::str_empty () to the left and to the right of the&& operator. chromoting_jingle_glue iq_request.cc 248void JingleInfoRequest::OnResponse(const buzz::XmlElement* stanza) { ... std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST); std::string port_str = server->Attr(buzz::QN_JINGLE_INFO_UDP); if (host != buzz::STR_EMPTY && host != buzz::STR_EMPTY) { ...}На самом деле нужно проверить и переменную port_str:if (host != buzz::STR_EMPTY && port_str != buzz::STR_EMPTY) {Немного из классики:V530 The return value of function empty is required to be utilized.chrome_frame_npapi np_proxy_service.cc 293
  • 8. bool NpProxyService::GetProxyValueJSONString(std::string* output) { DCHECK(output); output->empty(); ...}Должно быть: output->clear();А вот даже есть и работа с нулевым указателем:V522 Dereferencing of the null pointer plugin_instance might takeplace. Check the logical condition. chrome_frame_npapichrome_frame_npapi.cc 517bool ChromeFrameNPAPI::Invoke(...){ ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(header); if (!plugin_instance && (plugin_instance->automation_client_.get())) return false; ...}Ещё один пример проверки, которая никогда не сработает:V547 Expression current_idle_time < 0 is always false. Unsignedtype value is never < 0. browser idle_win.cc 23IdleState CalculateIdleState(unsigned int idle_threshold) { ... DWORD current_idle_time = 0; ...
  • 9. // Will go -ve if we have been idle for a long time (2gb seconds). if (current_idle_time < 0) current_idle_time = INT_MAX; ...}Пожалуй, следует остановиться. Я могу продолжать дальше, но это становится скучно. И это ведьтолько то, что относится к самому Chromium. А ведь есть ещё тесты с ошибками наподобие этой:V554 Incorrect use of auto_ptr. The memory allocated with new []will be cleaned using delete. interactive_ui_testsaccessibility_win_browsertest.cc 306void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) { ... auto_ptr<VARIANT> child_array(new VARIANT[child_count]); ...}И огромное количество библиотек, на которых и строится Chromium. Причем размер библиотекзначительно больше, чем размер самого Chromium. А там тоже множество интересных мест вкоде. Понятно, что возможно код с ошибками нигде не используется, но ошибка от этого неперестает быть ошибкой. Один первый попавшийся пример (библиотка ICU):V547 Expression * string != 0 || * string != _ is always true.Probably the && operator should be used here. icui18n ucol_sit.cpp242U_CDECL_BEGIN static const char* U_CALLCONV_processVariableTop(...){ ... if(i == locElementCapacity && (*string != 0 || *string != _)) {
  • 10. *status = U_BUFFER_OVERFLOW_ERROR; } ...}Выражение "(*string != 0 || *string != _)" всегда истинно. Видимо должно быть: (*string == 0 ||*string == _).ВыводPVS-Studio потерпел поражение. Код Chromium - один из лучших кодов, которые мыанализировали. Мы практически ничего не нашли в Chromium. Вернее, мы нашли очень дажемного ошибок, и в этой статье мы показали только небольшую их часть. Но если учесть, что все этиошибки размазаны по исходному коду объемом 460 мегабайт, то, получается, что их практическинет.P.S.Отвечаю на вопрос: сообщим ли мы разработчикам Chromium о найденных ошибках? Нет, несообщим. Это очень большой объем работы, который мы не можем позволить себе выполнятьбесплатно. Проверка Chromium это совсем не проверка Miranda IM или проверка Ultimate Toolbox.Это большая работа, необходимо внимательно изучить все сообщения и разобратьсядействительно ли это ошибка или нет. Для этого необходимо ориентироваться в проекте. Мыперешлём перевод этой статьи разработчикам Chromium, и если им будет интересно, они смогутсами выполнить анализ проекта и проанализировать все диагностические сообщения. Да, дляэтого им будет необходимо приобрести PVS-Studio. Но любой отдел Google легко может себе этопозволить.P.P.S.Нет, мы не жадные. Мы готовы помогать открытым проектам, наподобие FlylinkDC++. Но эторазные вещи.