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

Введение

Компания Huawei впервые анонсировала компилятор Ark при запуске новых моделей смартфонов P30 и P30 Pro. Утверждается, что компилятор Ark улучшит беглость системы Android на 24% и скорость отклика на 44%. Сторонние приложения для Android также получат ускорение на 60% после перекомпиляции с помощью компилятора Ark. Версия проекта с открытым исходным кодом называется OpenArkCompiler; его исходный код доступен на Gitee, китайском форке GitHub.

Для проверки этого проекта я использовал статический анализатор кода PVS-Studio. Это инструмент для обнаружения ошибок и потенциальных уязвимостей в исходном коде программ C, C++, C# и Java.

Размер проекта 50 KLOC и проверка не заняла много времени. Небольшой проект означает скромные результаты: в статье речь пойдет об 11 из 39 предупреждений (высокого и среднего уровня).

В коде обнаружены дефекты

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

V502 Возможно, оператор ?: работает не так, как ожидалось. Оператор ?: имеет более низкий приоритет, чем оператор ==. mir_parser.cpp 884

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>(....));
  }
  ....
}

Нас интересует следующая часть:

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

Приоритет оператора «==» выше, чем у тернарного оператора (?:). Поэтому условное выражение оценивается в неправильном порядке и эквивалентно следующему коду:

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

Поскольку константы OP_intrinsiccall и OP_intrinsiccallassigned не равны нулю, условие будет возвращать true все время, что означает, что тело elseветвь является недостижимым кодом.

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

V570 Переменная theDoubleVal присваивается самой себе. лексер.cpp 283

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. Я думаю, что исправленная версия должна выглядеть так:

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

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

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

Я все еще могу ошибаться; возможно, этот код следует исправить каким-то совершенно другим способом. Это выглядит неясным для стороннего программиста, такого как я.

Предупреждения 3–5

V524 Странно, что тело функции - полностью эквивалентно телу функции +. mpl_number.h 158

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 Странно, что тело функции «-» полностью эквивалентно телу функции «+». mpl_number.h 233
  • V524 Странно, что тело функции «-» полностью эквивалентно телу функции «+». mpl_number.h 238

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

V560 Часть условного выражения всегда ложна: !firstImport. parser.cpp 2633

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

Переменная firstImport, проверенная в первом условном выражении, всегда имеет значение true. Это означает, что следующее выражение всегда будет оцениваться как false:

paramIsIPA && !firstImport

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

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

V547 Выражение idx ›= 0 всегда верно. Значение беззнакового типа всегда ›= 0. lexer.h 129

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

Эта проверка индексной переменной idx (›= 0) не имеет смысла, поскольку переменная не имеет знака. Возможно, оно предназначалось для сравнения с каким-то другим значением в качестве порога индексации в массив line, либо эту бессмысленную проверку нужно вообще убрать.

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

V728 Излишняя проверка может быть упрощена. Оператор ‘||’ окружен противоположными выражениями ‘c != ‘\»’’ и ‘c == ‘\»’’. лексер.cpp 400

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

Анализатор обнаружил шаблон кода, который можно упростить. Это похоже на эту форму:

A || (!A && smth)

Выражение !A всегда будет оцениваться как true, поэтому исходное выражение можно упростить следующим образом:

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

Предупреждения 9–10

V728 Излишняя проверка может быть упрощена. ‘(A && !B) || (!A && B)’ эквивалентно выражению ‘bool(A) != bool(B)’. mir_nodes.cpp 1552

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

Это еще один фрагмент, который нуждается в рефакторинге. Чтобы сделать его более читабельным, я разбил код на несколько строк, в то время как в исходном виде условие занимает две полные строки, из-за чего разобраться в нем стало намного сложнее. Код можно переписать в более простом и понятном виде:

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

Аналогичным образом рефакторим еще один фрагмент кода:

  • V728 Излишняя проверка может быть упрощена. ‘(А && В) || (!A && !B)’ эквивалентно выражению ‘bool(A) == bool(B)’. bin_mpl_import.cpp 702

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

V1048 Переменной floatSpec-›floatStr присвоено такое же значение. input.inl 1356

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;
}

Анализатор обнаружил две одинаковые инициализации переменной floatSpec-›floatStr. Я считаю, что вторую повторяющуюся строку можно удалить.

Вывод

Буквально несколько дней назад мы проверили еще один проект Huawei — Huawei Cloud DIS SDK. В настоящее время компания делает свои проекты открытыми, что является хорошей новостью для сообщества разработчиков. Такие проекты, как Ark Compiler или Harmony OS, очень молоды и еще не стали популярными, поэтому инвестиции в контроль качества кода на этом этапе должны быть очень выгодными, поскольку это может помочь избежать потенциальных уязвимостей и критики со стороны клиентов.

использованная литература