212 lines
7.1 KiB
C++
212 lines
7.1 KiB
C++
//===- ChromiumCheckModelTest.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.
|
|
|
|
#include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h"
|
|
#include "TestingSupport.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Analysis/CFG.h"
|
|
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <string>
|
|
|
|
using namespace clang;
|
|
using namespace dataflow;
|
|
using namespace test;
|
|
|
|
namespace {
|
|
using ::testing::NotNull;
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
static constexpr char ChromiumCheckHeader[] = R"(
|
|
namespace std {
|
|
class ostream;
|
|
} // namespace std
|
|
|
|
namespace logging {
|
|
class VoidifyStream {
|
|
public:
|
|
VoidifyStream() = default;
|
|
void operator&(std::ostream&) {}
|
|
};
|
|
|
|
class CheckError {
|
|
public:
|
|
static CheckError Check(const char* file, int line, const char* condition);
|
|
static CheckError DCheck(const char* file, int line, const char* condition);
|
|
static CheckError PCheck(const char* file, int line, const char* condition);
|
|
static CheckError PCheck(const char* file, int line);
|
|
static CheckError DPCheck(const char* file, int line, const char* condition);
|
|
|
|
std::ostream& stream();
|
|
|
|
~CheckError();
|
|
|
|
CheckError(const CheckError& other) = delete;
|
|
CheckError& operator=(const CheckError& other) = delete;
|
|
CheckError(CheckError&& other) = default;
|
|
CheckError& operator=(CheckError&& other) = default;
|
|
};
|
|
|
|
} // namespace logging
|
|
|
|
#define LAZY_CHECK_STREAM(stream, condition) \
|
|
!(condition) ? (void)0 : ::logging::VoidifyStream() & (stream)
|
|
|
|
#define CHECK(condition) \
|
|
LAZY_CHECK_STREAM( \
|
|
::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \
|
|
!(condition))
|
|
|
|
#define PCHECK(condition) \
|
|
LAZY_CHECK_STREAM( \
|
|
::logging::CheckError::PCheck(__FILE__, __LINE__, #condition).stream(), \
|
|
!(condition))
|
|
|
|
#define DCHECK(condition) \
|
|
LAZY_CHECK_STREAM( \
|
|
::logging::CheckError::DCheck(__FILE__, __LINE__, #condition).stream(), \
|
|
!(condition))
|
|
|
|
#define DPCHECK(condition) \
|
|
LAZY_CHECK_STREAM( \
|
|
::logging::CheckError::DPCheck(__FILE__, __LINE__, #condition).stream(), \
|
|
!(condition))
|
|
)";
|
|
|
|
// A definition of the `CheckError` class that looks like the Chromium one, but
|
|
// is actually something else.
|
|
static constexpr char OtherCheckHeader[] = R"(
|
|
namespace other {
|
|
namespace logging {
|
|
class CheckError {
|
|
public:
|
|
static CheckError Check(const char* file, int line, const char* condition);
|
|
};
|
|
} // namespace logging
|
|
} // namespace other
|
|
)";
|
|
|
|
/// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
|
|
std::string ReplacePattern(std::string S, const std::string &Pattern,
|
|
const std::string &Replacement) {
|
|
size_t Pos = 0;
|
|
Pos = S.find(Pattern, Pos);
|
|
if (Pos != std::string::npos)
|
|
S.replace(Pos, Pattern.size(), Replacement);
|
|
return S;
|
|
}
|
|
|
|
template <typename Model>
|
|
class ModelAdaptorAnalysis
|
|
: public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> {
|
|
public:
|
|
explicit ModelAdaptorAnalysis(ASTContext &Context)
|
|
: DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(
|
|
Context, /*ApplyBuiltinTransfer=*/true) {}
|
|
|
|
static NoopLattice initialElement() { return NoopLattice(); }
|
|
|
|
void transfer(const CFGElement *E, NoopLattice &, Environment &Env) {
|
|
M.transfer(E, Env);
|
|
}
|
|
|
|
private:
|
|
Model M;
|
|
};
|
|
|
|
template <typename Matcher>
|
|
void runDataflow(llvm::StringRef Code, Matcher Match) {
|
|
const tooling::FileContentMappings FileContents = {
|
|
{"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}};
|
|
|
|
ASSERT_THAT_ERROR(
|
|
checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>(
|
|
AnalysisInputs<ModelAdaptorAnalysis<ChromiumCheckModel>>(
|
|
Code, ast_matchers::hasName("target"),
|
|
[](ASTContext &C, Environment &) {
|
|
return ModelAdaptorAnalysis<ChromiumCheckModel>(C);
|
|
})
|
|
.withASTBuildArgs({"-fsyntax-only",
|
|
"-fno-delayed-template-parsing", "-std=c++17"})
|
|
.withASTBuildVirtualMappedFiles(std::move(FileContents)),
|
|
/*VerifyResults=*/
|
|
[&Match](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
|
|
&Results,
|
|
const AnalysisOutputs &AO) { Match(Results, AO.ASTCtx); }),
|
|
llvm::Succeeded());
|
|
}
|
|
|
|
TEST(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) {
|
|
auto Expectations =
|
|
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
|
|
ASTContext &ASTCtx) {
|
|
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
|
|
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
|
|
|
|
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
|
ASSERT_THAT(FooDecl, NotNull());
|
|
|
|
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
|
|
|
|
EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
|
|
};
|
|
|
|
std::string Code = R"(
|
|
#include "check.h"
|
|
|
|
void target(bool Foo) {
|
|
$check(Foo);
|
|
bool X = true;
|
|
(void)X;
|
|
// [[p]]
|
|
}
|
|
)";
|
|
runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations);
|
|
runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations);
|
|
runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations);
|
|
runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations);
|
|
}
|
|
|
|
TEST(ChromiumCheckModelTest, UnrelatedCheckIgnored) {
|
|
auto Expectations =
|
|
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
|
|
ASTContext &ASTCtx) {
|
|
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
|
|
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
|
|
|
|
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
|
ASSERT_THAT(FooDecl, NotNull());
|
|
|
|
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
|
|
|
|
EXPECT_FALSE(Env.flowConditionImplies(*FooVal));
|
|
};
|
|
|
|
std::string Code = R"(
|
|
#include "othercheck.h"
|
|
|
|
void target(bool Foo) {
|
|
if (!Foo) {
|
|
(void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo");
|
|
}
|
|
bool X = true;
|
|
(void)X;
|
|
// [[p]]
|
|
}
|
|
)";
|
|
runDataflow(Code, Expectations);
|
|
}
|
|
} // namespace
|