mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-27 16:06:05 +00:00

This commit deletes the "simple" constructor of `CallDescription` which did not require a `CallDescription::Mode` argument and always used the "wildcard" mode `CDM::Unspecified`. A few months ago, this vague matching mode was used by many checkers, which caused bugs like https://github.com/llvm/llvm-project/issues/81597 and https://github.com/llvm/llvm-project/issues/88181. Since then, my commits improved the available matching modes and ensured that all checkers explicitly specify the right matching mode. After those commits, the only remaining references to the "simple" constructor were some unit tests; this commit updates them to use an explicitly specified matching mode (often `CDM::SimpleFunc`). The mode `CDM::Unspecified` was not deleted in this commit because it's still a reasonable choice in `GenericTaintChecker` and a few unit tests.
301 lines
8.9 KiB
C++
301 lines
8.9 KiB
C++
//===- unittests/StaticAnalyzer/NoStateChangeFuncVisitorTest.cpp ----------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CheckerRegistration.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
|
|
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
|
|
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Base classes for testing NoStateChangeFuncVisitor.
|
|
//
|
|
// Testing is done by observing a very simple trait change from one node to
|
|
// another -- the checker sets the ErrorPrevented trait to true if
|
|
// 'preventError()' is called in the source code, and sets it to false if
|
|
// 'allowError()' is called. If this trait is false when 'error()' is called,
|
|
// a warning is emitted.
|
|
//
|
|
// The checker then registers a simple NoStateChangeFuncVisitor to add notes to
|
|
// inlined functions that could have, but neglected to prevent the error.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrorPrevented, bool)
|
|
|
|
namespace clang {
|
|
namespace ento {
|
|
namespace {
|
|
|
|
class ErrorNotPreventedFuncVisitor : public NoStateChangeFuncVisitor {
|
|
public:
|
|
ErrorNotPreventedFuncVisitor()
|
|
: NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough) {}
|
|
|
|
virtual PathDiagnosticPieceRef
|
|
maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
|
|
const ObjCMethodCall &Call,
|
|
const ExplodedNode *N) override {
|
|
return nullptr;
|
|
}
|
|
|
|
virtual PathDiagnosticPieceRef
|
|
maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
|
|
const CXXConstructorCall &Call,
|
|
const ExplodedNode *N) override {
|
|
return nullptr;
|
|
}
|
|
|
|
virtual PathDiagnosticPieceRef
|
|
maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
|
|
const ExplodedNode *N) override {
|
|
PathDiagnosticLocation L = PathDiagnosticLocation::create(
|
|
N->getLocation(),
|
|
N->getState()->getStateManager().getContext().getSourceManager());
|
|
return std::make_shared<PathDiagnosticEventPiece>(
|
|
L, "Returning without prevening the error");
|
|
}
|
|
|
|
void Profile(llvm::FoldingSetNodeID &ID) const override {
|
|
static int Tag = 0;
|
|
ID.AddPointer(&Tag);
|
|
}
|
|
};
|
|
|
|
template <class Visitor>
|
|
class StatefulChecker : public Checker<check::PreCall> {
|
|
const BugType BT{this, "error()", categories::SecurityError};
|
|
|
|
public:
|
|
void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
|
|
if (CallDescription{CDM::SimpleFunc, {"preventError"}, 0}.matches(Call)) {
|
|
C.addTransition(C.getState()->set<ErrorPrevented>(true));
|
|
return;
|
|
}
|
|
|
|
if (CallDescription{CDM::SimpleFunc, {"allowError"}, 0}.matches(Call)) {
|
|
C.addTransition(C.getState()->set<ErrorPrevented>(false));
|
|
return;
|
|
}
|
|
|
|
if (CallDescription{CDM::SimpleFunc, {"error"}, 0}.matches(Call)) {
|
|
if (C.getState()->get<ErrorPrevented>())
|
|
return;
|
|
const ExplodedNode *N = C.generateErrorNode();
|
|
if (!N)
|
|
return;
|
|
auto R =
|
|
std::make_unique<PathSensitiveBugReport>(BT, "error() called", N);
|
|
R->template addVisitor<Visitor>();
|
|
C.emitReport(std::move(R));
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
} // namespace ento
|
|
} // namespace clang
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Non-thorough analysis: only the state right before and right after the
|
|
// function call is checked for the difference in trait value.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace clang {
|
|
namespace ento {
|
|
namespace {
|
|
|
|
class NonThoroughErrorNotPreventedFuncVisitor
|
|
: public ErrorNotPreventedFuncVisitor {
|
|
public:
|
|
virtual bool
|
|
wasModifiedInFunction(const ExplodedNode *CallEnterN,
|
|
const ExplodedNode *CallExitEndN) override {
|
|
return CallEnterN->getState()->get<ErrorPrevented>() !=
|
|
CallExitEndN->getState()->get<ErrorPrevented>();
|
|
}
|
|
};
|
|
|
|
void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
|
|
AnalyzerOptions &AnOpts) {
|
|
AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
|
|
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
|
|
Registry
|
|
.addChecker<StatefulChecker<NonThoroughErrorNotPreventedFuncVisitor>>(
|
|
"test.StatefulChecker", "Description", "");
|
|
});
|
|
}
|
|
|
|
TEST(NoStateChangeFuncVisitor, NonThoroughFunctionAnalysis) {
|
|
std::string Diags;
|
|
EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
//preventError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags,
|
|
"test.StatefulChecker: Calling 'g' | Returning without prevening "
|
|
"the error | Returning from 'g' | error() called\n");
|
|
|
|
Diags.clear();
|
|
|
|
EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
preventError();
|
|
allowError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags,
|
|
"test.StatefulChecker: Calling 'g' | Returning without prevening "
|
|
"the error | Returning from 'g' | error() called\n");
|
|
|
|
Diags.clear();
|
|
|
|
EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
preventError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags, "");
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace ento
|
|
} // namespace clang
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Thorough analysis: only the state right before and right after the
|
|
// function call is checked for the difference in trait value.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace clang {
|
|
namespace ento {
|
|
namespace {
|
|
|
|
class ThoroughErrorNotPreventedFuncVisitor
|
|
: public ErrorNotPreventedFuncVisitor {
|
|
public:
|
|
virtual bool
|
|
wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
|
|
const ExplodedNode *CallExitBeginN) override {
|
|
return CurrN->getState()->get<ErrorPrevented>() !=
|
|
CallExitBeginN->getState()->get<ErrorPrevented>();
|
|
}
|
|
};
|
|
|
|
void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
|
|
AnalyzerOptions &AnOpts) {
|
|
AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
|
|
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
|
|
Registry.addChecker<StatefulChecker<ThoroughErrorNotPreventedFuncVisitor>>(
|
|
"test.StatefulChecker", "Description", "");
|
|
});
|
|
}
|
|
|
|
TEST(NoStateChangeFuncVisitor, ThoroughFunctionAnalysis) {
|
|
std::string Diags;
|
|
EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
//preventError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags,
|
|
"test.StatefulChecker: Calling 'g' | Returning without prevening "
|
|
"the error | Returning from 'g' | error() called\n");
|
|
|
|
Diags.clear();
|
|
|
|
EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
preventError();
|
|
allowError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags, "test.StatefulChecker: error() called\n");
|
|
|
|
Diags.clear();
|
|
|
|
EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
|
|
void error();
|
|
void preventError();
|
|
void allowError();
|
|
|
|
void g() {
|
|
preventError();
|
|
}
|
|
|
|
void f() {
|
|
g();
|
|
error();
|
|
}
|
|
)", Diags));
|
|
EXPECT_EQ(Diags, "");
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace ento
|
|
} // namespace clang
|