﻿# Проверка Clang 11 с помощью PVS\-Studio

Время от времени нам приходится писать статьи о проверке очередной версии какого\-то компилятора\. Это неинтересно\. Однако, как показывает практика, если этого долго не делать, люди начинают сомневаться, достоин ли анализатор PVS\-Studio звания хорошего ловца багов и потенциальных уязвимостей\. Возможно, новый компилятор уже умеет это делать? Да, компиляторы не стоят на месте\. Однако развивается и PVS\-Studio, вновь и вновь демонстрируя возможности находить ошибки даже в коде таких высококачественных проектов, как компиляторы\.

![0771_Clang11_ru/image1.png](https://import.viva64.com/docx/blog/0771_Clang11_ru/image1.png)

## Время перепроверить код Clang

Честно говоря, за основу этого текста я взял статью "[Проверка компилятора GCC 10 с помощью PVS\-Studio](https://pvs-studio.ru/ru/blog/posts/cpp/0727/)"\. Так что если вам покажется, что вы уже читали где\-то некоторые абзацы, то вам не кажется :\)\.

Не секрет, что в компиляторах существуют свои собственные встроенные статические анализаторы кода и они тоже развиваются\. Поэтому, время от времени мы пишем статьи о том, что статический анализатор PVS\-Studio может находить ошибки даже внутри компиляторов и что мы не зря едим хлеб :\)\.

На самом деле, сравнивать классические статические анализаторы с компиляторами нельзя\. Статические анализаторы – это не только поиск ошибок в коде, но и развитая инфраструктура\. Например, это интеграция с такими системами, как SonarQube, Azure DevOps, Travis CI, CircleCI, GitLab CI/CD, Jenkins, Visual Studio\. Это развитые механизмы массового подавления предупреждений, что позволяет быстро начать использовать PVS\-Studio даже в большом старом проекте\. Это рассылка уведомлений\. И так далее, и так далее\. Однако, всё равно первый вопрос, который задают: "Может ли PVS\-Studio найти такое, что не могут найти компиляторы?"\. А значит, мы будем вновь и вновь писать статьи о проверке этих самих компиляторов\.

Вернёмся к теме проверки проекта Clang\. Нет нужды останавливаться на этом проекте и рассказывать, что он из себя представляет\. На самом деле, был проверен код не только самого Clang 11, но и библиотеки LLVM 11, на которой он построен\. С точки зрения этой статьи, нет разницы, найден дефект в коде компилятора или библиотеки\. 

Код Clang/LLVM показался мне гораздо понятнее кода GCC\. По крайней мере, нет всех этих ужасных макросов, и активно применяются современные возможности языка C\+\+\.

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

Пример для тестов:

```cpp
Spaces.SpacesInParentheses = false;               // <=
Spaces.SpacesInCStyleCastParentheses = true;      // <=
verifyFormat("Type *A = ( Type * )P;", Spaces);
verifyFormat("Type *A = ( vector<Type *, int *> )P;", Spaces);
verifyFormat("x = ( int32 )y;", Spaces);
verifyFormat("int a = ( int )(2.0f);", Spaces);
verifyFormat("#define AA(X) sizeof((( X * )NULL)->a)", Spaces);
verifyFormat("my_int a = ( my_int )sizeof(int);", Spaces);
verifyFormat("#define x (( int )-1)", Spaces);

// Run the first set of tests again with:
Spaces.SpacesInParentheses = false;               // <=
Spaces.SpaceInEmptyParentheses = true;
Spaces.SpacesInCStyleCastParentheses = true;      // <=
verifyFormat("call(x, y, z);", Spaces);
verifyFormat("call( );", Spaces);
```

Анализатор предупреждает, что переменным присваиваются те же самые значения, которые и так в них лежат:

* V1048 The 'Spaces\.SpacesInParentheses' variable was assigned the same value\. FormatTest\.cpp 11554
* V1048 The 'Spaces\.SpacesInCStyleCastParentheses' variable was assigned the same value\. FormatTest\.cpp 11556

Формально анализатор выдал верное срабатывание, и это является тем фрагментом кода, который стоит упростить или исправить\. При этом понятно, что на самом деле всё хорошо и что править что\-то тоже смысла нет\.

Другой пример: анализатор выдаёт огромное количество предупреждений на автосгенерированный файл Options\.inc\. В файле можно видеть "простыню кода":

![0771_Clang11_ru/image3.png](https://import.viva64.com/docx/blog/0771_Clang11_ru/image3.png)

И на всё это PVS\-Studio сыплет предупреждения:

* V501 There are identical sub\-expressions to the left and to the right of the '\=\=' operator: nullptr \=\= nullptr Options\.inc 26
* V501 There are identical sub\-expressions to the left and to the right of the '\=\=' operator: nullptr \=\= nullptr Options\.inc 27
* V501 There are identical sub\-expressions to the left and to the right of the '\=\=' operator: nullptr \=\= nullptr Options\.inc 28
* и так далее на каждую строчку\.\.\.

Всё это нестрашно\. Всё это [можно](https://pvs-studio.ru/ru/blog/posts/cpp/0523/) победить: отключить проверку лишних файлов, разметить некоторые макросы и функции, подавить определённые типы срабатываний и так далее\. Можно, но заниматься этим в рамках задачи по написанию статьи неинтересно\. Поэтому, я поступил точно так же, как и в статье про компилятор GCC\. Я изучал отчёт, пока у меня не набралось 11 интересных примеров кода для написания статьи\. Почему 11? Я подумал, что раз версия Clang равна 11, то пусть и фрагментов будет 11 :\)\.

## 11 подозрительных фрагментов кода

**Фрагмент N1, деление по модулю на единицу**

![0771_Clang11_ru/image4.png](https://import.viva64.com/docx/blog/0771_Clang11_ru/image4.png)

Классная ошибка\! Люблю такие\!

```cpp
void Act() override {
  ....
  // If the value type is a vector, and we allow vector select, then in 50%
  // of the cases generate a vector select.
  if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 1)) {
    unsigned NumElem =
        cast<FixedVectorType>(Val0->getType())->getNumElements();
    CondTy = FixedVectorType::get(CondTy, NumElem);
  }
  ....
}
```

Предупреждение PVS\-Studio: V1063 The modulo by 1 operation is meaningless\. The result will always be zero\. llvm\-stress\.cpp 631

Для того, чтобы получать случайное значение 0 или 1, используют деление по модулю\. Но, видимо, это значение _1_ сбивает с толку, и люди допускают классический паттерн ошибки, деля на единицу, хотя необходимо делит на двойку\. Операция _X % 1_ не имеет смысла, так как в результате даёт всегда _0_\. Правильный вариант кода:

```cpp
if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 2)) {
```

Диагностика V1063, недавно появившаяся в PVS\-Studio, до безобразия простая, но, как видите, работает\.

Как мы знаем, разработчики компиляторов посматривают на то, что мы делаем, и заимствуют наши наработки\. Ничего плохого в этом нет\. Нам приятно, что [PVS\-Studio является двигателем прогресса](https://pvs-studio.ru/ru/blog/posts/0659/)\. Засекаем время, через сколько такая же диагностика появится в Clang и GCC :\)\.

**Фрагмент N2, опечатка в условии**

```cpp
class ReturnValueSlot {
  ....
  bool isNull() const { return !Addr.isValid(); }
  ....
};

static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
                                   const FunctionDecl *F2, unsigned NumParams) {
  ....
  unsigned I1 = 0, I2 = 0;
  for (unsigned I = 0; I != NumParams; ++I) {
    QualType T1 = NextParam(F1, I1, I == 0);
    QualType T2 = NextParam(F2, I2, I == 0);
    if (!T1.isNull() && !T1.isNull() && !Context.hasSameUnqualifiedType(T1, T2))
      return false;
  }
  return true;
}
```

Предупреждение PVS\-Studio: V501 There are identical sub\-expressions to the left and to the right of the '&&' operator: \!T1\.isNull\(\) && \!T1\.isNull\(\) SemaOverload\.cpp 9493

Дважды выполняется проверка _\!T1\.isNull\(\)_\. Это явная опечатка, и вторая часть условия должна проверять переменную _T2_\.

**Фрагмент N3, потенциальный выход за границу массива**

```cpp
std::vector<Decl *> DeclsLoaded;

SourceLocation ASTReader::getSourceLocationForDeclID(GlobalDeclID ID) {
  ....
  unsigned Index = ID - NUM_PREDEF_DECL_IDS;

  if (Index > DeclsLoaded.size()) {
    Error("declaration ID out-of-range for AST file");
    return SourceLocation();
  }

  if (Decl *D = DeclsLoaded[Index])
    return D->getLocation();
  ....
}
```

Предупреждение PVS\-Studio: V557 Array overrun is possible\. The 'Index' index is pointing beyond array bound\. ASTReader\.cpp 7318

Предположим, в массиве хранится один элемент, и значение переменной _Index_ тоже равно единице\. Условие _\(1 \> 1\)_ является ложным, и как результат произойдёт выход за границу массива\. Правильная проверка:

```cpp
if (Index >= DeclsLoaded.size()) {
```

**Фрагмент N4, порядок вычислений аргументов**

```cpp
void IHexELFBuilder::addDataSections() {
  ....
  uint32_t SecNo = 1;
  ....
  Section = &Obj->addSection<OwnedDataSection>(
      ".sec" + std::to_string(SecNo++), RecAddr,
      ELF::SHF_ALLOC | ELF::SHF_WRITE, SecNo);
  ....
}
```

Предупреждение PVS\-Studio: V567 Unspecified behavior\. The order of argument evaluation is not defined for 'addSection' function\. Consider inspecting the 'SecNo' variable\. Object\.cpp 1223

Обратите внимание, что аргумент _SecNo_ используется дважды, да ещё попутно инкрементируется\. Вот только невозможно сказать, в каком порядке будут вычисляться аргументы\. Поэтому результат будет меняться в зависимости от версии компилятора или настроек компиляции\.

Поясню это синтетическим примером:

```cpp
#include <cstdio>
int main()
{
  int i = 1;
  printf("%d, %d\n", i, i++);
  return 0;
}
```

В зависимости от компилятора, может быть распечатано, как "1, 1", так и "2, 1"\. Воспользовавшись Compiler Explorer я получит следующие результаты:

* программа, скомпилированная с помощью Clang 11\.0\.0, [выдаёт](https://godbolt.org/z/YYs4Te): 1, 1\.
* программа, скомпилированная с помощью GCC 10\.2, [выдаёт](https://godbolt.org/z/q9oGz8): 2, 1\.

Что интересно, для этого простого случая компилятор Clang выдаёт предупреждение: 

```cpp
<source>:6:26: warning:
unsequenced modification and access to 'i' [-Wunsequenced]
printf("%d, %d\n", i, i++);
```

Видимо, в реальных условиях это предупреждение не помогло\. Диагностика или отключена, так как неудобна для практического использования, или компилятор не смог предупредить о более сложном случае\.

**Фрагмент N5, странная повторная проверка**

```cpp
template <class ELFT>
void GNUStyle<ELFT>::printVersionSymbolSection(const ELFFile<ELFT> *Obj,
                                               const Elf_Shdr *Sec) {

  ....
  Expected<StringRef> NameOrErr =
      this->dumper()->getSymbolVersionByIndex(Ndx, IsDefault);
  if (!NameOrErr) {
    if (!NameOrErr) {
      unsigned SecNdx = Sec - &cantFail(Obj->sections()).front();
      this->reportUniqueWarning(createError(
          "unable to get a version for entry " + Twine(I) +
          " of SHT_GNU_versym section with index " + Twine(SecNdx) + ": " +
          toString(NameOrErr.takeError())));
    }
    Versions.emplace_back("<corrupt>");
    continue;
  }
  ....
}
```

Предупреждение PVS\-Studio: V571 Recurring check\. The 'if \(\!NameOrErr\)' condition was already verified in line 4666\. ELFDumper\.cpp 4667

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

**Фрагмент N6, разыменование потенциально нулевого указателя**

```cpp
void RewriteObjCFragileABI::RewriteObjCClassMetaData(
  ObjCImplementationDecl *IDecl, std::string &Result)
{
  ObjCInterfaceDecl *CDecl = IDecl->getClassInterface();

  if (CDecl->isImplicitInterfaceDecl()) {
    RewriteObjCInternalStruct(CDecl, Result);
  }

  unsigned NumIvars = !IDecl->ivar_empty()
  ? IDecl->ivar_size()
  : (CDecl ? CDecl->ivar_size() : 0);
  ....
}
```

Предупреждение PVS\-Studio: V595 The 'CDecl' pointer was utilized before it was verified against nullptr\. Check lines: 5275, 5284\. RewriteObjC\.cpp 5275

В процессе выполнения первой проверки указатель _CDecl_ всегда смело разыменовывается:

```cpp
if (CDecl->isImplicitInterfaceDecl())
```

И только из кода, написанного ниже, становится понятно, что этот указатель может быть нулевым:

```cpp
(CDecl ? CDecl->ivar_size() : 0)
```

Скорее всего, первая проверка должна выглядеть так:

```cpp
if (CDecl && CDecl->isImplicitInterfaceDecl())
```

**Фрагмент N7, разыменование потенциально нулевого указателя**

```cpp
bool
Sema::InstantiateClass(....)
{
  ....
  NamedDecl *ND = dyn_cast<NamedDecl>(I->NewDecl);
  CXXRecordDecl *ThisContext =
      dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
  CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
                              ND && ND->isCXXInstanceMember());
  ....
}
```

Предупреждение PVS\-Studio: V595 The 'ND' pointer was utilized before it was verified against nullptr\. Check lines: 2803, 2805\. SemaTemplateInstantiate\.cpp 2803

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

**Фрагмент N8, функция продолжает выполнение, несмотря на ошибочное состояние**

```cpp
bool VerifyObject(llvm::yaml::Node &N,
                  std::map<std::string, std::string> Expected) {
  ....
  auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
  if (!V) {
    ADD_FAILURE() << KS << " is not a string";
    Match = false;
  }
  std::string VS = V->getValue(Tmp).str();
  ....
}
```

Предупреждение PVS\-Studio: V1004 The 'V' pointer was used unsafely after it was verified against nullptr\. Check lines: 61, 65\. TraceTests\.cpp 65

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

```cpp
auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
if (!V) {
  ADD_FAILURE() << KS << " is not a string";
  Match = false;
  return false;
}
std::string VS = V->getValue(Tmp).str();
```

**Фрагмент N9, опечатка**

```cpp
const char *tools::SplitDebugName(const ArgList &Args, const InputInfo &Input,
                                  const InputInfo &Output) {
  if (Arg *A = Args.getLastArg(options::OPT_gsplit_dwarf_EQ))
    if (StringRef(A->getValue()) == "single")
      return Args.MakeArgString(Output.getFilename());

  Arg *FinalOutput = Args.getLastArg(options::OPT_o);
  if (FinalOutput && Args.hasArg(options::OPT_c)) {
    SmallString<128> T(FinalOutput->getValue());
    llvm::sys::path::replace_extension(T, "dwo");
    return Args.MakeArgString(T);
  } else {
    // Use the compilation dir.
    SmallString<128> T(
        Args.getLastArgValue(options::OPT_fdebug_compilation_dir));
    SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput()));
    llvm::sys::path::replace_extension(F, "dwo");
    T += F;
    return Args.MakeArgString(F);       // <=
  }
}
```

Предупреждение PVS\-Studio: V1001 The 'T' variable is assigned but is not used by the end of the function\. CommonArgs\.cpp 873

Обратите внимание на конец функции\. Локальная переменная _T_ изменяется, но никак не используется\. Скорее всего, это опечатка и функция должна завершаться следующими строчками кода:

```cpp
T += F;
return Args.MakeArgString(T);
```

**Фрагмент N10, делитель равен нулю**

```cpp
typedef int32_t si_int;
typedef uint32_t su_int;

typedef union {
  du_int all;
  struct {
#if _YUGA_LITTLE_ENDIAN
    su_int low;
    su_int high;
#else
    su_int high;
    su_int low;
#endif // _YUGA_LITTLE_ENDIAN
  } s;
} udwords;

COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem) {
  ....
  if (d.s.low == 0) {
    if (d.s.high == 0) {
      // K X
      // ---
      // 0 0
      if (rem)
        *rem = n.s.high % d.s.low;
      return n.s.high / d.s.low;
    }
  ....
}
```

Предупреждения PVS\-Studio:

* V609 Mod by zero\. Denominator 'd\.s\.low' \=\= 0\. udivmoddi4\.c 61
* V609 Divide by zero\. Denominator 'd\.s\.low' \=\= 0\. udivmoddi4\.c 62

Не знаю ошибка это или какая\-то хитрая задумка, но код очень странный\. Есть две обыкновенные целочисленные переменные, и одна делится на другую\. Что интересно, это происходит, только если обе переменные равны нулю\. Что всё это значит?

**Фрагмент N11, Copy\-Paste**

```cpp
bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(....)
{
  ....
  StringRef FName = II->getName();
  ....
  if (FName == "postEvent" &&
      FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
    return true;
  }

  if (FName == "postEvent" &&
      FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
    return true;
  }
  ....
}
```

Предупреждение PVS\-Studio: V581 The conditional expressions of the 'if' statements situated alongside each other are identical\. Check lines: 3108, 3113\. MallocChecker\.cpp 3113

Фрагмент кода был скопирован, но никак не изменён\. Второй фрагмент следует либо удалить, либо модифицировать, чтобы он начал выполнять какую\-то полезную проверку\.

## Заключение

[![getTrialImageLink](https://cdn.pvs-studio.ru/media/get_trial_insert_ru.png)](https://pvs-studio.ru/ru/pvs-studio-download/)

Напоминаю, что для проверки открытых проектов вы можете использовать [вот этот бесплатный вариант лицензии](https://pvs-studio.ru/ru/blog/posts/0600/)\. Кстати, есть и другие варианты бесплатного лицензирования PVS\-Studio, в том числе даже для закрытых проектов\. Они перечислены здесь: "[Бесплатные варианты лицензирования PVS\-Studio](https://pvs-studio.ru/ru/blog/posts/0614/)"\. Спасибо за внимание\. 

## Другие наши статьи про проверку компиляторов

1. [Проверка LLVM \(Clang\)](https://pvs-studio.ru/ru/blog/posts/0108/) \(август 2011\), [вторая проверка](https://pvs-studio.ru/ru/blog/posts/cpp/0155/) \(август 2012\), [третья проверка](https://pvs-studio.ru/ru/blog/posts/cpp/0446/) \(октябрь 2016\), [четвёртая проверка](https://pvs-studio.ru/ru/blog/posts/cpp/0629/) \(апрель 2019\)
1. [Проверка GCC](https://pvs-studio.ru/ru/blog/posts/cpp/0425/) \(август 2016\), [вторая проверка](https://pvs-studio.ru/ru/blog/posts/cpp/0727/) \(апрель 2020\)
1. [Проверка Huawei Ark Compiler](https://pvs-studio.ru/ru/blog/posts/cpp/0690/) \(декабрь 2019\)
1. [Проверка \.NET Compiler Platform \("Roslyn"\)](https://pvs-studio.ru/ru/blog/posts/csharp/0363/) \(декабрь 2015\), [вторая проверка](https://pvs-studio.ru/ru/blog/posts/csharp/0622/) \(апрель 2019\)
1. [Проверка Roslyn Analyzers](https://pvs-studio.ru/ru/blog/posts/csharp/0664/) \(август 2019\)
1. [Проверка PascalABC\.NET](https://pvs-studio.ru/ru/blog/posts/csharp/0492/) \(март 2017\)