﻿# Проверка кода компилятора Ark Compiler, недавно открытого компанией Huawei

Во время презентаций летом 2019 года Huawei анонсировала технологию Ark Compiler\. По заверениям представителей компании, этот проект с открытым исходным кодом позволяет существенно повысить плавность и отзывчивость Android и сторонних приложений\. Новый интересный открытый проект по традиции должен пройти проверку качества кода с помощью PVS\-Studio\.

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

## Введение

Впервые компилятор Huawei Ark был представлен вместе с запуском смартфонов Huawei P30 и P30 Pro\. По заявлению Huawei, компилятор Ark повышает плавность работы Android на 24%, а скорость отклика – на 44%\. При этом сторонние приложения для Android, после перекомпиляции с помощью Ark, могут работать на 60% быстрее\. Открытый проект имеет название OpenArkCompiler\. Его исходный код доступен на китайском аналоге сайта GitHub – [Gitee](https://gitee.com/openarkcompiler/OpenArkCompiler)\.

Для проверки проекта использовался статический анализатор кода – [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\. Это инструмент для выявления ошибок и потенциальных уязвимостей в исходном коде программ, написанных на языках С, C\+\+, C\# и Java\.

Анализатор быстро справился с проектом на 50К строк кода\. Для маленького проекта и результаты анализа скромные: в статью вошло 11 предупреждений из 39 \(уровней High и Medium\)\.

## Обзор дефектов кода

**Предупреждение 1**

[V502](https://pvs-studio.ru/ru/docs/warnings/v502/) Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. mir\_parser\.cpp 884

```cpp
enum Opcode : uint8 {
  kOpUndef,
  ....
  OP_intrinsiccall,
  OP_intrinsiccallassigned,
  ....
  kOpLast,
};

bool MIRParser::ParseStmtIntrinsiccall(StmtNodePtr &stmt, bool isAssigned) {
  Opcode o = !isAssigned ? (....)
                         : (....);
  auto *intrnCallNode = mod.CurFuncCodeMemPool()->New<IntrinsiccallNode>(....);
  lexer.NextToken();
  if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) {
    intrnCallNode->SetIntrinsic(GetIntrinsicID(lexer.GetTokenKind()));
  } else {
    intrnCallNode->SetIntrinsic(static_cast<MIRIntrinsicID>(....));
  }
  ....
}
```

Нам интересна следующая часть этого кода:

```cpp
if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) {
  ....
}
```

Оператор '\=\=' имеет более высокий приоритет, чем тернарный оператор \(?:\)\. Следовательно, условное выражение вычисляется неправильно\. Написанный код эквивалентен следующему:

```cpp
if ((o == !isAssigned) ? OP_intrinsiccall : OP_intrinsiccallassigned) {
  ....
}
```

А с учётом того, что константы _OP\_intrinsiccall_ и _OP\_intrinsiccallassigned_ имеют ненулевые значения, то это условие всегда возвращает истинное значение\. Тело ветки _else_ является недостижимым кодом\.

**Предупреждение 2**

[V570](https://pvs-studio.ru/ru/docs/warnings/v570/) The 'theDoubleVal' variable is assigned to itself\. lexer\.cpp 283

```cpp
int64 theIntVal = 0;
float theFloatVal = 0.0;
double theDoubleVal = 0.0;

TokenKind MIRLexer
::GetFloatConst(uint32 valStart, uint32 startIdx, bool negative) {
  ....
  theIntVal = static_cast<int>(theFloatVal);
  theDoubleVal = static_cast<double>(theDoubleVal); // <=
  if (theFloatVal == -0) {
    theDoubleVal = -theDoubleVal;
  }
  ....
}
```

Переменная _theDoubleVal_ присваивается сама себе, при этом никак не изменяясь\. Скорее всего, хотели записать результат в переменную _theFloatVal_\. Именно эта переменная затем используется в условии\. В этом случае и приведение типа должно быть к _float_, а не к _double_\. Рискну предположить, что код должен быть таким:

```cpp
theFloatVal = static_cast<float>(theDoubleVal);
if (theFloatVal == -0) {
  theDoubleVal = -theDoubleVal;
```

или даже таким, если просто перепутали переменную в условии:

```cpp
if (theDoubleVal == -0) {
  theDoubleVal = -theDoubleVal;
```

Хотя, возможно, я не прав, и всё должно быть по\-другому\. Код выглядит весьма непонятно для стороннего программиста, такого как я\.

**Предупреждения 3\-5**

[V524](https://pvs-studio.ru/ru/docs/warnings/v524/) It is odd that the body of '\-' function is fully equivalent to the body of '\+' function\. mpl\_number\.h 158

```cpp
template <typename T, typename Tag>
inline Number<T, Tag> operator+(const Number<T, Tag> &lhs,
                                const Number<T, Tag> &rhs) {
  return Number<T, Tag>(lhs.get() + rhs.get());
}

template <typename T, typename Tag>
inline Number<T, Tag> operator-(const Number<T, Tag> &lhs,
                                const Number<T, Tag> &rhs) {
  return Number<T, Tag>(lhs.get() + rhs.get());
}
```

В заголовочном файле _mpl\_number\.h_ продублировали много кода с незначительными изменениями\. И, конечно, допустили ошибки\. В этом примере операторы сложения и вычитания реализованы одинаково\. В теле оператора вычитания забыли поменять знак операции\.

Ещё несколько примеров приведу списком:

* V524 It is odd that the body of '\-' function is fully equivalent to the body of '\+' function\. mpl\_number\.h 233
* V524 It is odd that the body of '\-' function is fully equivalent to the body of '\+' function\. mpl\_number\.h 238

**Предупреждение 6**

[V560](https://pvs-studio.ru/ru/docs/warnings/v560/) A part of conditional expression is always false: \!firstImport\. parser\.cpp 2633

```cpp
bool MIRParser::ParseMIRForImport() {
  ....
  if (paramIsIPA && firstImport) {
    BinaryMplt *binMplt = new BinaryMplt(mod);
    mod.SetBinMplt(binMplt);
    if (!(*binMplt).Import(...., paramIsIPA && !firstImport, paramIsComb)) {
      ....
    }
    ....
  }
  ....
}
```

В теле первого условного выражения переменная _firstImport_ всегда имеет значение _true_\. В этом случае выражение

```cpp
paramIsIPA && !firstImport
```

всегда будет иметь значение _false_\. Этот фрагмент кода либо содержит логическую ошибку, либо его можно упростить, передав константу _false_ в функцию _Import_\.

**Предупреждение 7**

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'idx \>\= 0' is always true\. Unsigned type value is always \>\= 0\. lexer\.h 129

```cpp
char GetCharAtWithLowerCheck(uint32 idx) const {
  return idx >= 0 ? line[idx] : 0;
}
```

Проверка переменной\-индекса _idx_ таким образом \(\>\= 0\) не имеет никакого смысла, так как это беззнаковый тип\. Возможно, здесь стоит добавить проверку другой границы доступа к массиву _line_, либо просто удалить эту бессмысленную проверку\.

**Предупреждение 8**

[V728](https://pvs-studio.ru/ru/docs/warnings/v728/) An excessive check can be simplified\. The '\|\|' operator is surrounded by opposite expressions 'c \!\= '\\"'' and 'c \=\= '\\"''\.  lexer\.cpp 400

```cpp
TokenKind MIRLexer::GetTokenWithPrefixDoubleQuotation() {
  ....
  char c = GetCurrentCharWithUpperCheck();
  while ((c != 0) &&
         (c != '\"' || (c == '\"' && GetCharAtWithLowerCheck(....) == '\\'))) {
    ....
  }
  ....
}
```

Анализатор "поймал" паттерн кода, который можно упростить\. Паттерн выглядит примерно так:

```cpp
A || (!A && smth)
```

Выражение _\!A_ будет всегда иметь значение _true_\. Тогда исходный пример можно упростить до такого:

```cpp
while ((c != 0) && (c != '\"' || (GetCharAtWithLowerCheck(....) == '\\'))) {
  ....
}
```

**Предупреждения 9\-10**

[V728](https://pvs-studio.ru/ru/docs/warnings/v728/) An excessive check can be simplified\. The '\(A && \!B\) \|\| \(\!A && B\)' expression is equivalent to the 'bool\(A\) \!\= bool\(B\)' expression\. mir\_nodes\.cpp 1552

```cpp
bool BinaryNode::Verify() const {
  ....
  if ((IsAddress(GetBOpnd(0)->GetPrimType()) &&
      !IsAddress(GetBOpnd(1)->GetPrimType()))
    ||
     (!IsAddress(GetBOpnd(0)->GetPrimType()) &&
       IsAddress(GetBOpnd(1)->GetPrimType()))) {
    ....
  }
  ....
}
```

Ещё один пример кода, который можно подвергнуть рефакторингу\. Сам пример разбит на несколько строк для удобства\. В оригинале условное выражение написано в две длинные строки, что сильно ухудшало читаемость\. Этот код можно написать проще и понятнее:

```cpp
if (IsAddress(GetBOpnd(0)->GetPrimType()) !=
    IsAddress(GetBOpnd(1)->GetPrimType()))
  ....
}
```

Ещё одно место, где можно провести аналогичный рефакторинг:

* V728 An excessive check can be simplified\. The '\(A && B\) \|\| \(\!A && \!B\)' expression is equivalent to the 'bool\(A\) \=\= bool\(B\)' expression\. bin\_mpl\_import\.cpp 702

**Предупреждение 11**

[V1048](https://pvs-studio.ru/ru/docs/warnings/v1048/) The 'floatSpec\-\>floatStr' variable was assigned the same value\. input\.inl 1356

```cpp
static void SecInitFloatSpec(SecFloatSpec *floatSpec)
{
  floatSpec->floatStr = floatSpec->buffer;
  floatSpec->allocatedFloatStr = NULL;
  floatSpec->floatStrSize = sizeof(floatSpec->buffer) /
                            sizeof(floatSpec->buffer[0]);
  floatSpec->floatStr = floatSpec->buffer;
  floatSpec->floatStrUsedLen = 0;
}
```

Анализатор обнаружил 2 одинаковые строки инициализации переменной _floatSpec\-\>floatStr_\. Скорее всего, лишнюю строку можно удалить\.

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

Совсем недавно мы делали обзор кода [Huawei Cloud DIS SDK](https://pvs-studio.ru/ru/blog/posts/java/0688/)\. Компания Huawei начала активно открывать код для общественности, что не может не радовать сообщество разработчиков\. Такие проекты, как Ark Compiler или Harmony OS, только появились и ещё не стали массовыми\. Вложиться в контроль качества кода проектов на этом этапе будет очень выгодным, так как можно избежать появления потенциальных уязвимостей и критики пользователей\.

## Дополнительные ссылки

1. [Проверка LLVM в 2011](https://pvs-studio.ru/ru/blog/posts/0108/)
1. [Проверка LLVM в 2012](https://pvs-studio.ru/ru/blog/posts/cpp/0155/)
1. [Проверка GCC в 2016](https://pvs-studio.ru/ru/blog/posts/cpp/0425/)
1. [Проверка LLVM в 2016](https://pvs-studio.ru/ru/blog/posts/cpp/0446/)
1. [Проверка PascalABC\.NET в 2017](https://pvs-studio.ru/ru/blog/posts/csharp/0492/)
1. [Проверка Roslyn \(\.NET Compiler Platform\) в 2019](https://pvs-studio.ru/ru/blog/posts/csharp/0622/)
1. [Проверка LLVM в 2019](https://pvs-studio.ru/ru/blog/posts/cpp/0629/)