Продукт PVS-Studio содержит набор правил статического анализа, предназначенный для выявления 64-битных ошибок в коде программ на языке C/C++/C++11.
Ранее этот набор правил поставлялся в виде отдельного решения под названием Viva64. Сейчас статический анализатор Viva64 включен в состав программного продукта PVS-Studio, а под названием Viva64 понимается соответствующий набор правил. Подробнее это отражено на странице заказа.
Набор специализированных правил Viva64 позволяет выявлять дефекты, возникающие при разработке новых 64-битных приложений или при миграции 32-битного кода на 64-битные системы. Следование рекомендациям анализатора также позволяет оптимизировать 64-битный код и сделать его более производительным.
Обнаружение 64-битных ошибок представляет собой выявление участков кода, которые корректно работают в 32-битных версиях программ и дают сбой в 64-битных программах. Сбой может проявить себя в 64-битной программе как ее зависание, аварийное завершение, замедление или непредсказуемое поведение. Рассмотрим некоторые примеры 64-битных дефектов, выявляемых PVS-Studio. Гораздо подробнее с примерами вы можете познакомиться в статье "Коллекция примеров 64-битных ошибок в реальных программах".
Пример 64-битной ошибки при работе с Windows API
HANDLE hFileMapping = CreateFileMapping(
(HANDLE) 0xFFFFFFFF,
NULL, PAGE_READWRITE, dwMaximumSizeHigh,
dwMaximumSizeLow, name);
Вместо корректной зарезервированной константы INVALID_HANDLE_VALUE используется магическое число 0xFFFFFFFF. Согласно правилам языка Си/Си++ значение 0xFFFFFFFF имеет тип "unsigned int", так как не может быть представлено типом "int". Соответственно, расширяясь до 64-битного типа, значение 0xFFFFFFFFu превращается в 0x00000000FFFFFFFFu. Это некорректно в Win64-программе, где константа INVALID_HANDLE_VALUE имеет значение 0xFFFFFFFFFFFFFFFF.
Пример 64-битной ошибки приводящей к ошибке доступа
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B);
printf("%in", *ptr); // Access violation
Согласно правилам языка Си++ в выражении "A+B" переменная A типа int приводится к типу unsigned. Происходит сложение A и B. В результате мы получаем значение 0xFFFFFFFF типа unsigned. Вычисляется выражение "ptr + 0xFFFFFFFFu". Результат зависит от размерности указателя на данной платформе. В 32-битной программе, выражение будет эквивалентно "ptr - 1" и мы успешно распечатаем число 3. В 64-битной программе к указателю прибавится значение 0xFFFFFFFFu, в результате чего указатель окажется далеко за пределами массива.
Пример 64-битной ошибки приводящий к зависанию
size_t Count = BigValue;
for (unsigned Index = 0; Index != Count; Index++)
{ ... }
Это цикл никогда не прекратится, если значение Count > UINT_MAX. Предположим, что на 32-битных системах этот код работал с количеством итераций менее значения UINT_MAX. Но 64-битный вариант программы может обрабатывать больше данных, и ему может потребоваться большее количество итераций. Поскольку значения переменной Index лежат в диапазоне [0..UINT_MAX], то условие "Index != Count" никогда не выполнится, что и приводит к бесконечному циклу.
Ошибки при смешанном использовании 32-битных и 64-битных типов
Часто легко забыть, что хотя выражение целиком имеет 64-битных тип, то части этого выражения могут иметь 32-битный тип, и в них возможны переполнения. Рассмотрим различные выражения и результат их выполнения:
int x = 100000;
int y = 100000;
int z = 100000;
intptr_t size = 1; // Результат:
intptr_t v1 = x * y * z; // -1530494976
intptr_t v2 = intptr_t(x) * y * z; // 1000000000000000
intptr_t v3 = x * y * intptr_t(z); // 141006540800000
intptr_t v4 = size * x * y * z; // 1000000000000000
intptr_t v5 = x * y * z * size; // -1530494976
intptr_t v6 = size * (x * y * z); // -1530494976
intptr_t v7 = size * (x * y) * z; // 141006540800000
intptr_t v8 = ((size * x) * y) * z; // 1000000000000000
intptr_t v9 = size * (x * (y * z)); // -1530494976
С точки зрения компиляторов подобные выражения безопасны, поскольку в них имеются только приведения 32-битых типов в 64-битные типы. Однако подобные конструкции могут содержать малозаметные ошибки, проявляющие себя на определенных наборах входных данных. Анализатор позволяет выявлять и изучать подобные потенциально опасные выражения.
Пример 64-битной ошибки при работе с виртуальными функциями
Одним из красивых примеров является использование неверных типов аргументов в объявлениях виртуальных функций. Часто это не чья-то неаккуратность, а "несчастный случай", где нет виноватых, но есть ошибка. Рассмотрим следующую ситуацию.
В старых версиях библиотеки MFC есть класс CWinApp, в котором имеется функция WinHelp:
class CWinApp {
...
virtual void WinHelp(DWORD dwData, UINT nCmd);
};
Для показа собственной справки в пользовательском приложении необходимо было эту функцию перекрыть:
class CSampleApp : public CWinApp {
...
virtual void WinHelp(DWORD dwData, UINT nCmd);
};
Затем появились 64-битные системы. Разработчикам MFC пришлось поменять интерфейс функции WinHelp (и некоторых других функций) так:
class CWinApp {
...
virtual void WinHelp(DWORD_PTR dwData, UINT nCmd);
};
В 32-битном режиме типы DWORD_PTR и DWORD совпадали, а вот в 64-битном нет. Естественно разработчики пользовательского приложения также должны сменить тип на DWORD_PTR, но чтобы это сделать, про это необходимо знать. В результате в 64-битной программе возникает ошибка, так как функция WinHelp в пользовательском классе не вызывается.
Пример необоснованного потребления памяти
struct MyStruct
{
bool m_bool;
char *m_pointer;
int m_int;
};
Создание подобных структур не является ошибкой. Однако если таких структур большое количество, то это может привести к значительному увеличению потребляемой памяти и замедлению работы программы. Эта структура занимает в 64-битной программе 24 байта, однако простой перестановкой полей ее можно сократить до 16 байт:
struct MyStructOpt
{
char *m_pointer;
int m_int;
bool m_bool;
};
Здесь были перечислены только некоторые виды 64-биных ошибок. Более подробно с ними вы можете познакомиться в статьях на нашем сайте и в уроках по разработке 64-битных приложений.
Преимущества использования статического анализа для выявления 64-битных дефектов
Разработка современных программ требует от программиста знания паттернов ошибок, возникающих при написании 64-битного исходного кода. Многие из 64-битных ошибок неочевидны и требуют от программиста большого опыта и повышенной внимательности. Набор правил Viva64 реализованный в PVS-Studio позволяет диагностировать данные виды ошибок, тем самым выполняя две функции: устраняет дефекты в программе и обучает программиста написанию корректного кода, учитывающего специфику 64-битных систем.
Использование PVS-Studio снижает риски, связанные с освоением новых 64-битных платформ, и позволяет с большей уверенностью определять сроки реализации 64-битных проектов. Более подробно с процесс оценки описан в тексте "Оценка стоимости процесса 64-битной миграции Си/Си++ приложений".
Использование инструмента PVS-Studio позволяет выпустить 64-битное приложение на рынок в 3-4 раза быстрее, чем без него.
Используемая методология статического анализа кода имеет существенные преимущества над другими видами анализа, так как позволяет охватить весь программный код. Процедура проверки кода не может как-либо повредить сам код. Процесс анализа полностью контролируется человеком, и именно он принимает решения о необходимости его модификации. С помощью PVS-Studio вы сможете выявить более 90% ошибок еще на этапе конструирования (работы с исходным кодом) 64-битных программ.
Инструмент PVS-Studio сопровождается большой базой знаний по разработке 64-битного кода (справочная система, статьи, примеры), которая позволит поднять уровень знаний программистов.