clang статический анализатор пропускает некоторые проверки

Я использую статический анализатор clang 4.0.0. Для следующего примера

int fun(){

    int aa = 1,bb = 0;
    int cc = aa/bb; // 1) devide by zero. // Reported by clang

    int *pt = nullptr;
    int a = *pt;    // 2) null pointer dereference. // NOT Reported by clang

    int b;
    int c = a + b;  // 3) Unused initialization. // Reported by clang

    return cc;
}

Статический анализатор Clang сообщает только о двух проблемах 1 и 3 и пропускает проблему 2.

В то время как если бы я изменил порядок выдачи вот так

int fun(){

    int *pt = nullptr;
    int a = *pt;    // 1) null pointer dereference. // Reported by clang

    int aa = 1,bb = 0;
    int cc = aa/bb; // 2) devide by zero. // NOT Reported by clang

    int b;
    int c = a + b;  // 3) Unused initialization. // Reported by clang

    return cc;
}

Затем статический анализатор хлопает по отчетам 1 и 3 и пропускает 2.

Я запускаю статический анализатор clang с помощью этой команды

clang-check.exe -analyze D: \ testsrc \ anothercpp.cpp

Это очень непоследовательное поведение. Независимо от того, в каком порядке находятся проблемы, одна из проблем пропускается. Кроме того, я проверил этот сценарий с помощью clang 5.0.1 только для получения тех же результатов.

Кто-нибудь знает, почему это происходит со статическим анализатором?

Заранее спасибо.

-Гемант


person Hemant    schedule 09.02.2018    source источник
comment
проверен с последней версией 5.0.1 дает тот же результат   -  person Hemant    schedule 09.02.2018
comment
Интересно, не случайно ли это вообще, а может быть связано с первой проблемой, которая уже потенциально может вызвать разного рода хаос (любая из ваших первых проблем связана с неопределенным поведением и будет задерживаться в большинстве систем). Это может помешать последующему анализу. Я также мог представить, что дальнейшие проверки чувствительности к потоку (где отслеживаются значения переменных) отключены на этом пути, потому что они могут сообщать о фиктивных ошибках в целом. Ошибка 3 может быть надежно обнаружена без проведения такого анализа, поэтому о ней всегда сообщается. Но это всего лишь предположения.   -  person PaulR    schedule 09.02.2018
comment
Похоже, анализатор прекращает обработку после первого появления некоторых проверок. Не уверен, как именно это сделано, но если они делают два прохода - один с разыменованием нулевого указателя и делением на нулевые разрывы после первой ошибки. Вы даже можете попробовать решить одну и ту же проблему дважды подряд - например, он перестает есть первое из двух деление на ноль.   -  person Mihayl    schedule 09.02.2018
comment
Если я добавлю второе разыменование нулевого указателя после первого, то второе будет отображаться не как разыменование нулевого указателя, а как неиспользуемая переменная   -  person Hemant    schedule 09.02.2018
comment
А если вы хотите погрузиться в github.com/llvm-mirror/clang / tree / master / lib / StaticAnalyzer   -  person Mihayl    schedule 09.02.2018


Ответы (2)


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

Когда DereferenceChecker, предположительно найденный разыменование нулевого указателя сообщает об ошибке, оно создает «узел ошибки», который останавливает дальнейшее исследование для чувствительного к пути анализа.

void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
                                   CheckerContext &C) const {
  // Generate an error node.
ExplodedNode *N = C.generateErrorNode(State);

CheckerContext :: generateErrorNode - это задокументировано, чтобы остановить исследование данного пути в программе.

  /// \brief Generate a transition to a node that will be used to report
  /// an error. This node will be a sink. That is, it will stop exploration of
  /// the given path.
  ///
  /// @param State The state of the generated node.
  /// @param Tag The tag to uniquely identify the creation site. If null,
  ///        the default tag for the checker will be used.
  ExplodedNode *generateErrorNode(ProgramStateRef State = nullptr,
                                  const ProgramPointTag *Tag = nullptr) {
    return generateSink(State, Pred,
                       (Tag ? Tag : Location.getTag()));
}

Это имеет смысл, потому что после такой серьезной ошибки, как разыменование нулевого указателя, можно сделать не так много значимых прогнозов относительно программы. Поскольку разыменование нулевого указателя - это неопределенное поведение в C ++, стандарт позволяет случаться. Только взглянув на детали программы и среды, в которой она работает, можно сделать больше прогнозов. Скорее всего, эти прогнозы были бы вне рамок статического анализатора.

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

Обнаружение неиспользуемой переменной не требует анализа с учетом пути. Таким образом, этот тип проверки по-прежнему будет работать.

person PaulR    schedule 09.02.2018

Все это показывает, что для статического анализатора нельзя просто написать несколько примитивных искусственных тестов. Я подробно обсуждал это в статье «Почему мне не нравятся синтетические тесты». Скорее всего, по какой-то внутренней причине (но уж точно не из-за ошибки) анализатор Clang пропускает фрагмент кода, следующий за нулевой операцией разыменования, что, очевидно, приводит к неопределенному поведению. Или может пропустить из-за деления на ноль. И в этом нет ничего плохого. На самом деле не имеет значения, что написано после нулевого разыменования или деления на ноль: последующий код все равно не выполняется. Итак, эта проблема не имеет ничего общего с ошибкой компилятора / анализатора, а скорее является результатом неаккуратно написанных подобных тестов. Написание хорошего теста для диагностики - сложная работа, требующая от вас осторожности и понимания многих тонких деталей.

person AndreyKarpov    schedule 09.02.2018