| // 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 "external/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h" // IWYU pragma: keep | |
| #include "external/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 |