При анализе кода PVS-Studio анализирует поток данных и оперирует значениями переменных. Значения берутся из констант или выводятся из условных выражений. Мы называем их виртуальными ценностями. Недавно мы их доработали для работы с многосимвольными константами и это стало поводом для создания нового диагностического правила.

Введение

Многосимвольные литералы определяются реализацией, поэтому разные компиляторы могут кодировать их по-разному. Например, GCC и Clang устанавливают значение в зависимости от порядка символов в литерале, а MSVC перемещает их в зависимости от типа символа (обычный или экранированный).

Например, литерал «T\x65s\x74» будет закодирован по-разному, в зависимости от компилятора. Подобную логику пришлось добавить в анализатор. В результате мы сделали новое диагностическое правило V1039 для выявления таких литералов в коде. Эти литералы опасны в кроссплатформенных проектах, использующих для сборки несколько компиляторов.

Диагностика V1039

Давайте посмотрим на пример. Код ниже, скомпилированный разными компиляторами, будет вести себя по-разному:

#include <stdio.h>
void foo(int c)
{
  if (c == 'T\x65s\x74')                       // <= V1039
  {
    printf("Compiled with GCC or Clang.\n");
  }
  else
  {
    printf("It's another compiler (for example, MSVC).\n");
  }
}
int main(int argc, char** argv)
{
  foo('Test');
  return 0;
}

Программа, скомпилированная разными компиляторами, будет выводить на экран разные сообщения.

Для проекта, использующего определенный компилятор, это не будет заметно. Но при портировании могут возникнуть проблемы, поэтому такие литералы следует заменять простыми числовыми константами, например, «Test» нужно изменить на 0x54657374.

Чтобы продемонстрировать разницу между компиляторами, мы напишем небольшую утилиту, которая берет последовательности из 3 и 4 символов, такие как «GHIJ» и «GHI», и отображает их представление в памяти после компиляции.

Код утилиты:

#include <stdio.h>
typedef int char_t;
void PrintBytes(const char* format, char_t lit)
{
  printf("%20s : ", format);
  const unsigned char *ptr = (const unsigned char*)&lit;
  for (int i = sizeof(lit); i--;)
  {
    printf("%c", *ptr++);
  }
  putchar('\n');
}
int main(int argc, char** argv)
{
  printf("Hex codes are: G(%02X) H(%02X) I(%02X) J(%02X)\n",'G','H','I','J');
  PrintBytes("'GHIJ'", 'GHIJ');
  PrintBytes("'\\x47\\x48\\x49\\x4A'", '\x47\x48\x49\x4A');
  PrintBytes("'G\\x48\\x49\\x4A'", 'G\x48\x49\x4A');
  PrintBytes("'GH\\x49\\x4A'", 'GH\x49\x4A');
  PrintBytes("'G\\x48I\\x4A'", 'G\x48I\x4A');
  PrintBytes("'GHI\\x4A'", 'GHI\x4A');
  PrintBytes("'GHI'", 'GHI');
  PrintBytes("'\\x47\\x48\\x49'", '\x47\x48\x49');
  PrintBytes("'GH\\x49'", 'GH\x49');
  PrintBytes("'\\x47H\\x49'", '\x47H\x49');
  PrintBytes("'\\x47HI'", '\x47HI');
  return 0;
}

Вывод утилиты, скомпилированной Visual C++:

Hex codes are: G(47) H(48) I(49) J(4A)
              'GHIJ' : JIHG
  '\x47\x48\x49\x4A' : GHIJ
     'G\x48\x49\x4A' : HGIJ
        'GH\x49\x4A' : JIHG
        'G\x48I\x4A' : JIHG
           'GHI\x4A' : JIHG
               'GHI' : IHG
      '\x47\x48\x49' : GHI
            'GH\x49' : IHG
         '\x47H\x49' : HGI
            '\x47HI' : IHG

Вывод утилиты, скомпилированной GCC или Clang:

Hex codes are: G(47) H(48) I(49) J(4A)
              'GHIJ' : JIHG
  '\x47\x48\x49\x4A' : JIHG
     'G\x48\x49\x4A' : JIHG
        'GH\x49\x4A' : JIHG
        'G\x48I\x4A' : JIHG
           'GHI\x4A' : JIHG
               'GHI' : IHG
      '\x47\x48\x49' : IHG
            'GH\x49' : IHG
         '\x47H\x49' : IHG
            '\x47HI' : IHG

Вывод

Диагностика V1039 добавлена ​​в недавно выпущенный анализатор PVS-Studio 7.03версии. Скачать последнюю версию анализатора можно на странице загрузки.