QEMU – достаточно известное приложение для эмуляции. Статический анализ может помочь разработчикам таких сложных проектов, как QEMU, отлавливать ошибки на раннем этапе и в целом повысить его качество и надёжность. В этой статье будет проверен исходный код приложения QEMU на потенциальные уязвимости и ошибки с помощью инструмента статического анализа PVS-Studio.
QEMU является свободным ПО, предназначенным для эмуляции аппаратного обеспечения различных платформ. Оно позволяет запускать приложения и операционные системы на отличающихся от целевой аппаратных платформах, например, приложение, написанное для MIPS запустить для архитектуры x86. QEMU также поддерживает эмуляцию разнообразных периферийных устройств, например, видеокарты, usb и т.д. Проект достаточно сложный и достойный внимания, именно такие проекты представляют интерес для статического анализа, поэтому было решено проверить его код с помощью PVS-Studio.
Об анализе
Исходный код проекта можно получить из зеркала на github. Проект достаточно объемный и может компилироваться для различных платформ. Для более легкой проверки кода воспользуемся системой мониторинга компиляции PVS-Studio. Эта система предназначена для очень простой интеграции статического анализа практически в любую сборочную платформу. Система основана на отслеживании вызовов компилятора во время сборки и позволяет собрать всю информацию для последующего анализа файлов. Другими словами, просто запускаем сборку, PVS-Studio собирает необходимую информацию, а после запускаем анализ — все просто. Подробности можно почитать по ссылке выше.
После проверки анализатор обнаружил множество потенциальных проблем. Для диагностик общего назначения (General Analysis) было получено: 1940 уровня High, 1996 уровня Medium, 9596 уровня Low. После просмотра всех предупреждений было решено остановиться на диагностиках для первого уровня достоверности (High). Таких предупреждений нашлось достаточно много (1940), но большая часть предупреждений либо однотипна, либо связана с многократным использованием подозрительного макроса. Для примера рассмотрим макрос g_new.
#define g_new(struct_type, n_structs)
_G_NEW (struct_type, n_structs, malloc)
#define _G_NEW(struct_type, n_structs, func) (struct_type *) (G_GNUC_EXTENSION ({ gsize __n = (gsize) (n_structs); gsize __s = sizeof (struct_type); gpointer __p; if (__s == 1) __p = g_##func (__n); else if (__builtin_constant_p (__n) && (__s == 0 || __n <= G_MAXSIZE / __s)) __p = g_##func (__n * __s); else __p = g_##func##_n (__n, __s); __p; }))
На каждое использование этого макроса анализатор выдает предупреждение V773 (Visibility scope of the '__p' pointer was exited without releasing the memory. A memory leak is possible). Макрос g_new определен в библиотеке glib, он использует макрос _G_NEW, а этот макрос в свою очередь использует другой макрос G_GNUC_EXTENSION, говорящий компилятору GCC пропускать предупреждения о нестандартном коде. Именно этот нестандартный код и вызывает предупреждение анализатора, обратите внимание на предпоследнюю строку. В целом же макрос является рабочим. Предупреждений этого типа нашлось 848 штук, то есть почти половина срабатываний приходится всего лишь на одно единственное место в коде.
Все эти лишние предупреждения легко убрать, используя настройки анализатора. Впрочем, конкретно данный случай, который встретился при написании статьи, является поводом нашей команды немного доработать логику анализатора для таких ситуаций.
Таким образом, большое количество предупреждений не всегда говорит о плохом качестве кода. Тем не менее есть некоторые по-настоящему подозрительные места. Что же, давайте приступим к разбору предупреждений.
Предупреждение N1
V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 2395, 2397. megasas.c 2395
#define MEGASAS_MAX_SGE 128 /* Firmware limit */
....
static void megasas_scsi_realize(PCIDevice *dev, Error **errp)
{
....
if (s->fw_sge >= MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE) {
....
} else if (s->fw_sge >= 128 - MFI_PASS_FRAME_SIZE) {
....
}
....
}
Любые использования "магических" чисел в коде всегда вызывают подозрение. Здесь два условия, и на первый взгляд они кажутся разными, но, если посмотреть значение макроса MEGASAS_MAX_SGE, то окажется, что условия дублируют друг друга. Скорее всего, здесь опечатка и вместо 128 должно стоять другое число. Конечно, это проблема всех "магических" чисел, достаточно просто опечататься при их использовании. Применение макросов и констант сильно помогает разработчику в этом случае.
Предупреждение N2
V523 The 'then' statement is equivalent to the 'else' statement. cp0_helper.c 383
target_ulong helper_mftc0_cause(CPUMIPSState *env)
{
....
CPUMIPSState *other = mips_cpu_map_tc(env, &other_tc);
if (other_tc == other->current_tc) {
tccause = other->CP0_Cause;
} else {
tccause = other->CP0_Cause;
}
....
}
В рассматриваемом коде тела then и else условного оператора идентичны. Здесь, скорее всего, copy-paste. Просто скопировали тело then ветвления, а исправить забыли. Можно предположить, что вместо объекта other необходимо было использовать env. Исправление этого подозрительного места могло бы выглядеть следующим образом:
if (other_tc == other->current_tc) {
tccause = other->CP0_Cause;
} else {
tccause = env->CP0_Cause;
}
Однозначно сказать, как должно быть на самом деле могут только разработчики этого кода. Еще похожее место:
- V523 The 'then' statement is equivalent to the 'else' statement. translate.c 641
Предупреждение N3
V547 Expression 'ret < 0' is always false. qcow2-cluster.c 1557
static int handle_dependencies(....)
{
....
if (end <= old_start || start >= old_end) {
....
} else {
if (bytes == 0 && *m) {
....
return 0; // <= 3
}
if (bytes == 0) {
....
return -EAGAIN; // <= 4
}
....
}
return 0; // <= 5
}
int qcow2_alloc_cluster_offset(BlockDriverState *bs, ....)
{
....
ret = handle_dependencies(bs, start, &cur_bytes, m);
if (ret == -EAGAIN) { // <= 2
....
} else if (ret < 0) { // <= 1
....
}
}
Здесь анализатор обнаружил, что условие (комментарий 1) никогда не выполнится. Значение переменной ret инициализируется результатом выполнения функции handle_dependencies, эта функция возвращает только 0 или -EAGAIN (комментарии 3, 4, 5). Чуть выше, в первом условии, мы проверили значение ret на -EAGAIN (комментарий 2), поэтому результат выполнения выражения ret < 0 будет всегда ложным. Возможно, раньше функция handle_dependencies и возвращала другие значения, но потом в результате, например, рефакторинга поведение поменялось. Здесь надо просто завершить рефакторинг. Похожие срабатывания:
- V547 Expression is always false. qcow2.c 1070
- V547 Expression 's->state != MIGRATION_STATUS_COLO' is always false. colo.c 595
- V547 Expression 's->metadata_entries.present & 0x20' is always false. vhdx.c 769
Предупреждение N4
V557 Array overrun is possible. The 'dwc2_glbreg_read' function processes value '[0..63]'. Inspect the third argument. Check lines: 667, 1040. hcd-dwc2.c 667
#define HSOTG_REG(x) (x) // <= 5
....
struct DWC2State {
....
#define DWC2_GLBREG_SIZE 0x70
uint32_t glbreg[DWC2_GLBREG_SIZE / sizeof(uint32_t)]; // <= 1
....
}
....
static uint64_t dwc2_glbreg_read(void *ptr, hwaddr addr, int index,
unsigned size)
{
....
val = s->glbreg[index]; // <= 2
....
}
static uint64_t dwc2_hsotg_read(void *ptr, hwaddr addr, unsigned size)
{
....
switch (addr) {
case HSOTG_REG(0x000) ... HSOTG_REG(0x0fc): // <= 4
val = dwc2_glbreg_read(ptr, addr,
(addr - HSOTG_REG(0x000)) >> 2, size); // <= 3
....
}
....
}
В этом коде есть потенциальная проблема с выходом за границу массива. В структуре DWC2State определен массив glbreg,состоящий из 28 элементов (комментарий 1). В функции dwc2_glbreg_read по индексу обращаются к нашему массиву (комментарий 2). Теперь обратите внимание, что в функцию dwc2_glbreg_read в качестве индекса передают выражение (addr — HSOTG_REG(0x000)) >> 2 (комментарий 3), которое может принимать значение в диапазоне [0..63]. Для того чтобы в этом убедиться, обратите внимание на комментарии 4 и 5. Возможно, тут надо скорректировать диапазон значений из комментария 4.
Еще похожие срабатывания:
- V557 Array overrun is possible. The 'dwc2_hreg0_read' function processes value '[0..63]'. Inspect the third argument. Check lines: 814, 1050. hcd-dwc2.c 814
- V557 Array overrun is possible. The 'dwc2_hreg1_read' function processes value '[0..191]'. Inspect the third argument. Check lines: 927, 1053. hcd-dwc2.c 927
- V557 Array overrun is possible. The 'dwc2_pcgreg_read' function processes value '[0..127]'. Inspect the third argument. Check lines: 1012, 1060. hcd-dwc2.c 1012
Предупреждение N5
V575 The 'strerror_s' function processes '0' elements. Inspect the second argument. commands-win32.c 1642
void qmp_guest_set_time(bool has_time, int64_t time_ns,
Error **errp)
{
....
if (GetLastError() != 0) {
strerror_s((LPTSTR) & msg_buffer, 0, errno);
....
}
}
Функция strerror_s возвращает текстовое описание кода системной ошибки. Её сигнатура выглядит так:
errno_t strerror_s( char *buf, rsize_t bufsz, errno_t errnum );
Первый параметр — это указатель на буфер, куда будет скопировано текстовое описание, второй параметр — размер буфера, третий — код ошибки. В коде в качестве размера буфера передается 0, это явно ошибочное значение. Кстати, есть возможность узнать заранее сколько байт надо выделять: надо просто вызвать strerrorlen_s, которая возвращает длину текстового описания ошибки. Это значение можно использовать для выделения буфера достаточного размера.
Предупреждение N6
V595 The 'blen2p' pointer was utilized before it was verified against nullptr. Check lines: 103, 106. dsound_template.h 103
static int glue (
....
DWORD *blen1p,
DWORD *blen2p,
int entire,
dsound *s
)
{
....
dolog("DirectSound returned misaligned buffer %ld %ld\n",
*blen1p, *blen2p); // <= 1
glue(.... p2p ? *p2p : NULL, *blen1p,
blen2p ? *blen2p : 0); // <= 2
....
}
В этом коде значение аргумента blen2p сначала используется (комментарий 1), а потом проверяется на nullptr (комментарий 2). Это крайне подозрительное место выглядит так, как будто просто забыли вставить проверку перед первым использованием (комментарий 1). Как вариант исправления — просто добавить проверку:
dolog("DirectSound returned misaligned buffer %ld %ld\n",
*blen1p, blen2p ? *blen2p : 0);
Тут еще возникает вопрос по поводу аргумента blen1p. Вероятно, он тоже может быть нулевым указателем, и тут тоже надо будет добавить проверку. Еще несколько подобных срабатываний:
- V595 The 'ref' pointer was utilized before it was verified against nullptr. Check lines: 2191, 2193. uri.c 2191
- V595 The 'cmdline' pointer was utilized before it was verified against nullptr. Check lines: 420, 425. qemu-io.c 420
- V595 The 'dp' pointer was utilized before it was verified against nullptr. Check lines: 288, 294. onenand.c 288
- V595 The 'omap_lcd' pointer was utilized before it was verified against nullptr. Check lines: 81, 87. omap_lcdc.c 81
Предупреждение N7
V597 The compiler could delete the 'memset' function call, which is used to flush 'op_info' object. The RtlSecureZeroMemory() function should be used to erase the private data. virtio-crypto.c 354
static void virtio_crypto_free_request(VirtIOCryptoReq *req)
{
if (req) {
if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {
....
/* Zeroize and free request data structure */
memset(op_info, 0, sizeof(*op_info) + max_len); // <= 1
g_free(op_info);
}
g_free(req);
}
}
В этом фрагменте кода вызывается функция memset для объекта op_info (комментарий 1), после этого op_info сразу удаляется, то есть, другими словами, после очистки этот объект нигде больше не модифицируется. Это как раз тот самый случай, когда в процессе оптимизации компилятор может удалить вызов memset. Чтобы исключить подобное потенциальное поведение, можно воспользоваться специальными функциями, которые компилятор никогда не удаляет. См. также статью "Безопасная очистка приватных данных".
Предупреждение N8
V610 Unspecified behavior. Check the shift operator '>>'. The left operand is negative ('number' = [-32768..2147483647]). cris.c 2111
static void
print_with_operands (const struct cris_opcode *opcodep,
unsigned int insn,
unsigned char *buffer,
bfd_vma addr,
disassemble_info *info,
const struct cris_opcode *prefix_opcodep,
unsigned int prefix_insn,
unsigned char *prefix_buffer,
bfd_boolean with_reg_prefix)
{
....
int32_t number;
....
if (signedp && number > 127)
number -= 256; // <= 1
....
if (signedp && number > 32767)
number -= 65536; // <= 2
....
unsigned int highbyte = (number >> 24) & 0xff;
....
}
Так как переменная number может иметь отрицательное значение, побитовый сдвиг вправо является неуточнённым поведением (unspecified behavior). Чтобы убедиться в том, что рассматриваемая переменная может принять отрицательное значение, обратите внимание на комментарии 1 и 2. Для устранения различий поведения вашего кода на различных платформах, таких случаев нужно не допускать.
Еще предупреждения:
- V610 Undefined behavior. Check the shift operator '<<'. The left operand is negative ('(hclk_div — 1)' = [-1..15]). aspeed_smc.c 1041
- V610 Undefined behavior. Check the shift operator '<<'. The left operand '(target_long) — 1' is negative. exec-vary.c 99
- V610 Undefined behavior. Check the shift operator '<<'. The left operand is negative ('hex2nib(words[3][i * 2 + 2])' = [-1..15]). qtest.c 561
Также есть несколько предупреждений такого же типа, только в качестве левого операнда выступает -1.
V610 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. hppa.c 2702
int print_insn_hppa (bfd_vma memaddr, disassemble_info *info)
{
....
disp = (-1 << 10) | imm10;
....
}
Другие подобные предупреждения:
- V610 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. hppa.c 2718
- V610 Undefined behavior. Check the shift operator '<<'. The left operand '-0x8000' is negative. fmopl.c 1022
- V610 Undefined behavior. Check the shift operator '<<'. The left operand '(intptr_t) — 1' is negative. sve_helper.c 889
Предупреждение N9
V616 The 'TIMER_NONE' named constant with the value of 0 is used in the bitwise operation. sys_helper.c 179
#define HELPER(name) ....
enum {
TIMER_NONE = (0 << 30), // <= 1
....
}
void HELPER(mtspr)(CPUOpenRISCState *env, ....)
{
....
if (env->ttmr & TIMER_NONE) { // <= 2
....
}
}
Можно легко убедиться, что значение макроса TIMER_NONE равно нулю (комментарий 1). Далее этот макрос используется в побитовой операции, результат которой всегда будет 0. Как итог, тело условного оператора if (env->ttmr & TIMER_NONE) никогда не выполнится.
Предупреждение N10
V629 Consider inspecting the 'n << 9' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qemu-img.c 1839
#define BDRV_SECTOR_BITS 9
static int coroutine_fn convert_co_read(ImgConvertState *s,
int64_t sector_num, int nb_sectors, uint8_t *buf)
{
uint64_t single_read_until = 0;
int n;
....
while (nb_sectors > 0) {
....
uint64_t offset;
....
single_read_until = offset + (n << BDRV_SECTOR_BITS);
....
}
....
}
В этом фрагменте кода над переменной n, имеющей 32-битный знаковый тип, выполняется операция сдвига, потом этот 32-битный знаковый результат расширяется до 64-битного знакового типа, и далее, как беззнаковый тип, складывается с беззнаковой 64-битной переменной offset. Предположим, что на момент выполнения выражения переменная n имеет некоторые значимые старшие 9 бит. Мы выполняем операцию сдвига на 9 разрядов (BDRV_SECTOR_BITS), а это, в свою очередь, является неопределенным поведением, тогда в качестве результата мы можем получить выставленный бит в старшем разряде. Напомним, что этот бит в знаковом типе отвечает за знак, то есть результат может стать отрицательным. Так как переменная n знакового типа, то при расширении будет учтен знак. Далее результат складывается с переменной offset. Из этих рассуждений нетрудно увидеть, что результат выполнения выражения может отличаться от предполагаемого. Одним из возможных вариантов решения является замена типа переменной n на 64-битный беззнаковый тип, то есть на uint64_t.
Вот еще похожие срабатывания:
- V629 Consider inspecting the '1 << refcount_order' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2.c 3204
- V629 Consider inspecting the 's->cluster_size << 3' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2-bitmap.c 283
- V629 Consider inspecting the 'i << s->cluster_bits' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2-cluster.c 983
- V629 Consider inspecting the expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. vhdx.c 1145
- V629 Consider inspecting the 'delta << 2' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. mips.c 4341
Предупреждение N11
V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. nand.c 310
static void nand_command(NANDFlashState *s)
{
....
s->addr &= (1ull << s->addrlen * 8) - 1;
....
}
Тут просто подозрительное место. Непонятно, что программист хотел сделать вначале: сдвиг или умножение. Даже если здесь нет ошибки, все равно надо еще раз взглянуть на код и правильно расставить скобочки. Тут как раз одно из таких мест, которые должны смотреть разработчики, чтобы убедиться в корректности своего алгоритма. Другие такие места:
- V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 449
- V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 1235
- V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 1264
Предупреждение N12
V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. pl181.c 400
static void pl181_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
....
if (s->cmd & PL181_CMD_ENABLE) {
if (s->cmd & PL181_CMD_INTERRUPT) {
....
} if (s->cmd & PL181_CMD_PENDING) { // <= else if
....
} else {
....
}
....
}
....
}
В этом коде, судя по форматированию, прямо напрашивается использование else if вместо if. Возможно, здесь забыли дописать else. Тогда вариант исправления может быть такой:
} else if (s->cmd & PL181_CMD_PENDING) { // <= else if
Однако есть вероятность, что с этим кодом все в порядке, и тут некорректное форматирование текста программы, которое сбивает с толку. Тогда код мог бы выглядеть так:
if (s->cmd & PL181_CMD_INTERRUPT) {
....
}
if (s->cmd & PL181_CMD_PENDING) { // <= if
....
} else {
....
}
Предупреждение N13
V773 The function was exited without releasing the 'rule' pointer. A memory leak is possible. blkdebug.c 218
static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
{
....
struct BlkdebugRule *rule;
....
rule = g_malloc0(sizeof(*rule)); // <= 1
....
if (local_error) {
error_propagate(errp, local_error);
return -1; // <= 2
}
....
/* Add the rule */
QLIST_INSERT_HEAD(&s->rules[event], rule, next); // <= 3
....
}
В данном коде выделяется объект rule (комментарий 1) и добавляется в список для последующего использования (комментарий 3), но в случае ошибки происходит возврат из функции без удаления ранее созданного объекта rule (комментарий 2). Здесь надо просто правильно обработать ошибку: удалить ранее созданный объект, иначе будет утечка памяти.
Предупреждение N14
V781 The value of the 'ix' index is checked after it was used. Perhaps there is a mistake in program logic. uri.c 2110
char *uri_resolve_relative(const char *uri, const char *base)
{
....
ix = pos;
if ((ref->path[ix] == '/') && (ix > 0)) {
....
}
Здесь анализатор обнаружил потенциальный выход за границу массива. Сначала читается элемент массива ref->path по индексу ix, а потом ix проверяется на корректность (ix > 0). Правильным решением тут будет поменять эти действия местами:
if ((ix > 0) && (ref->path[ix] == '/')) {
Таких мест нашлось несколько:
- V781 The value of the 'ix' index is checked after it was used. Perhaps there is a mistake in program logic. uri.c 2112
- V781 The value of the 'offset' index is checked after it was used. Perhaps there is a mistake in program logic. keymaps.c 125
- V781 The value of the 'quality' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 326, 335. vnc-enc-tight.c 326
- V781 The value of the 'i' index is checked after it was used. Perhaps there is a mistake in program logic. mem_helper.c 1929
Предупреждение N15
V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. cadence_gem.c 1486
typedef struct CadenceGEMState {
....
uint32_t regs_ro[CADENCE_GEM_MAXREG];
}
....
static void gem_write(void *opaque, hwaddr offset, uint64_t val,
unsigned size)
{
....
val &= ~(s->regs_ro[offset]);
....
}
В этом коде выполняется побитовая операция с объектами разных типов. Левый операнд — это аргумент val, имеющий 64-битный беззнаковый тип. В качестве правого операнда выступает полученное значение элемента массива s->regs_ro по индексу offset, имеющий 32-битный беззнаковый тип. Результат операции в правой части (~(s->regs_ro[offset])) является 32-битным беззнаковым типом, и перед побитовым умножением он расширится до 64-битного типа нулями, то есть после вычисления всего выражения обнулятся все старшие биты переменной val. Такие места всегда выглядят подозрительными. Тут можно только порекомендовать разработчикам еще раз пересмотреть этот код. Еще похожее:
- V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. xlnx-zynq-devcfg.c 199
- V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. soc_dma.c 214
- V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. fpu_helper.c 418
Предупреждение N16
V1046 Unsafe usage of the 'bool' and 'unsigned int' types together in the operation '&='. helper.c 10821
static inline uint32_t extract32(uint32_t value, int start, int length);
....
static ARMVAParameters aa32_va_parameters(CPUARMState *env, uint32_t va,
ARMMMUIdx mmu_idx)
{
....
bool epd, hpd;
....
hpd &= extract32(tcr, 6, 1);
}
В этом фрагменте кода происходит операция побитового И над переменной hpd, имеющей тип bool, и результатом выполнения функции extract32, имеющим тип uint32_t. Так как битовое значение булевой переменной может быть только 0 или 1, то результат выражения будет всегда false, если младший бит, возвращаемый функцийе extract32, равен нулю. Давайте рассмотрим это на примере. Предположим, что значение hpd равно true, а функция вернула значение 2, то есть в двоичном представлении операция будет выглядеть так 01 & 10 = 0, а результат выражения будет равен false. Скорее всего, программист хотел выставлять значение true, если функция возвращает что-то отличное от нуля. По всей видимости, надо исправить код так, чтобы результат выполнения функции приводился к типу bool, например, так:
hpd = hpd && (bool)extract32(tcr, 6, 1);
Заключение
Как видно, анализатор нашел много подозрительных мест. Возможно, найденные потенциальные проблемы пока никак себя не проявляют, но их наличие не может не тревожить, так как они способны выстрелить в самый неожиданный момент. Просмотреть все подозрительные места заранее и исправить — лучше, чем чинить бесконечный поток багов. Очевидно, что для сложных проектов, подобных этому, статический анализ может принести ощутимую пользу, особенно если организовать регулярную проверку проекта. Если Вы хотите попробовать PVS-Studio для своего проекта, вы можете скачать анализатор и получить бесплатный триальный ключ на этой странице.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Evgeniy Ovsannikov. Checking QEMU using PVS-Studio.
Gasaraki
Для ядра Linux данным инструментом кто-нибудь пользуется?
Добавьте комментарий в свою статью про бесплатное использование для открытых проектов.
dimorphus
Я раз в несколько лет запускаю. Отослал пару патчей (например, git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5bf1f439c89d). В общем и целом, малоинформативно из-за большого количества ложных срабатываний из-за макросов. Другие люди тоже запускают:
$ git log --pretty=format:'%h %aN %ad %gs' --no-merges --grep 'PVS-Studio'
7c6082b903ac Oleh Kravchenko Wed Oct 16 10:24:30 2019 +0300 leds: mlxreg: Fix possible buffer overflow
4016ba85880b Oleh Kravchenko Wed Sep 4 00:18:19 2019 +0300 led: triggers: Fix dereferencing of null pointer
6a258f7d0fbd Colin Ian King Sat Apr 8 00:22:03 2017 +0100 ubifs: Fix cut and paste erroron sb type comparisons
6eeabd8b2b8a Colin Ian King Fri Apr 14 15:25:40 2017 +0100 staging: media: atomisp: fix range checking on clk_num
e56efe9322c5 Colin Ian King Sat Apr 8 18:09:59 2017 +0100 lockd: remove redundant check on block
7ecaeaffd72a Colin Ian King Sat Apr 8 18:28:42 2017 +0100 scsi: aic7xxx: fix order of arguments in function prototype
eea422709fd8 Colin Ian King Fri Apr 14 14:58:02 2017 +0100 scsi: fc: remove redundant check of an unsigned long being less than zero
ba508685d90f Colin Ian King Sat Apr 8 00:44:03 2017 +0100 staging: wilc1000: fix incorrect strncasecmp length
5bf1f439c89d Denis Efremov Fri Oct 25 15:53:25 2013 +0400 xfs:xfs_dir2_node.c: pointer use before check for null
Ядра регулярно анализируется Coverity (https://scan.coverity.com/projects/linux), Klever/LDV (http://linuxtesting.org/results/ldv). К тому же есть специальные инструменты для анализа кода ядра типа sparse, smatch, куча правил для coccinelle. Скоро будет добавлен удобный запуск Clang-Tidy (https://lkml.org/lkml/2020/7/24/1079). Но всё это меркнет, по сравнению с динамическим syzkaller и количеством реальных и пока не исправленных багов, выявленных им (https://syzkaller.appspot.com/upstream).
Andrey2008
Хочу на всякий случай отметить, что разовые запуски анализатора не являются правильным способом его применения. Нерегулярные ненастроенные прогоны неэффективны. Во-первых, как верно отмечено, много ложных срабатываний. Во-вторых, многие ошибки найдены и исправлены к этому моменту более дорогими способами.
Анализатор, во-первых, должен быть настроен, а во-вторых, запускаться регулярно. Как внедрить статический анализатор кода в legacy проект и не демотивировать команду.
dimorphus
Абсолютно с вами согласен. К сожалению, собственными силами в свободное время для всего ядра linux этого не осилить, поэтому мой комментарий имеет смысл только для тех, кто хочет за вечер запустить и что-то посмотреть.
Более того, я заметил что другие анализаторы и компиляторы берут к себе диагностики, которые ранее я только у вас видел. Лично написал простой шаблон для для coccinelle (он, конечно, дал очень много ложных срабатыванию, но мне так было проще) по образу вашей диагностики и исправил несколько реальных багов (
drm/radeon: fix fb_div check in ni_init_smc_spll_table(), staging: rtl8188eu: fix HighestRate check in odm_ARFBRefresh_8188E())
Andrey2008
Да, так и есть :). Спасибо.
P.S. На эту тему: PVS-Studio — двигатель прогресса.
Andrey2008
Вставлять в каждую статью про варианты бесплатного использования как-то неуместно, пожалуй. Но на всякий случай если кто-то пропустил: Бесплатные варианты лицензирования PVS-Studio.