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 class ChromiumCheckModelTest : public ::testing::TestWithParam<std::string> { 132 protected: 133 template <typename Matcher> 134 void runDataflow(llvm::StringRef Code, Matcher Match) { 135 const tooling::FileContentMappings FileContents = { 136 {"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}}; 137 138 ASSERT_THAT_ERROR( 139 test::checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>( 140 Code, "target", 141 [](ASTContext &C, Environment &) { 142 return ModelAdaptorAnalysis<ChromiumCheckModel>(C); 143 }, 144 [&Match]( 145 llvm::ArrayRef< 146 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 147 Results, 148 ASTContext &ASTCtx) { Match(Results, ASTCtx); }, 149 {"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"}, 150 FileContents), 151 llvm::Succeeded()); 152 } 153 }; 154 155 TEST_F(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) { 156 auto Expectations = 157 [](llvm::ArrayRef< 158 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 159 Results, 160 ASTContext &ASTCtx) { 161 ASSERT_THAT(Results, ElementsAre(Pair("p", _))); 162 const Environment &Env = Results[0].second.Env; 163 164 const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); 165 ASSERT_THAT(FooDecl, NotNull()); 166 167 auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None)); 168 169 EXPECT_TRUE(Env.flowConditionImplies(*FooVal)); 170 }; 171 172 std::string Code = R"( 173 #include "check.h" 174 175 void target(bool Foo) { 176 $check(Foo); 177 bool X = true; 178 (void)X; 179 // [[p]] 180 } 181 )"; 182 runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations); 183 runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations); 184 runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations); 185 runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations); 186 } 187 188 TEST_F(ChromiumCheckModelTest, UnrelatedCheckIgnored) { 189 auto Expectations = 190 [](llvm::ArrayRef< 191 std::pair<std::string, DataflowAnalysisState<NoopLattice>>> 192 Results, 193 ASTContext &ASTCtx) { 194 ASSERT_THAT(Results, ElementsAre(Pair("p", _))); 195 const Environment &Env = Results[0].second.Env; 196 197 const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); 198 ASSERT_THAT(FooDecl, NotNull()); 199 200 auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None)); 201 202 EXPECT_FALSE(Env.flowConditionImplies(*FooVal)); 203 }; 204 205 std::string Code = R"( 206 #include "othercheck.h" 207 208 void target(bool Foo) { 209 if (!Foo) { 210 (void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo"); 211 } 212 bool X = true; 213 (void)X; 214 // [[p]] 215 } 216 )"; 217 runDataflow(Code, Expectations); 218 } 219 } // namespace 220