1 //===- unittests/StaticAnalyzer/CallDescriptionTest.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 9 #include "Reusables.h" 10 11 #include "clang/AST/ExprCXX.h" 12 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 13 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 14 #include "clang/Tooling/Tooling.h" 15 #include "gtest/gtest.h" 16 #include <type_traits> 17 18 namespace clang { 19 namespace ento { 20 namespace { 21 22 // A wrapper around CallDescriptionMap<bool> that allows verifying that 23 // all functions have been found. This is needed because CallDescriptionMap 24 // isn't supposed to support iteration. 25 class ResultMap { 26 size_t Found, Total; 27 CallDescriptionMap<bool> Impl; 28 29 public: 30 ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data) 31 : Found(0), 32 Total(std::count_if(Data.begin(), Data.end(), 33 [](const std::pair<CallDescription, bool> &Pair) { 34 return Pair.second == true; 35 })), 36 Impl(std::move(Data)) {} 37 38 const bool *lookup(const CallEvent &Call) { 39 const bool *Result = Impl.lookup(Call); 40 // If it's a function we expected to find, remember that we've found it. 41 if (Result && *Result) 42 ++Found; 43 return Result; 44 } 45 46 // Fail the test if we haven't found all the true-calls we were looking for. 47 ~ResultMap() { EXPECT_EQ(Found, Total); } 48 }; 49 50 // Scan the code body for call expressions and see if we find all calls that 51 // we were supposed to find ("true" in the provided ResultMap) and that we 52 // don't find the ones that we weren't supposed to find 53 // ("false" in the ResultMap). 54 template <typename MatchedExprT> 55 class CallDescriptionConsumer : public ExprEngineConsumer { 56 ResultMap &RM; 57 void performTest(const Decl *D) { 58 using namespace ast_matchers; 59 using T = MatchedExprT; 60 61 if (!D->hasBody()) 62 return; 63 64 const StackFrameContext *SFC = 65 Eng.getAnalysisDeclContextManager().getStackFrame(D); 66 const ProgramStateRef State = Eng.getInitialState(SFC); 67 68 // FIXME: Maybe use std::variant and std::visit for these. 69 const auto MatcherCreator = []() { 70 if (std::is_same<T, CallExpr>::value) 71 return callExpr(); 72 if (std::is_same<T, CXXConstructExpr>::value) 73 return cxxConstructExpr(); 74 if (std::is_same<T, CXXMemberCallExpr>::value) 75 return cxxMemberCallExpr(); 76 if (std::is_same<T, CXXOperatorCallExpr>::value) 77 return cxxOperatorCallExpr(); 78 llvm_unreachable("Only these expressions are supported for now."); 79 }; 80 81 const Expr *E = findNode<T>(D, MatcherCreator()); 82 83 CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager(); 84 CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> { 85 if (std::is_base_of<CallExpr, T>::value) 86 return CEMgr.getCall(E, State, SFC); 87 if (std::is_same<T, CXXConstructExpr>::value) 88 return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E), 89 /*Target=*/nullptr, State, SFC); 90 llvm_unreachable("Only these expressions are supported for now."); 91 }(); 92 93 // If the call actually matched, check if we really expected it to match. 94 const bool *LookupResult = RM.lookup(*Call); 95 EXPECT_TRUE(!LookupResult || *LookupResult); 96 97 // ResultMap is responsible for making sure that we've found *all* calls. 98 } 99 100 public: 101 CallDescriptionConsumer(CompilerInstance &C, 102 ResultMap &RM) 103 : ExprEngineConsumer(C), RM(RM) {} 104 105 bool HandleTopLevelDecl(DeclGroupRef DG) override { 106 for (const auto *D : DG) 107 performTest(D); 108 return true; 109 } 110 }; 111 112 template <typename MatchedExprT = CallExpr> 113 class CallDescriptionAction : public ASTFrontendAction { 114 ResultMap RM; 115 116 public: 117 CallDescriptionAction( 118 std::initializer_list<std::pair<CallDescription, bool>> Data) 119 : RM(Data) {} 120 121 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, 122 StringRef File) override { 123 return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler, 124 RM); 125 } 126 }; 127 128 TEST(CallDescription, SimpleNameMatching) { 129 EXPECT_TRUE(tooling::runToolOnCode( 130 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 131 {{"bar"}, false}, // false: there's no call to 'bar' in this code. 132 {{"foo"}, true}, // true: there's a call to 'foo' in this code. 133 })), 134 "void foo(); void bar() { foo(); }")); 135 } 136 137 TEST(CallDescription, RequiredArguments) { 138 EXPECT_TRUE(tooling::runToolOnCode( 139 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 140 {{"foo", 1}, true}, 141 {{"foo", 2}, false}, 142 })), 143 "void foo(int); void foo(int, int); void bar() { foo(1); }")); 144 } 145 146 TEST(CallDescription, LackOfRequiredArguments) { 147 EXPECT_TRUE(tooling::runToolOnCode( 148 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 149 {{"foo", None}, true}, 150 {{"foo", 2}, false}, 151 })), 152 "void foo(int); void foo(int, int); void bar() { foo(1); }")); 153 } 154 155 constexpr StringRef MockStdStringHeader = R"code( 156 namespace std { inline namespace __1 { 157 template<typename T> class basic_string { 158 class Allocator {}; 159 public: 160 basic_string(); 161 explicit basic_string(const char*, const Allocator & = Allocator()); 162 ~basic_string(); 163 T *c_str(); 164 }; 165 } // namespace __1 166 using string = __1::basic_string<char>; 167 } // namespace std 168 )code"; 169 170 TEST(CallDescription, QualifiedNames) { 171 constexpr StringRef AdditionalCode = R"code( 172 void foo() { 173 using namespace std; 174 basic_string<char> s; 175 s.c_str(); 176 })code"; 177 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); 178 EXPECT_TRUE(tooling::runToolOnCode( 179 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 180 {{{"std", "basic_string", "c_str"}}, true}, 181 })), 182 Code)); 183 } 184 185 TEST(CallDescription, MatchConstructor) { 186 constexpr StringRef AdditionalCode = R"code( 187 void foo() { 188 using namespace std; 189 basic_string<char> s("hello"); 190 })code"; 191 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); 192 EXPECT_TRUE(tooling::runToolOnCode( 193 std::unique_ptr<FrontendAction>( 194 new CallDescriptionAction<CXXConstructExpr>({ 195 {{{"std", "basic_string", "basic_string"}, 2, 2}, true}, 196 })), 197 Code)); 198 } 199 200 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"} 201 // This feature is actually implemented, but the test infra is not yet 202 // sophisticated enough for testing this. To do that, we will need to 203 // implement a much more advanced dispatching mechanism using the CFG for 204 // the implicit destructor events. 205 206 TEST(CallDescription, MatchConversionOperator) { 207 constexpr StringRef Code = R"code( 208 namespace aaa { 209 namespace bbb { 210 struct Bar { 211 operator int(); 212 }; 213 } // bbb 214 } // aaa 215 void foo() { 216 aaa::bbb::Bar x; 217 int tmp = x; 218 })code"; 219 EXPECT_TRUE(tooling::runToolOnCode( 220 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 221 {{{"aaa", "bbb", "Bar", "operator int"}}, true}, 222 })), 223 Code)); 224 } 225 226 TEST(CallDescription, RejectOverQualifiedNames) { 227 constexpr auto Code = R"code( 228 namespace my { 229 namespace std { 230 struct container { 231 const char *data() const; 232 }; 233 } // namespace std 234 } // namespace my 235 236 void foo() { 237 using namespace my; 238 std::container v; 239 v.data(); 240 })code"; 241 242 // FIXME: We should **not** match. 243 EXPECT_TRUE(tooling::runToolOnCode( 244 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 245 {{{"std", "container", "data"}}, true}, 246 })), 247 Code)); 248 } 249 250 TEST(CallDescription, DontSkipNonInlineNamespaces) { 251 constexpr auto Code = R"code( 252 namespace my { 253 /*not inline*/ namespace v1 { 254 void bar(); 255 } // namespace v1 256 } // namespace my 257 void foo() { 258 my::v1::bar(); 259 })code"; 260 261 { 262 SCOPED_TRACE("my v1 bar"); 263 EXPECT_TRUE(tooling::runToolOnCode( 264 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 265 {{{"my", "v1", "bar"}}, true}, 266 })), 267 Code)); 268 } 269 { 270 // FIXME: We should **not** skip non-inline namespaces. 271 SCOPED_TRACE("my bar"); 272 EXPECT_TRUE(tooling::runToolOnCode( 273 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 274 {{{"my", "bar"}}, true}, 275 })), 276 Code)); 277 } 278 } 279 280 TEST(CallDescription, SkipTopInlineNamespaces) { 281 constexpr auto Code = R"code( 282 inline namespace my { 283 namespace v1 { 284 void bar(); 285 } // namespace v1 286 } // namespace my 287 void foo() { 288 using namespace v1; 289 bar(); 290 })code"; 291 292 { 293 SCOPED_TRACE("my v1 bar"); 294 EXPECT_TRUE(tooling::runToolOnCode( 295 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 296 {{{"my", "v1", "bar"}}, true}, 297 })), 298 Code)); 299 } 300 { 301 SCOPED_TRACE("v1 bar"); 302 EXPECT_TRUE(tooling::runToolOnCode( 303 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 304 {{{"v1", "bar"}}, true}, 305 })), 306 Code)); 307 } 308 } 309 310 TEST(CallDescription, SkipAnonimousNamespaces) { 311 constexpr auto Code = R"code( 312 namespace { 313 namespace std { 314 namespace { 315 inline namespace { 316 struct container { 317 const char *data() const { return nullptr; }; 318 }; 319 } // namespace inline anonymous 320 } // namespace anonymous 321 } // namespace std 322 } // namespace anonymous 323 324 void foo() { 325 std::container v; 326 v.data(); 327 })code"; 328 329 EXPECT_TRUE(tooling::runToolOnCode( 330 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 331 {{{"std", "container", "data"}}, true}, 332 })), 333 Code)); 334 } 335 336 TEST(CallDescription, AliasNames) { 337 constexpr StringRef AliasNamesCode = R"code( 338 namespace std { 339 struct container { 340 const char *data() const; 341 }; 342 using cont = container; 343 } // std 344 )code"; 345 346 constexpr StringRef UseAliasInSpelling = R"code( 347 void foo() { 348 std::cont v; 349 v.data(); 350 })code"; 351 constexpr StringRef UseStructNameInSpelling = R"code( 352 void foo() { 353 std::container v; 354 v.data(); 355 })code"; 356 const std::string UseAliasInSpellingCode = 357 (Twine{AliasNamesCode} + UseAliasInSpelling).str(); 358 const std::string UseStructNameInSpellingCode = 359 (Twine{AliasNamesCode} + UseStructNameInSpelling).str(); 360 361 // Test if the code spells the alias, wile we match against the struct name, 362 // and again matching against the alias. 363 { 364 SCOPED_TRACE("Using alias in spelling"); 365 { 366 SCOPED_TRACE("std container data"); 367 EXPECT_TRUE(tooling::runToolOnCode( 368 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 369 {{{"std", "container", "data"}}, true}, 370 })), 371 UseAliasInSpellingCode)); 372 } 373 { 374 // FIXME: We should be able to see-through aliases. 375 SCOPED_TRACE("std cont data"); 376 EXPECT_TRUE(tooling::runToolOnCode( 377 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 378 {{{"std", "cont", "data"}}, false}, 379 })), 380 UseAliasInSpellingCode)); 381 } 382 } 383 384 // Test if the code spells the struct name, wile we match against the struct 385 // name, and again matching against the alias. 386 { 387 SCOPED_TRACE("Using struct name in spelling"); 388 { 389 SCOPED_TRACE("std container data"); 390 EXPECT_TRUE(tooling::runToolOnCode( 391 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 392 {{{"std", "container", "data"}}, true}, 393 })), 394 UseAliasInSpellingCode)); 395 } 396 { 397 // FIXME: We should be able to see-through aliases. 398 SCOPED_TRACE("std cont data"); 399 EXPECT_TRUE(tooling::runToolOnCode( 400 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 401 {{{"std", "cont", "data"}}, false}, 402 })), 403 UseAliasInSpellingCode)); 404 } 405 } 406 } 407 408 TEST(CallDescription, AliasSingleNamespace) { 409 constexpr StringRef Code = R"code( 410 namespace aaa { 411 namespace bbb { 412 namespace ccc { 413 void bar(); 414 }} // namespace bbb::ccc 415 namespace bbb_alias = bbb; 416 } // namespace aaa 417 void foo() { 418 aaa::bbb_alias::ccc::bar(); 419 })code"; 420 { 421 SCOPED_TRACE("aaa bbb ccc bar"); 422 EXPECT_TRUE(tooling::runToolOnCode( 423 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 424 {{{"aaa", "bbb", "ccc", "bar"}}, true}, 425 })), 426 Code)); 427 } 428 { 429 // FIXME: We should be able to see-through namespace aliases. 430 SCOPED_TRACE("aaa bbb_alias ccc bar"); 431 EXPECT_TRUE(tooling::runToolOnCode( 432 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 433 {{{"aaa", "bbb_alias", "ccc", "bar"}}, false}, 434 })), 435 Code)); 436 } 437 } 438 439 TEST(CallDescription, AliasMultipleNamespaces) { 440 constexpr StringRef Code = R"code( 441 namespace aaa { 442 namespace bbb { 443 namespace ccc { 444 void bar(); 445 }}} // namespace aaa::bbb::ccc 446 namespace aaa_bbb_ccc = aaa::bbb::ccc; 447 void foo() { 448 using namespace aaa_bbb_ccc; 449 bar(); 450 })code"; 451 { 452 SCOPED_TRACE("aaa bbb ccc bar"); 453 EXPECT_TRUE(tooling::runToolOnCode( 454 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 455 {{{"aaa", "bbb", "ccc", "bar"}}, true}, 456 })), 457 Code)); 458 } 459 { 460 // FIXME: We should be able to see-through namespace aliases. 461 SCOPED_TRACE("aaa_bbb_ccc bar"); 462 EXPECT_TRUE(tooling::runToolOnCode( 463 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 464 {{{"aaa_bbb_ccc", "bar"}}, false}, 465 })), 466 Code)); 467 } 468 } 469 470 TEST(CallDescription, NegativeMatchQualifiedNames) { 471 EXPECT_TRUE(tooling::runToolOnCode( 472 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 473 {{{"foo", "bar"}}, false}, 474 {{{"bar", "foo"}}, false}, 475 {{"foo"}, true}, 476 })), 477 "void foo(); struct bar { void foo(); }; void test() { foo(); }")); 478 } 479 480 TEST(CallDescription, MatchBuiltins) { 481 // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins. 482 EXPECT_TRUE(tooling::runToolOnCode( 483 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( 484 {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "memset", 3}, true}})), 485 "void foo() {" 486 " int x;" 487 " __builtin___memset_chk(&x, 0, sizeof(x)," 488 " __builtin_object_size(&x, 0));" 489 "}")); 490 } 491 492 } // namespace 493 } // namespace ento 494 } // namespace clang 495