Находим ошибки в коде компилятора GCC с помощью анализатора PVS-Studio

    GCCЯ регулярно проверяю различные открытые проекты, чтобы продемонстрировать возможности статического анализатора кода PVS-Studio (C, C++, C#). Настало время компилятора GCC. Бесспорно, GCC — это очень качественный и оттестированный проект, поэтому найти в нём хотя бы несколько ошибок уже большое достижение для любого инструмента. К моей радости, PVS-Studio справился с этой задачей. Никто не застрахован от опечаток и невнимательности. Именно поэтому PVS-Studio может стать вашей дополнительной линией обороны на фронте бесконечной войны с багами.

    GCC


    GNU Compiler Collection (обычно используется сокращение GCC) — набор компиляторов для различных языков программирования, разработанный в рамках проекта GNU. GCC является свободным программным обеспечением, распространяется фондом свободного программного обеспечения на условиях GNU GPL и GNU LGPL и является ключевым компонентом GNU toolchain. Проект написан на языке C и C++.

    Компилятор GCC имеет хорошие встроенные диагностики, помогающие выявлять многие ошибки на этапе компиляции. Естественно, GCC собирается с помощью GCC и, соответственно, может выявлять ошибки в собственном коде. Дополнительно исходный код GCC проверяется с помощью анализатора Coverity. Да и вообще, думаю GCC проверялся энтузиастами с помощью многих анализаторов и других инструментов. Это делает поиск ошибок в GCC большим испытанием для анализатора кода PVS-Studio.

    Для анализа была взята trunk версия из git-репозитория: (git) commit 00a7fcca6a4657b6cf203824beda1e89f751354b svn+ssh://gcc.gnu.org/svn/gcc/trunk@238976

    Примечание. Статья задержалась с выходом, и возможно какие-то ошибки уже исправлены. Но это не имеет значения: постоянно появляются новые ошибки, старые исчезают. Главное — статья показывает, что статический анализ может помогать программистам выявлять ошибки после их появления.

    Предвидя дискуссию


    Как я сказал во введении, я считаю GCC проектом с высоким качеством кода. Уверен, многие захотят поспорить. В качестве примера приведу цитату из Wikipedia на русском языке:

    Некоторые разработчики OpenBSD, например Тео де Раадт и Отто Мурбек (Otto Moerbeek), критикуют GCC, называя его «громоздким, глючным, медленным и генерирующим плохой код».

    Я считаю такие заявления необоснованными. Да, возможно, код GCC содержит много макросов, которые затрудняют его чтение. Но я никак не могу согласиться с заявлением о его глючности. Если бы GCC глючил, вообще бы нигде ничего не работало. Вы только вспомните, как много программ им компилируется и успешно работает. Создатели GCC делают огромную, сложную работу с большим профессионализмом. Спасибо им. Я рад, что могу протестировать работу PVS-Studio на таком высококачественном проекте.

    Для тех, кто скажет, что код компилятора Clang всё равно круче, напомню: в нём PVS-Studio также находил ошибки: 1, 2.

    PVS-Studio


    Я проверил код GCC с помощью Alpha-версии анализатора PVS-Studio for Linux. Мы планируем начать выдавать заинтересовавшимся программистам Beta-версию анализатора в середине сентября 2016 года. Инструкцию о том, как стать одним из первых, кто сможет попробовать Beta-версию PVS-Studio for Linux на своём проекте, вы найдете в статье "PVS-Studio признаётся в любви к Linux".

    Если вы читаете эту статью гораздо позже, чем сентябрь 2016, и хотите попробовать PVS-Studio for Linux, то приглашаю вас на страницу продукта: http://www.viva64.com/ru/pvs-studio/

    Результаты проверки


    Мы добрались до самого интересного раздела, который, я думаю, с нетерпением ждут наши постоянные читатели. Рассмотрим участки кода, где анализатор нашел ошибки или крайне подозрительные моменты.

    К сожалению, я не могу выдать разработчикам компилятора полный отчёт. В нем пока слишком много мусора (ложных срабатываний), связанных с тем, что анализатор не полностью готов к встрече с миром Linux. Нужно проделать работу по уменьшению количества ложных предупреждений на типовые используемые конструкции. Попробую пояснить на одном простом примере. Многие диагностики не должны ругаться на выражения, относящиеся к макросам assert. Эти макросы бывают устроены весьма творчески и надо научить анализатор не обращать на них внимание. Но дело в том, что определяется макрос assert очень по-разному, и надо обучить анализатор всем типовым вариантам.

    Поэтому разработчиков GCC прошу подождать выхода по крайней мере Beta-версии анализатора. Я не хочу испортить впечатление отчетом, сгенерированным недоделанной версией.

    Классика (Copy-Paste)


    Начнем мы с самой классической и распространённой ошибки, которая выявляется с помощью диагностики V501. Как правило, такие ошибки появляются из-за невнимательности при Copy-Paste или просто являются опечатками, допускаемыми при наборе нового кода.

    static bool
    dw_val_equal_p (dw_val_node *a, dw_val_node *b)
    {
      ....
      case dw_val_class_vms_delta:
        return (!strcmp (a->v.val_vms_delta.lbl1,
                         b->v.val_vms_delta.lbl1)
                && !strcmp (a->v.val_vms_delta.lbl1,
                            b->v.val_vms_delta.lbl1));
      ....
    }

    Предупреждение анализатора PVS-Studio: V501 There are identical sub-expressions '!strcmp(a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)' to the left and to the right of the '&&' operator. dwarf2out.c 1428

    Быстро увидеть ошибки проблематично и следует внимательно присмотреться. Именно поэтому ошибка и не была выявлена при обзорах кода и рефакторинге.

    Функция strcmp дважды сравнивает одни и те же строки. Мне кажется, второй раз следовало сравнивать не члены класса lbl1, а lbl2. Тогда корректный код должен выглядеть так:

    return (!strcmp (a->v.val_vms_delta.lbl1,
                     b->v.val_vms_delta.lbl1)
            && !strcmp (a->v.val_vms_delta.lbl2,
                        b->v.val_vms_delta.lbl2));

    Хочу отметить, что код, приведённый в статье, немного отформатирован, чтобы он занимал мало места по оси X. На самом деле, код выглядит так:

    Плохое форматирование кода



    Ошибки, возможно, удалось бы избежать, если использовать «табличное» выравнивание кода. Например, ошибку было бы легче заметить, если отформатировать код так:

    Табличное форматирование кода



    Подробнее я рассматривал такой подход в электронной книге "Главный вопрос программирования, рефакторинга и всего такого" (см. главу N13: Выравнивайте однотипный код «таблицей»). Рекомендую всем, кто заботится о качестве своего кода, познакомиться с приведённой здесь ссылкой.

    Давайте рассмотрим ещё одну ошибку, которая, я уверен, появилась из-за Copy-Paste:

    const char *host_detect_local_cpu (int argc, const char **argv)
    {
      unsigned int has_avx512vl = 0;
      unsigned int has_avx512ifma = 0;
      ....
      has_avx512dq = ebx & bit_AVX512DQ;
      has_avx512bw = ebx & bit_AVX512BW;
      has_avx512vl = ebx & bit_AVX512VL;       // <=
      has_avx512vl = ebx & bit_AVX512IFMA;     // <=
      ....
    }

    Предупреждение анализатора PVS-Studio: V519 The 'has_avx512vl' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 500, 501. driver-i386.c 501

    В переменную has_avx512vl дважды подряд записываются различные значения. Это не имеет смысла. Я изучил код и обнаружил переменную has_avx512ifma. Скорее всего, именно она и должна инициализироваться выражением ebx & bit_AVX512IFMA. Тогда корректный код должен быть таким:

    has_avx512vl   = ebx & bit_AVX512VL;    
    has_avx512ifma = ebx & bit_AVX512IFMA;

    Опечатка


    Продолжу испытание вашей внимательности. Посмотрите на код и, не подсматривая ниже, попробуйте найти ошибку.

    static bool
    ubsan_use_new_style_p (location_t loc)
    {
      if (loc == UNKNOWN_LOCATION)
        return false;
    
      expanded_location xloc = expand_location (loc);
      if (xloc.file == NULL || strncmp (xloc.file, "\1", 2) == 0
          || xloc.file == '\0' || xloc.file[0] == '\xff'
          || xloc.file[1] == '\xff')
        return false;
    
      return true;
    }

    Предупреждение анализатора PVS-Studio: V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *xloc.file == '\0'. ubsan.c 1472

    Здесь программист случайно забыл разыменовать указатель в выражении xloc.file == '\0'. В результате указатель просто сравнивается с 0, т.е. с NULL. Никакого эффекта это не имеет, так как ранее такая проверка уже выполнялась: xloc.file == NULL.

    Хорошо, что терминальный ноль программист записал как '\0'. Это помогает быстрее понять, что код ошибочен и как его надо исправить. Про это я также писал в книге (см. главу N9: Используйте для обозначения терминального нуля литерал '\0').

    Правильный вариант кода:

    if (xloc.file == NULL || strncmp (xloc.file, "\1", 2) == 0
        || xloc.file[0] == '\0' || xloc.file[0] == '\xff'
        || xloc.file[1] == '\xff')
      return false;

    Хотя, давайте ещё немного улучшим код. Я рекомендую отформатировать выражение так:

    if (   xloc.file == NULL
        || strncmp (xloc.file, "\1", 2) == 0
        || xloc.file[0] == '\0'
        || xloc.file[0] == '\xff'
        || xloc.file[1] == '\xff')
      return false;

    Обратите внимание: теперь, если допустить ту же ошибку, шанс её заметить будет чуть-чуть выше:

    if (   xloc.file == NULL
        || strncmp (xloc.file, "\1", 2) == 0
        || xloc.file == '\0'
        || xloc.file[0] == '\xff'
        || xloc.file[1] == '\xff')
      return false;

    Потенциальное разыменование нулевого указателя


    Ещё этот раздел можно было бы назвать «стотысячный пример, почему макросы — это плохо». Я очень не люблю макросы и всегда призываю поменьше их использовать. Макросы затрудняют чтение кода, провоцируют появление ошибок, усложняют работу статическим анализаторам. Как мне показалось из недолгого общения с кодом GCC, его авторы очень любят макросы. Я замучался изучать, во что раскрывается тот или иной макрос и возможно поэтому пропустил немало интересных ошибок. Признаюсь, я иногда бываю ленив. Но пару ошибок, связанных с макросами, я всё-таки продемонстрирую.

    odr_type
    get_odr_type (tree type, bool insert)
    {
      ....
      odr_types[val->id] = 0;
      gcc_assert (val->derived_types.length() == 0);
      if (odr_types_ptr)
        val->id = odr_types.length ();
      ....
    }

    Предупреждение анализатора PVS-Studio: V595 The 'odr_types_ptr' pointer was utilized before it was verified against nullptr. Check lines: 2135, 2139. ipa-devirt.c 2135

    Видите здесь ошибку? Думаю, нет, и сообщение анализатора ясности не вносит. Всё дело в том, что odr_types — это не имя переменной, а макрос, объявленным следующим образом:

    #define odr_types (*odr_types_ptr)

    Если раскрыть макрос и убрать всё не относящееся к делу, мы получим следующий код:

    (*odr_types_ptr)[val->id] = 0;
    if (odr_types_ptr)

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

    Рассмотрим ещё один аналогичный случай:

    static inline bool
    sd_iterator_cond (sd_iterator_def *it_ptr, dep_t *dep_ptr)
    {
      ....
      it_ptr->linkp = &DEPS_LIST_FIRST (list);
      if (list)
        continue;
      ....
    }

    Предупреждение анализатора PVS-Studio: V595 The 'list' pointer was utilized before it was verified against nullptr. Check lines: 1627, 1629. sched-int.h 1627

    Чтобы увидеть ошибку, нам опять потребуется показать устройство макроса:

    #define DEPS_LIST_FIRST(L) ((L)->first)

    Раскрываем макрос и получаем:

    it_ptr->linkp = &((list)->first);
    if (list)
      continue;

    И сейчас многие воскликнут: «Стоп, стоп! Здесь нет ошибки. Мы ведь просто получаем указатель на член класса. Никакого разыменования нулевого указателя здесь нет. Да, возможно код не аккуратен, но ошибки здесь нет!».

    Всё не так просто. Здесь возникает неопределённое поведение. И то, что такой код может работать на практике, это просто везение. На самом деле, так писать нельзя. Например, оптимизирующий компилятор, увидев list->first, может удалить проверку if (list). Раз мы выполняли оператор ->, значит предполагается, что указатель не равен nullptr. Если это так, то проверять указатель не нужно.

    Я написал целую статью на эту тему: "Разыменовывание нулевого указателя приводит к неопределённому поведению". Там как раз рассматривается аналогичный случай. Прежде чем спорить, прошу внимательно познакомиться с этой статьёй.

    Впрочем, рассмотренная ситуация действительно сложна и неочевидна. Я допускаю, что могу быть всё-таки неправ и ошибки здесь нет. Однако, до сих пор мне никто не смог это доказать. Будет интересно услышать комментарии разработчиков GCC, если они обратят внимание на эту статью. Уж они-то точно должны знать, как работает компилятор и следует ли интерпретировать такой код как ошибочный, или нет.

    Использование разрушенного массива


    static void
    dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
    {
      const char *name;
      if (symbol->m_name)
        name = symbol->m_name;
      else
      {
        char buf[64];
        sprintf (buf, "__%s_%i", hsa_seg_name (symbol->m_segment),
           symbol->m_name_number);
         name = buf;
      }
      fprintf (f, "align(%u) %s_%s %s",
               hsa_byte_alignment (symbol->m_align),
               hsa_seg_name(symbol->m_segment),
               hsa_type_name(symbol->m_type & ~BRIG_TYPE_ARRAY_MASK),
               name);
      ....
    }

    Предупреждение анализатора PVS-Studio: V507 Pointer to local array 'buf' is stored outside the scope of this array. Such a pointer will become invalid. hsa-dump.c 704

    Строка формируется во временном буфере buf. Адрес этого временного буфера сохраняется в переменной name и используется далее в теле функции. Ошибка в том, что после записи буфера в переменную name, сам этот буфер будет уничтожен.

    Использовать указатель на разрушенный буфер нельзя. Формально мы имеем дело с неопределённым поведением. На практике этот код может вполне успешно работать. Корректная работа программы — это один из вариантов проявления неопределенного поведения.

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

    Чтобы исправить ошибку, достаточно объявить массив buf в той же области видимости, что и указатель name:

    static void
    dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
    {
      const char *name;
      char buf[64];
      ....
    }

    Выполнение одинаковых действий, независимо от условия


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

    bool
    thread_through_all_blocks (bool may_peel_loop_headers)
    {
      ....
      /* Case 1, threading from outside to inside the loop
         after we'd already threaded through the header.  */
      if ((*path)[0]->e->dest->loop_father
          != path->last ()->e->src->loop_father)
      {
        delete_jump_thread_path (path);
        e->aux = NULL;
        ei_next (&ei);
      }
      else
      {
        delete_jump_thread_path (path);
        e->aux = NULL;
        ei_next (&ei);
      }
      ....
    }

    Предупреждение анализатора PVS-Studio: V523 The 'then' statement is equivalent to the 'else' statement. tree-ssa-threadupdate.c 2596

    Если этот код ошибочный, я, к сожалению, не догадываюсь как его следует исправить. Это тот случай, когда надо быть знакомым с проектом, чтобы внести правку.

    Избыточное выражение вида (A == 1 || A != 2)


    static const char *
    alter_output_for_subst_insn (rtx insn, int alt)
    {
      const char *insn_out, *sp ;
      char *old_out, *new_out, *cp;
      int i, j, new_len;
    
      insn_out = XTMPL (insn, 3);
    
      if (alt < 2 || *insn_out == '*' || *insn_out != '@')
        return insn_out;
      ....
    }

    Предупреждение анализатора PVS-Studio: V590 Consider inspecting this expression. The expression is excessive or contains a misprint. gensupport.c 1640

    Нас интересует условие: (alt < 2 || *insn_out == '*' || *insn_out != '@')

    Его можно сократить до: (alt < 2 || *insn_out != '@')

    Рискну предположить, что оператор != следует заменить на ==. Тогда код примет более осмысленный вид:

    if (alt < 2 || *insn_out == '*' || *insn_out == '@')

    Обнуление не того указателя


    Рассмотрим функцию, занимающуюся освобождением ресурсов:

    void
    free_original_copy_tables (void)
    {
      gcc_assert (original_copy_bb_pool);
      delete bb_copy;
      bb_copy = NULL;
      delete bb_original;
      bb_copy = NULL;
      delete loop_copy;
      loop_copy = NULL;
      delete original_copy_bb_pool;
      original_copy_bb_pool = NULL;
    }

    Предупреждение анализатора PVS-Studio: V519 The 'bb_copy' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1076, 1078. cfg.c 1078

    Обратите внимание на эти 4 строчки кода:

    delete bb_copy;
    bb_copy = NULL;
    delete bb_original;
    bb_copy = NULL;

    Случайно дважды обнуляется указатель bb_copy. Правильный вариант:

    delete bb_copy;
    bb_copy = NULL;
    delete bb_original;
    bb_original = NULL;

    Assert, который ничего не проверят


    Неправильное условие, являющееся аргументом макроса gcc_assert не повлияет на корректность работы программы, но усложнит поиск ошибки, если таковая возникнет. Рассмотрим код:

    static void
    output_loc_operands (dw_loc_descr_ref loc, int for_eh_or_skip)
    {
      unsigned long die_offset
        = get_ref_die_offset (val1->v.val_die_ref.die);
      ....
      gcc_assert (die_offset > 0
            && die_offset <= (loc->dw_loc_opc == DW_OP_call2)
                 ? 0xffff
                 : 0xffffffff);
      ....
    }

    Предупреждение анализатора PVS-Studio: V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '<=' operator. dwarf2out.c 2053

    Приоритет тернарного оператора ?: ниже, чем у оператора сравнения <=. Это значит, что мы имеем дело с условием вида:

    die_offset > 0 &&
      ((die_offset <= (loc->dw_loc_opc == DW_OP_call2)) ?
        0xffff : 0xffffffff);

    Таким образом, второй операнд оператора && может принимать значение 0xffff или 0xffffffff. Оба эти значения обозначают истину, поэтому выражение можно упростить до:

    (die_offset > 0)

    Это явно не то, что задумывал программист. Чтобы исправить ситуацию, следует добавить пару круглых скобок:

    gcc_assert (die_offset > 0
          && die_offset <= ((loc->dw_loc_opc == DW_OP_call2)
               ? 0xffff
               : 0xffffffff));

    Оператор ?: очень коварен и его лучше не использовать в сложных выражениях. Уж очень легко допустить ошибку. У нас собрано большое количество примеров таких ошибок, найденных анализатором PVS-Studio в различных открытых проектах. Подробнее об операторе ?: я писал в уже упомянутой ранее книге (см. главу N4: Бойтесь оператора ?: и заключайте его в круглые скобки).

    Кажется, забыли про «cost»


    Структура alg_hash_entry объявлена следующим образом:

    struct alg_hash_entry {
      unsigned HOST_WIDE_INT t;
      machine_mode mode;
      enum alg_code alg;
      struct mult_cost cost;
      bool speed;
    };

    В функции synth_mult программист решил проверить, тот ли это объект, который ему нужен. Для этого ему требуется сравнить поля структуры. Однако, кажется в этом месте допущена ошибка:

    static void synth_mult (....)
    {
      ....
      struct alg_hash_entry *entry_ptr;
      ....
      if (entry_ptr->t == t
          && entry_ptr->mode == mode
          && entry_ptr->mode == mode
          && entry_ptr->speed == speed
          && entry_ptr->alg != alg_unknown)
      {
      ....
    }

    Предупреждение анализатора PVS-Studio: V501 There are identical sub-expressions 'entry_ptr->mode == mode' to the left and to the right of the '&&' operator. expmed.c 2573

    Два раза подряд проверяется mode, но зато нет проверки cost. Возможно, одно из сравнений нужно просто удалить, а возможно, нужно сравнивать cost. Мне сложно судить, но код явно стоит поправить.

    Дубликаты присваиваний


    Следующие участки кода, на мой взгляд, не представляют опасности и, кажется, дублирующееся присваивание можно просто удалить.

    Случай N1
    type_p
    find_structure (const char *name, enum typekind kind)
    {
      ....
      structures = s;                   // <=
      s->kind = kind;
      s->u.s.tag = name;
      structures = s;                   // <=
      return s;
    }

    Предупреждение анализатора PVS-Studio: V519 The 'structures' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 842, 845. gengtype.c 845

    Случай N2
    static rtx
    ix86_expand_sse_pcmpistr (....)
    {
      unsigned int i, nargs;
      ....
        case V8DI_FTYPE_V8DI_V8DI_V8DI_INT_UQI:
        case V16SI_FTYPE_V16SI_V16SI_V16SI_INT_UHI:
        case V2DF_FTYPE_V2DF_V2DF_V2DI_INT_UQI:
        case V4SF_FTYPE_V4SF_V4SF_V4SI_INT_UQI:
        case V8SF_FTYPE_V8SF_V8SF_V8SI_INT_UQI:
        case V8SI_FTYPE_V8SI_V8SI_V8SI_INT_UQI:
        case V4DF_FTYPE_V4DF_V4DF_V4DI_INT_UQI:
        case V4DI_FTYPE_V4DI_V4DI_V4DI_INT_UQI:
        case V4SI_FTYPE_V4SI_V4SI_V4SI_INT_UQI:
        case V2DI_FTYPE_V2DI_V2DI_V2DI_INT_UQI:
          nargs = 5;         // <=
          nargs = 5;         // <=
          mask_pos = 1;
          nargs_constant = 1;
          break;
      ....
    }

    Предупреждение анализатора PVS-Studio: V519 The 'nargs' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 39951, 39952. i386.c 39952

    Случай N3

    Последний случай более странный, чем остальные. Возможно, тут есть какая-то ошибка. Переменной steptype значение присваивается 2 или 3 раза. Это подозрительно.

    static void
    cand_value_at (....)
    {
      aff_tree step, delta, nit;
      struct iv *iv = cand->iv;
      tree type = TREE_TYPE (iv->base);
      tree steptype = type;                 // <=
      if (POINTER_TYPE_P (type))
        steptype = sizetype;                // <=
      steptype = unsigned_type_for (type);  // <=
      ....
    }

    Предупреждение анализатора PVS-Studio: V519 The 'steptype' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 5173, 5174. tree-ssa-loop-ivopts.c 5174

    Заключение


    Я рад, что написал эту статью. Теперь мне есть что отвечать на комментарии вида «PVS-Studio не нужен, так как все те же предупреждения выдаёт и GCC». Как видите, PVS-Studio очень мощный инструмент и превосходит по диагностическим возможностям GCC. Я не отрицаю, что в GCC реализованы отличные диагностики. Этот компилятор, при должной настройке, действительно выявляет много проблем в коде. Но PVS-Studio — это специализированный и быстро развивающийся инструмент, а это значит, он всегда будет лучше выявлять ошибки в коде, чем это делают компиляторы.

    Приглашаю познакомиться с проверками других известных открытых проектов, посетив этот раздел нашего сайта. А также, тем, кто использует Twitter, последовать за мной @Code_Analysis. Я регулярно публикую ссылки на интересные статьи по программированию на языке C и C++, а также рассказываю о новых достижениях нашего анализатора.


    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey karpov. Bugs found in GCC with the help of PVS-Studio.

    Прочитали статью и есть вопрос?
    Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.
    PVS-Studio
    1,014.84
    Static Code Analysis for C, C++, C# and Java
    Share post

    Similar posts

    Comments 48

      0
      Не боитесь, что проверяемый «проглотит» свои же эти ошибки и далее не даст их совершать?

      Т.е. разработчики учтут и внесут дополнительные проверки.
        0
        А чего им боятся то? Всех не перевешают! Проверку всех ошибок, находимые PVS-Studio в ближайшее время вряд ли кто то сможет перенести что в Clang что в GCC. Так что им хватит еще на хлеб с маслом.
          +12
          Даже если так, это не страшно. Мы ведь тоже не стоим на месте. Собственно, наша задача всегда бежать впереди компиляторов.
        • UFO just landed and posted this here
            +1
            А GCC разве не люди писали? Каждый из нас может допустить подобного рода ошибку.
              +22
              Ах эта вера в то, что есть по-настоящему крутые программисты, которые не допускают ошибок…
                0
                И чем ближе человек к чайнику — тем больше в это верит. :-) Иллюзии уходят с написанием первого компилятора. :-)
              –9
              Частенько читаю ваши статьи на хабре и порой выношу для себя полезные вещи (спасибо!), но не могу припомнить ответа на вопрос: «Сообщаете ли Вы мэнтейнерам продуктов о найденных ошибках?»
                +8
                Толсто. Но на всякий случай, если не троллинг: да (см. ответы на часто задаваемые вопросы, ссылка в конце статьи).
                  +1
                  Мне кажется, что стоит упоминать об этом не в FAQ на стороннем сайте, а прямо в статье. Статья ведь должна в идеале быть самодостаточной, а одна строчка «мы сообщаем о багах авторам софта» сильно её не увеличит. Это для вас ваши статьи относятся к некой серии, а читатель может начать с любой из них.

                  Так что думаю нет, это не троллинг комментатора, а (мелкая?) недоработка автора статьи. Данный комментатор мог часто читать ваши статьи, но не ходить в FAQ — и это, имхо, нормально.
                    +2
                    Если в каждую статью вставлять про «отписали разработчикам», про «версию для линукс», про «проверяете ли PVS-Studio», то статьи будут похожи на юридический текст. Что главное нисколько не помешает первым вопросом написать: «А вы проверяете PVS-Studio своим анализатором?».
                      0
                      Вы не правы: всё писать не нужно. Проверка PVS-Studio самой собой не относится к теме стати, и версия для линукса тоже слабо… А вот то, что об этих конкретных ошибках, о которых написано в статье было сообщено разработчиком — является неотъемлемой частью повествования.

                      Это как снимать фильм про какого-нибудь супер-героя, но концовку не делать, потому что ведь и без того понятно, что герой всегда побеждает.
                        +1
                        В конкретно данном случае сложность с этим:
                        К сожалению, я не могу выдать разработчикам компилятора полный отчёт.

                        А вообще, уже традицией стало задавать вопрос про то, сообщили ли разработчикам. Эпиком стал этот комментарий.
                        Но в последнее время этот вопрос перестали задавать, народ расслабился и уже в комментариях к этой статье вылезло сразу несколько F.A.Q.-овых вопросов.
                          +2
                          является неотъемлемой частью повествования


                          Для Вас — это. Для других — другое является такой же частью.
                          0
                          Именно этот текст вставлять не обязательно.
                          Все же было бы приятно видедь ссылку в конце поста на соответствующий репорт (хотя бы по тому, что уже нашлось среди «мусора»), согласитесь? По крайней мере для меня, статья выглядела бы полностью законченной в плане текущей истории.
                        0
                        Не могу понять, в чем увидели троллинг с моей стороны, когда действительно было интересно это узнать.

                        P.S. Да, виноват, под кат не заглянул.
                        +3
                        https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77421
                          0
                          Благодарю.
                        0

                        Интересно, может ли вот этот код привести к проблемам:


                        strncmp (xloc.file, "\1", 2)

                        Из-за того, что строка, с которой сравнивают, имеет длину 1, а длина указана как 2.

                          0
                          Таки длина 2. К строкам, полученным из литералов автоматически добавляется нулевой байт.
                            0

                            Да, я почему-то был под впечатлением, что 2 — это сколько символов сравнивать не считая нулевой байт (наверное из-за схожего параметра у strncpy, где надо вычитать единицу для нулевого байта и руками его доставлять).

                            0
                            Функция strncmp() сравнивает в лексикографическом порядке не более count символов из двух строк, заканчивающихся символом конца строки, и возвращает целое значение, зависящее от результата сравнения следующим образом. Если в какой-нибудь из заданных строк меньше count символов, сравнение заканчивается при обнаружении первого нулевого символа.
                              +3

                              Я не вижу в стандарте ничего про «лексикографический порядок». Даже, наоборот:


                              The sign of a nonzero value returned by the comparison functions memcmp, strcmp, and strncmp is determined by the sign of the difference between the values of the first pair of characters (both interpreted as unsigned char) that differ in the objects being compared.

                              (C99, 7.21.4) — просто сравниваются байты. Для «лексикографического порядка» есть strcoll, правда, не знаю, насколько хорошо оно работает (в первую очередь, насколько хорошо оно работает с UTF-8, с однобайтовыми локалями всё обычно в порядке).

                                –5
                                Ну подумаешь, скопировал неудачное описание с просторов интернета. :) Суть не меняется.
                            –17
                            А если PVS Studio проверит PVS Studio, что будет? ))
                            –24
                            Когда уже будет «Находим ошибки в коде анализаторе PVS-Studio с помощью анализатора PVS-Studio»?
                              +7
                              См. выше.
                                0
                                Это последствия премодерации комментариев от «самых маленьких». Пока мой комментарий был на модерации — произошла накладка.
                              +3
                              Я ждал этого момента!
                              Спасибо за интересную статью. Теперь знаю чего ждать от любимого компилятора.
                                +2
                                А каким компилятором скомпилирован PVS-Studio for linux?
                                  +5
                                  GCC
                                    +5
                                    Тогда статью можно было назвать так: Проверяем GCC, скомпилированный GCC, анализатором PVC-Studio, скомпилированным GCC.
                                      0
                                      Рекурсивненько
                                        –1
                                        И пишем в Ворде, собираемом Microsoft.
                                        +2

                                        Ошибки PVS-Studio, вызванные ошибками GCC, выявленными PVS-Studio, скомпилированной с помощью GCC, могут являться на самом деле не ошибками PVS-Studio, а ошибками GCC :))

                                    +3
                                    Мне кажется, или в вашем примере кода лишняя скобка перед '?'?
                                    Приоритет тернарного оператора ?: ниже, чем у оператора сравнения <=. Это значит, что мы имеем дело с условием вида:

                                    (die_offset > 0 &&
                                      (die_offset <= (loc->dw_loc_opc == DW_OP_call2)) ?
                                        0xffff : 0xffffffff);
                                    
                                      +2
                                      Поправлю. Прошу писать про замеченные недостатки в личку.
                                      0
                                      Было бы очень интересно узнавать, по мимо ошибок, как вызвать эти ошибки в самой программе
                                      Например, что можно написать в программе и скомпилировать её GCC, чтобы была магия?
                                        0
                                        Это крайне сложная задача для меня, так как я не знаком с кодом проекта.
                                        0
                                        Диагностик GCC более чем достаточно.
                                          0
                                          -Weverything?
                                            0

                                            Это clang, GCC не знает -Weverything.

                                          –1
                                          А теперь очередь GDB: https://habrahabr.ru/company/pvs-studio/blog/310156/

                                          Only users with full accounts can post comments. Log in, please.