Принципы работы статического анализатора кода PVS-Studio

Post on 16-Feb-2017

43 views 0 download

Transcript of Принципы работы статического анализатора кода PVS-Studio

Принципы работы статического анализатора

кода PVS-StudioАвторы:к.т.н. Евгений Рыжков, evg@viva64.comк.ф.-м.н. Андрей Карпов, karpov@viva64.com

ООО "СиПроВер" (www.viva64.com)• Занимаемся разработкой, продвижением и продажей

собственного программного продукта.• Офис: г. Тула, 200 км от Москвы.• Штат: 24 человека

PVS-Studio• Более 320 диагностик для C, C++• Более 120 диагностик для C#• Windows• Linux• Плагин для Visual Studio• Быстрый старт (мониторинг компиляции)• SonarQube

Наши достижения• Для рекламы мы проверяем открытые проекты. На данный момент мы

проверили около 270 проектов.

• Побочный эффект: нашли более 10000 ошибок в открытых проектах, не задаваясь такой целью.

• В среднем 40 ошибок на проект – вроде немного.

• Важно ещё раз подчеркнуть, что это побочный эффект. У нас нет цели найти как можно больше ошибок. Часто мы останавливаемся, когда нашли достаточное количество дефектов в проекте, чтобы написать статью.

• Вывод: легко проверять даже незнакомые проекты и находить в них ошибки.

В начале, что мы НЕ ИСПОЛЬЗУЕМ

Мы не используем математический аппарат грамматик• Анализатор работает на более высоком уровне• Анализируется дерево разбора• Для построения дерева мы опираемся на уже существующие

компоненты:• Внешний препроцессор• Библиотека OpenC++, которую мы дорабатываем с развитием C++ по

мере необходимости (собственно от OpenC++ уже ничего не осталось)• При работе с C# кодом мы опираемся на Roslyn

Мы не используем методики доказательства правильности программ (program proof).• PVS-Studio не имеет ничего общего с Prototype Verification System

(PVS) http://pvs.csl.sri.com/• PVS-Studio сокращение от OOO "Program Verification Systems"

(ООО "Системы программной верификации")

Мы не используем поиск подстрок (string matching) и регулярные выражения (regular expressions)

• Тупиковый путь• Подводит даже в простейших ситуациях• Пример: if (A+B == A+B)• A+B == B+A• A+(B) == (A)+B• ((A+B)) == A+B

• Более фатально: типы, размеры объектов, наследование, значения переменных и так далее

Что МЫ используем

Детали анализа C++ и C# кода разнятся, но мы не будем делать в докладе уточнения.

Pattern-based analysis• Сопоставление с шаблоном на основе дерева разбора• Применяется для поиска мест в исходном коде, которые похожи

на известные шаблоны кода с ошибкой• Сложность диагностик крайне разнится• В некоторых случаях работают эмпирические алгоритмы

if ((*path)[0]->e->dest->loop_father != path->last()->e->....){ delete_jump_thread_path (path); e->aux = NULL; ei_next (&ei;);}else{ delete_jump_thread_path (path); e->aux = NULL; ei_next (&ei;);}

Простой случай: copy-paste

Проект GCC

V523 The 'then' statement is equivalent to the 'else' statement. tree-ssa-threadupdate.c 2596

Средний случай: проверка не той переменной

public override Predicate JoinWith(Predicate other){ var right = other as PredicateNullness; if (other != null) { if (this.value == right.value) {

Проект CodeContracts

V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'other', 'right'. CallerInvariant.cs 189

Сложный случай: неудачный макрос

#define ICB2400_VPINFO_PORT_OFF(chan) \ (ICB2400_VPINFO_OFF + \ sizeof (isp_icb_2400_vpinfo_t) + \ (chan * ICB2400_VPOPT_WRITE_SIZE))

off += ICB2400_VPINFO_PORT_OFF(chan - 1);

V733 It is possible that macro expansion resulted in incorrect evaluation order. Check expression: chan - 1 * 20. isp.c 2301

Проект FreeBSD

Type inference• Вывод типов на основе семантической модели программы

позволяет анализатору иметь полную информацию о всех переменных и выражениях, встречающихся в коде• Важно для выявления ошибок• Важно для исключений• Особенно важна информация о классах

Типы важны для выявления ошибокПроект Cocos2d-x

WCHAR *gai_strerrorW(int ecode);

#define gai_strerror gai_strerrorW

fprintf(stderr, "net_listen error for %s: %s", serv, gai_strerror(n)); V576 Incorrect format. Consider checking the fourth actual argument of the 'fprintf' function. The pointer to string of char type symbols is expected. ccconsole.cpp 341

Типы важны для исключений

// volatile переменная присваивается сама себеvolatile int *ptr;....*ptr = *ptr; // Нет срабатывания V570

Особенно важна информация о классах: например, иерархия наследования

class sg_throwable : public std::exception { .... };class sg_exception : public sg_throwable { .... };

if (!aInstall) { sg_exception("missing argument to scheduleToUpdate");}V596 The object was created but it is not being used. The 'throw' keyword could be missing: throw sg_exception(FOO); root.cxx 239

Проект FlightGear

Symbolic execution• Символьное выполнение позволяет вычислять значения

переменных, которые могут приводить к ошибкам, производить проверку диапазонов (range checking) значений• Один из самых важных механизмов:

• Переполнения• Утечки памяти• Выход за границы массива• Нулевые указатели / ссылки• Бессмысленные условия• Деление на 0• И так далее

Значения переменных: размер массива, индексыHandle<YieldTermStructure> md0Yts() { double q6mh[] = { 0.0001,0.0001,0.0001,0.0003,0.00055,0.0009,0.0014,0.0019, 0.0025,0.0031,0.00325,0.00313,0.0031,0.00307,0.00309, ........................................................ 0.02336,0.02407,0.0245 }; 60 элементов .... for(int i=0;i<10+18+37;i++) { i < 65 q6m.push_back( boost::shared_ptr<Quote>(new SimpleQuote(q6mh[i])));

Проект QuantLib

V557 Array overrun is possible. The value of 'i' index could reach 64. markovfunctional.cpp 176

Значения переменных: использование условий для определения диапазона

std::string rangeTypeLabel(int idx){ const char* rangeTypeLabels[] = {"Self", "Touch", "Target"}; if (idx >= 0 && idx <= 3) return rangeTypeLabels[idx]; else return "Invalid";}

V557 Array overrun is possible. The value of 'idx' index could reach 3. esmtool labels.cpp 502

Проект OpenMW

Значения функцийstatic inline size_t UnboxedTypeSize(JSValueType type){ switch (type) { ....... default: return 0; }}

Minstruction *loadUnboxedProperty(size_t offset, ....){ size_t index = offset / UnboxedTypeSize(unboxedType);

Проект Thunderbird

V609 Divide by zero. Denominator range [0..8]. ionbuilder.cpp 10922

Значения переменных: указатели / ссылкиif (providerName == null){ ProviderNotFoundException e = new ProviderNotFoundException( providerName.ToString(), SessionStateCategory.CmdletProvider, "ProviderNotFound", SessionStateStrings.ProviderNotFound); throw e;V3080 Possible null dereference. Consider inspecting 'providerName'. System.Management.Automation SessionStateProviderAPIs.cs 1004

Проект PowerShell

Method annotations• Аннотирование методов предоставляет больше информации об

используемых методах, чем может быть получено путём анализа только их сигнатуры.• C/C++. На данный момент проаннотировано 6570 функций

(стандартные библиотеки C и C++, POSIX, MFC, Qt, ZLib и так далее).• C#. На данный момент проаннотировано 920 функций.

Пример аннотирования функции memcmp

C_"int memcmp(const void *buf1, const void *buf2, size_t count);"ADD(REENTERABLE | RET_USE | F_MEMCMP | STRCMP | HARD_TEST | INT_STATUS, nullptr, nullptr, "memcmp", POINTER_1, POINTER_2, BYTE_COUNT);• C_ - вспомогательный механизм контроля аннотаций (юнит-тесты)• REENTERABLE – повторный вызов с теми же аргументами даст тот-же результат• RET_USE – результат должен быть использован• F_MEMCMP – запуск определённых проверок выхода за границы буфера• STR_CMP – При равенстве функция возвращает 0.• HARD_TEST – особая функция. Некоторые программисты определяют собственные такие

функции в своих namespace. Не учитывать namespace.• INT_STATUS – результат явно сравнивать с 1 или -1.• POINTER_1, POINTER_2 – указатели должны быть не нулевыми и разными.• BYTE_COUNT – параметр задает количество байт и должен быть больше 0.

Аннотация memcmp: проверка результата

bool operator()(const GUID& _Key1, const GUID& _Key2) const{ return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1;}

Проект CoreCLR

V698 Expression 'memcmp(....) == -1' is incorrect. This function can return not only the value '-1', but any negative value. Consider using 'memcmp(....) < 0' instead. sos util.cpp 142

Аннотация memcmp: хранение результата

Проект Firebird

V642 Saving the 'memcmp' function result inside the 'short' type variable is inappropriate. The significant bits could be lost breaking the program's logic. texttype.cpp 3

SSHORT TextType::compare(ULONG len1, const UCHAR* str1, ULONG len2, const UCHAR* str2){ .... SSHORT cmp = memcmp(str1, str2, MIN(len1, len2)); if (cmp == 0) cmp = (len1 < len2 ? -1 : (len1 > len2 ? 1 : 0)); return cmp;}

Аннотация memcmp: неверный аргументПроект GLG3D

V575 The 'memcmp' function processes '0' elements. Inspect the 'third' argument. graphics3D matrix4.cpp 269

bool Matrix4::operator==(const Matrix4& other) const { if (memcmp(this, &other, sizeof(Matrix4) == 0)) { return true; } ...}

static intpsymbol_compare (const void *addr1, const void *addr2, int length){ struct partial_symbol *sym1 = (struct partial_symbol *) addr1; struct partial_symbol *sym2 = (struct partial_symbol *) addr2;

return (memcmp (&sym1->ginfo.value, &sym1->ginfo.value, sizeof (sym1->ginfo.value)) == 0 && .......

Аннотация memcmp: разные аргументы

Проект GDB

V549 The first argument of 'memcmp' function is equal to the second argument. psymtab.c 1580

dst_s_read_private_key_file(....){ .... if (memcmp(in_buff, "Private-key-format: v", 20) != 0) goto fail; ....} 21 символ

Аннотация memcmp: buffer underrun

Проект Haiku

V512 A call of the 'memcmp' function will lead to underflow of the buffer '"Private-key-format: v"'. dst_api.c 858

Аннотация memcmp: нет состоянияПроект PHP

V501 There are identical sub-expressions '!memcmp("auto", charset_hint, 4)' to the left and to the right of the '||' operator. html.c 396

if ((len == 4) /* sizeof (none|auto|pass) */ && (!memcmp("pass", charset_hint, 4) || !memcmp("auto", charset_hint, 4) || !memcmp("auto", charset_hint, 4)))

Аннотирование пользовательских функций• Практически отсутствует (кроме отдельных элементов, как

собственной функции printf)• Развивать механизм нет смысла• В больших проектах никто не станет тратить месяцы на разметку• Анализатор должен работать сразу

Тестирование анализатора• Тестирование анализатора важнейшая часть процесса его

разработки• Самое сложное в статическом анализе: не ругаться• Большая тестовая база:• C++ Windows (Visual C++): 120 проектов• C++ Linux (GCC): ещё 34 проекта• C# Windows: 54 проекта

Вышлем по почте более подробный вариант презентации• Написать письмо: support@viva64.com• Подписаться на твиттер: @Code_Analysis

• Скачать PVS-Studio для Windows:http://www.viva64.com/ru/pvs-studio/

• Скачать PVS-Studio для Linux:http://www.viva64.com/ru/pvs-studio-download-linux/