blob: 92d3bca118e64a11fad1516fa800334ce3d48bd9 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/inference/replace_macros.h"
#include <memory>
#include <string>
#include <string_view>
#include "nullability/inference/ctn_replacement_macros.h"
#include "nullability/macro_arg_capture.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h" // IWYU pragma: keep
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using ::clang::CallExpr;
using ::clang::ast_matchers::anyOf;
using ::clang::ast_matchers::callExpr;
using ::clang::ast_matchers::functionDecl;
using ::clang::ast_matchers::hasDeclaration;
using ::clang::ast_matchers::hasName;
using ::clang::ast_matchers::match;
using ::clang::ast_matchers::selectFirst;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::NotNull;
clang::TestInputs getInputs(llvm::StringRef Source) {
clang::TestInputs Inputs = Source;
for (const auto& Entry : llvm::ArrayRef(ctn_replacement_macros_create(),
ctn_replacement_macros_size()))
Inputs.ExtraFiles.try_emplace(Entry.name, Entry.data);
Inputs.ExtraArgs.push_back("-include");
Inputs.ExtraArgs.push_back(std::string(ReplacementMacrosHeaderFileName));
Inputs.MakeAction = []() { return std::make_unique<ReplaceMacrosAction>(); };
return Inputs;
}
TEST(ReplaceMacrosAction, ReplacesCHECK) {
static constexpr std::string_view Source = R"cc(
#define CHECK(x) \
if (!x) __builtin_abort();
void foo() { CHECK(nullptr); }
)cc";
clang::TestAST AST(getInputs(Source));
const CallExpr* ArgumentCapture = selectFirst<CallExpr>(
"call", match(callExpr(hasDeclaration(
functionDecl(hasName(ArgCaptureAbortIfFalse))))
.bind("call"),
AST.context()));
ASSERT_THAT(ArgumentCapture, NotNull());
ASSERT_THAT(ArgumentCapture->getArg(0), NotNull());
EXPECT_TRUE(ArgumentCapture->getArg(0)->isNullPointerConstant(
AST.context(), Expr::NPC_ValueDependentIsNotNull));
}
TEST(ReplaceMacrosAction, ReplacesCHECK_NE) {
static constexpr std::string_view Source = R"cc(
#define CHECK_NE(x, y) \
if (x == y) __builtin_abort();
void foo() {
CHECK_NE(nullptr, nullptr);
}
)cc";
clang::TestAST AST(getInputs(Source));
const CallExpr* ArgumentCapture = selectFirst<CallExpr>(
"call", match(callExpr(hasDeclaration(
functionDecl(hasName(ArgCaptureAbortIfEqual))))
.bind("call"),
AST.context()));
ASSERT_THAT(ArgumentCapture, NotNull());
ASSERT_THAT(ArgumentCapture->getArg(0), NotNull());
EXPECT_TRUE(ArgumentCapture->getArg(0)->isNullPointerConstant(
AST.context(), Expr::NPC_ValueDependentIsNotNull));
ASSERT_THAT(ArgumentCapture->getArg(1), NotNull());
EXPECT_TRUE(ArgumentCapture->getArg(1)->isNullPointerConstant(
AST.context(), Expr::NPC_ValueDependentIsNotNull));
}
TEST(ReplaceMacrosAction, DoesNotReplaceMacroNotInReplacementFile) {
static constexpr std::string_view Source = R"cc(
#define TOTALLY_MADE_UP_CHECK_THAT_IS_NOT_IN_REPLACEMENT_FILE(x) \
if (!x) __builtin_abort();
void foo() {
TOTALLY_MADE_UP_CHECK_THAT_IS_NOT_IN_REPLACEMENT_FILE(nullptr);
}
)cc";
clang::TestAST AST(getInputs(Source));
const CallExpr* ArgumentCaptureFunctionCall = selectFirst<CallExpr>(
"call",
match(callExpr(hasDeclaration(
anyOf(functionDecl(hasName(ArgCaptureAbortIfFalse)),
functionDecl(hasName(ArgCaptureAbortIfEqual)))))
.bind("call"),
AST.context()));
ASSERT_THAT(ArgumentCaptureFunctionCall, IsNull());
}
TEST(ReplaceMacrosAction, ArgumentCaptureCompilesWithVariousTypes) {
static constexpr std::string_view Source = R"cc(
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmacro-redefined"
#define CHECK(x) \
if (!x) __builtin_abort();
#pragma clang diagnostic pop
struct S {
S();
S(const S &);
S(S &&);
operator bool() &;
operator bool() const &;
};
void foo() {
S s;
CHECK(static_cast<S *>(&s));
CHECK(static_cast<const S *>(&s));
CHECK(static_cast<S &>(s));
CHECK(static_cast<const S &>(s));
CHECK(static_cast<S &&>(s));
}
)cc";
clang::TestAST AST(getInputs(Source));
EXPECT_THAT(AST.diagnostics(), IsEmpty());
}
} // namespace
} // namespace clang::tidy::nullability