1 //===- ChromiumCheckModelTest.cpp -----------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models. 9 10 #include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h" 11 #include "NoopAnalysis.h" 12 #include "TestingSupport.h" 13 #include "clang/AST/ASTContext.h" 14 #include "clang/ASTMatchers/ASTMatchers.h" 15 #include "clang/Tooling/Tooling.h" 16 #include "llvm/ADT/ArrayRef.h" 17 #include "llvm/ADT/StringExtras.h" 18 #include "llvm/Support/Error.h" 19 #include "llvm/Testing/Support/Error.h" 20 #include "gmock/gmock.h" 21 #include "gtest/gtest.h" 22 #include <string> 23 24 using namespace clang; 25 using namespace dataflow; 26 using namespace test; 27 28 namespace { 29 using ::testing::_; 30 using ::testing::ElementsAre; 31 using ::testing::NotNull; 32 using ::testing::Pair; 33 34 static constexpr char ChromiumCheckHeader[] = R"( 35 namespace std { 36 class ostream; 37 } // namespace std 38 39 namespace logging { 40 class VoidifyStream { 41 public: 42 VoidifyStream() = default; 43 void operator&(std::ostream&) {} 44 }; 45 46 class CheckError { 47 public: 48 static CheckError Check(const char* file, int line, const char* condition); 49 static CheckError DCheck(const char* file, int line, const char* condition); 50 static CheckError PCheck(const char* file, int line, const char* condition); 51 static CheckError PCheck(const char* file, int line); 52 static CheckError DPCheck(const char* file, int line, const char* condition); 53 54 std::ostream& stream(); 55 56 ~CheckError(); 57 58 CheckError(const CheckError& other) = delete; 59 CheckError& operator=(const CheckError& other) = delete; 60 CheckError(CheckError&& other) = default; 61 CheckError& operator=(CheckError&& other) = default; 62 }; 63 64 } // namespace logging 65 66 #define LAZY_CHECK_STREAM(stream, condition) \ 67 !(condition) ? (void)0 : ::logging::VoidifyStream() & (stream) 68 69 #define CHECK(condition) \ 70 LAZY_CHECK_STREAM( \ 71 ::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \ 72 !(condition)) 73 74 #define PCHECK(condition) \ 75 LAZY_CHECK_STREAM( \ 76 ::logging::CheckError::PCheck(__FILE__, __LINE__, #condition).stream(), \ 77 !(condition)) 78 79 #define DCHECK(condition) \ 80 LAZY_CHECK_STREAM( \ 81 ::logging::CheckError::DCheck(__FILE__, __LINE__, #condition).stream(), \ 82 !(condition)) 83 84 #define DPCHECK(condition) \ 85 LAZY_CHECK_STREAM( \ 86 ::logging::CheckError::DPCheck(__FILE__, __LINE__, #condition).stream(), \ 87 !(condition)) 88 )"; 89 90 // A definition of the `CheckError` class that looks like the Chromium one, but 91 // is actually something else. 92 static constexpr char OtherCheckHeader[] = R"( 93 namespace other { 94 namespace logging { 95 class CheckError { 96 public: 97 static CheckError Check(const char* file, int line, const char* condition); 98 }; 99 } // namespace logging 100 } // namespace other 101 )"; 102 103 /// Replaces all occurrences of `Pattern` in `S` with `Replacement`. 104 std::string ReplacePattern(std::string S, const std::string &Pattern, 105 const std::string &Replacement) { 106 size_t Pos = 0; 107 Pos = S.find(Pattern, Pos); 108 if (Pos != std::string::npos) 109 S.replace(Pos, Pattern.size(), Replacement); 110 return S; 111 } 112 113 template <typename Model> 114 class ModelAdaptorAnalysis 115 : public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> { 116 public: 117 explicit ModelAdaptorAnalysis(ASTContext &Context) 118 : DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>( 119 Context, /*ApplyBuiltinTransfer=*/true) {} 120 121 static NoopLattice initialElement() { return NoopLattice(); } 122 123 void transfer(const Stmt *S, NoopLattice &, Environment &Env) { 124 M.transfer(S, Env); 125 } 126 127 private: 128 Model M; 129 }; 130 131 template <typename Matcher> 132 void runDataflow(llvm::StringRef Code, Matcher Match) { 133 const tooling::FileContentMappings FileContents = { 134 {"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}}; 135 136 ASSERT_THAT_ERROR( 137 test::checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>( 138 Code, "target", 139 [](ASTContext &C, Environment &) { 140 return ModelAdaptorAnalysis<ChromiumCheckModel>(C); 141 }, 142 [&Match]( 143 llvm::ArrayRef< 144 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 145 Results, 146 ASTContext &ASTCtx) { Match(Results, ASTCtx); }, 147 {"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"}, 148 FileContents), 149 llvm::Succeeded()); 150 } 151 152 TEST(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) { 153 auto Expectations = 154 [](llvm::ArrayRef< 155 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 156 Results, 157 ASTContext &ASTCtx) { 158 ASSERT_THAT(Results, ElementsAre(Pair("p", _))); 159 const Environment &Env = Results[0].second.Env; 160 161 const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); 162 ASSERT_THAT(FooDecl, NotNull()); 163 164 auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None)); 165 166 EXPECT_TRUE(Env.flowConditionImplies(*FooVal)); 167 }; 168 169 std::string Code = R"( 170 #include "check.h" 171 172 void target(bool Foo) { 173 $check(Foo); 174 bool X = true; 175 (void)X; 176 // [[p]] 177 } 178 )"; 179 runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations); 180 runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations); 181 runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations); 182 runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations); 183 } 184 185 TEST(ChromiumCheckModelTest, UnrelatedCheckIgnored) { 186 auto Expectations = 187 [](llvm::ArrayRef< 188 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 189 Results, 190 ASTContext &ASTCtx) { 191 ASSERT_THAT(Results, ElementsAre(Pair("p", _))); 192 const Environment &Env = Results[0].second.Env; 193 194 const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); 195 ASSERT_THAT(FooDecl, NotNull()); 196 197 auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None)); 198 199 EXPECT_FALSE(Env.flowConditionImplies(*FooVal)); 200 }; 201 202 std::string Code = R"( 203 #include "othercheck.h" 204 205 void target(bool Foo) { 206 if (!Foo) { 207 (void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo"); 208 } 209 bool X = true; 210 (void)X; 211 // [[p]] 212 } 213 )"; 214 runDataflow(Code, Expectations); 215 } 216 } // namespace 217