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 "CheckerRegistration.h" 10 #include "Reusables.h" 11 12 #include "clang/AST/ExprCXX.h" 13 #include "clang/Analysis/PathDiagnostic.h" 14 #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" 15 #include "clang/StaticAnalyzer/Core/Checker.h" 16 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 19 #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" 20 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" 21 #include "clang/Tooling/Tooling.h" 22 #include "gtest/gtest.h" 23 #include <type_traits> 24 25 namespace clang { 26 namespace ento { 27 namespace { 28 29 // A wrapper around CallDescriptionMap<bool> that allows verifying that 30 // all functions have been found. This is needed because CallDescriptionMap 31 // isn't supposed to support iteration. 32 class ResultMap { 33 size_t Found, Total; 34 CallDescriptionMap<bool> Impl; 35 36 public: 37 ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data) 38 : Found(0), 39 Total(std::count_if(Data.begin(), Data.end(), 40 [](const std::pair<CallDescription, bool> &Pair) { 41 return Pair.second == true; 42 })), 43 Impl(std::move(Data)) {} 44 45 const bool *lookup(const CallEvent &Call) { 46 const bool *Result = Impl.lookup(Call); 47 // If it's a function we expected to find, remember that we've found it. 48 if (Result && *Result) 49 ++Found; 50 return Result; 51 } 52 53 // Fail the test if we haven't found all the true-calls we were looking for. 54 ~ResultMap() { EXPECT_EQ(Found, Total); } 55 }; 56 57 // Scan the code body for call expressions and see if we find all calls that 58 // we were supposed to find ("true" in the provided ResultMap) and that we 59 // don't find the ones that we weren't supposed to find 60 // ("false" in the ResultMap). 61 template <typename MatchedExprT> 62 class CallDescriptionConsumer : public ExprEngineConsumer { 63 ResultMap &RM; 64 void performTest(const Decl *D) { 65 using namespace ast_matchers; 66 using T = MatchedExprT; 67 68 if (!D->hasBody()) 69 return; 70 71 const StackFrameContext *SFC = 72 Eng.getAnalysisDeclContextManager().getStackFrame(D); 73 const ProgramStateRef State = Eng.getInitialState(SFC); 74 75 // FIXME: Maybe use std::variant and std::visit for these. 76 const auto MatcherCreator = []() { 77 if (std::is_same<T, CallExpr>::value) 78 return callExpr(); 79 if (std::is_same<T, CXXConstructExpr>::value) 80 return cxxConstructExpr(); 81 if (std::is_same<T, CXXMemberCallExpr>::value) 82 return cxxMemberCallExpr(); 83 if (std::is_same<T, CXXOperatorCallExpr>::value) 84 return cxxOperatorCallExpr(); 85 llvm_unreachable("Only these expressions are supported for now."); 86 }; 87 88 const Expr *E = findNode<T>(D, MatcherCreator()); 89 90 CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager(); 91 CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> { 92 if (std::is_base_of<CallExpr, T>::value) 93 return CEMgr.getCall(E, State, SFC); 94 if (std::is_same<T, CXXConstructExpr>::value) 95 return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E), 96 /*Target=*/nullptr, State, SFC); 97 llvm_unreachable("Only these expressions are supported for now."); 98 }(); 99 100 // If the call actually matched, check if we really expected it to match. 101 const bool *LookupResult = RM.lookup(*Call); 102 EXPECT_TRUE(!LookupResult || *LookupResult); 103 104 // ResultMap is responsible for making sure that we've found *all* calls. 105 } 106 107 public: 108 CallDescriptionConsumer(CompilerInstance &C, 109 ResultMap &RM) 110 : ExprEngineConsumer(C), RM(RM) {} 111 112 bool HandleTopLevelDecl(DeclGroupRef DG) override { 113 for (const auto *D : DG) 114 performTest(D); 115 return true; 116 } 117 }; 118 119 template <typename MatchedExprT = CallExpr> 120 class CallDescriptionAction : public ASTFrontendAction { 121 ResultMap RM; 122 123 public: 124 CallDescriptionAction( 125 std::initializer_list<std::pair<CallDescription, bool>> Data) 126 : RM(Data) {} 127 128 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, 129 StringRef File) override { 130 return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler, 131 RM); 132 } 133 }; 134 135 TEST(CallDescription, SimpleNameMatching) { 136 EXPECT_TRUE(tooling::runToolOnCode( 137 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 138 {{"bar"}, false}, // false: there's no call to 'bar' in this code. 139 {{"foo"}, true}, // true: there's a call to 'foo' in this code. 140 })), 141 "void foo(); void bar() { foo(); }")); 142 } 143 144 TEST(CallDescription, RequiredArguments) { 145 EXPECT_TRUE(tooling::runToolOnCode( 146 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 147 {{"foo", 1}, true}, 148 {{"foo", 2}, false}, 149 })), 150 "void foo(int); void foo(int, int); void bar() { foo(1); }")); 151 } 152 153 TEST(CallDescription, LackOfRequiredArguments) { 154 EXPECT_TRUE(tooling::runToolOnCode( 155 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 156 {{"foo", None}, true}, 157 {{"foo", 2}, false}, 158 })), 159 "void foo(int); void foo(int, int); void bar() { foo(1); }")); 160 } 161 162 constexpr StringRef MockStdStringHeader = R"code( 163 namespace std { inline namespace __1 { 164 template<typename T> class basic_string { 165 class Allocator {}; 166 public: 167 basic_string(); 168 explicit basic_string(const char*, const Allocator & = Allocator()); 169 ~basic_string(); 170 T *c_str(); 171 }; 172 } // namespace __1 173 using string = __1::basic_string<char>; 174 } // namespace std 175 )code"; 176 177 TEST(CallDescription, QualifiedNames) { 178 constexpr StringRef AdditionalCode = R"code( 179 void foo() { 180 using namespace std; 181 basic_string<char> s; 182 s.c_str(); 183 })code"; 184 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); 185 EXPECT_TRUE(tooling::runToolOnCode( 186 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 187 {{{"std", "basic_string", "c_str"}}, true}, 188 })), 189 Code)); 190 } 191 192 TEST(CallDescription, MatchConstructor) { 193 constexpr StringRef AdditionalCode = R"code( 194 void foo() { 195 using namespace std; 196 basic_string<char> s("hello"); 197 })code"; 198 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); 199 EXPECT_TRUE(tooling::runToolOnCode( 200 std::unique_ptr<FrontendAction>( 201 new CallDescriptionAction<CXXConstructExpr>({ 202 {{{"std", "basic_string", "basic_string"}, 2, 2}, true}, 203 })), 204 Code)); 205 } 206 207 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"} 208 // This feature is actually implemented, but the test infra is not yet 209 // sophisticated enough for testing this. To do that, we will need to 210 // implement a much more advanced dispatching mechanism using the CFG for 211 // the implicit destructor events. 212 213 TEST(CallDescription, MatchConversionOperator) { 214 constexpr StringRef Code = R"code( 215 namespace aaa { 216 namespace bbb { 217 struct Bar { 218 operator int(); 219 }; 220 } // bbb 221 } // aaa 222 void foo() { 223 aaa::bbb::Bar x; 224 int tmp = x; 225 })code"; 226 EXPECT_TRUE(tooling::runToolOnCode( 227 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 228 {{{"aaa", "bbb", "Bar", "operator int"}}, true}, 229 })), 230 Code)); 231 } 232 233 TEST(CallDescription, RejectOverQualifiedNames) { 234 constexpr auto Code = R"code( 235 namespace my { 236 namespace std { 237 struct container { 238 const char *data() const; 239 }; 240 } // namespace std 241 } // namespace my 242 243 void foo() { 244 using namespace my; 245 std::container v; 246 v.data(); 247 })code"; 248 249 // FIXME: We should **not** match. 250 EXPECT_TRUE(tooling::runToolOnCode( 251 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 252 {{{"std", "container", "data"}}, true}, 253 })), 254 Code)); 255 } 256 257 TEST(CallDescription, DontSkipNonInlineNamespaces) { 258 constexpr auto Code = R"code( 259 namespace my { 260 /*not inline*/ namespace v1 { 261 void bar(); 262 } // namespace v1 263 } // namespace my 264 void foo() { 265 my::v1::bar(); 266 })code"; 267 268 { 269 SCOPED_TRACE("my v1 bar"); 270 EXPECT_TRUE(tooling::runToolOnCode( 271 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 272 {{{"my", "v1", "bar"}}, true}, 273 })), 274 Code)); 275 } 276 { 277 // FIXME: We should **not** skip non-inline namespaces. 278 SCOPED_TRACE("my bar"); 279 EXPECT_TRUE(tooling::runToolOnCode( 280 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 281 {{{"my", "bar"}}, true}, 282 })), 283 Code)); 284 } 285 } 286 287 TEST(CallDescription, SkipTopInlineNamespaces) { 288 constexpr auto Code = R"code( 289 inline namespace my { 290 namespace v1 { 291 void bar(); 292 } // namespace v1 293 } // namespace my 294 void foo() { 295 using namespace v1; 296 bar(); 297 })code"; 298 299 { 300 SCOPED_TRACE("my v1 bar"); 301 EXPECT_TRUE(tooling::runToolOnCode( 302 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 303 {{{"my", "v1", "bar"}}, true}, 304 })), 305 Code)); 306 } 307 { 308 SCOPED_TRACE("v1 bar"); 309 EXPECT_TRUE(tooling::runToolOnCode( 310 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 311 {{{"v1", "bar"}}, true}, 312 })), 313 Code)); 314 } 315 } 316 317 TEST(CallDescription, SkipAnonimousNamespaces) { 318 constexpr auto Code = R"code( 319 namespace { 320 namespace std { 321 namespace { 322 inline namespace { 323 struct container { 324 const char *data() const { return nullptr; }; 325 }; 326 } // namespace inline anonymous 327 } // namespace anonymous 328 } // namespace std 329 } // namespace anonymous 330 331 void foo() { 332 std::container v; 333 v.data(); 334 })code"; 335 336 EXPECT_TRUE(tooling::runToolOnCode( 337 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 338 {{{"std", "container", "data"}}, true}, 339 })), 340 Code)); 341 } 342 343 TEST(CallDescription, AliasNames) { 344 constexpr StringRef AliasNamesCode = R"code( 345 namespace std { 346 struct container { 347 const char *data() const; 348 }; 349 using cont = container; 350 } // std 351 )code"; 352 353 constexpr StringRef UseAliasInSpelling = R"code( 354 void foo() { 355 std::cont v; 356 v.data(); 357 })code"; 358 constexpr StringRef UseStructNameInSpelling = R"code( 359 void foo() { 360 std::container v; 361 v.data(); 362 })code"; 363 const std::string UseAliasInSpellingCode = 364 (Twine{AliasNamesCode} + UseAliasInSpelling).str(); 365 const std::string UseStructNameInSpellingCode = 366 (Twine{AliasNamesCode} + UseStructNameInSpelling).str(); 367 368 // Test if the code spells the alias, wile we match against the struct name, 369 // and again matching against the alias. 370 { 371 SCOPED_TRACE("Using alias in spelling"); 372 { 373 SCOPED_TRACE("std container data"); 374 EXPECT_TRUE(tooling::runToolOnCode( 375 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 376 {{{"std", "container", "data"}}, true}, 377 })), 378 UseAliasInSpellingCode)); 379 } 380 { 381 // FIXME: We should be able to see-through aliases. 382 SCOPED_TRACE("std cont data"); 383 EXPECT_TRUE(tooling::runToolOnCode( 384 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 385 {{{"std", "cont", "data"}}, false}, 386 })), 387 UseAliasInSpellingCode)); 388 } 389 } 390 391 // Test if the code spells the struct name, wile we match against the struct 392 // name, and again matching against the alias. 393 { 394 SCOPED_TRACE("Using struct name in spelling"); 395 { 396 SCOPED_TRACE("std container data"); 397 EXPECT_TRUE(tooling::runToolOnCode( 398 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 399 {{{"std", "container", "data"}}, true}, 400 })), 401 UseAliasInSpellingCode)); 402 } 403 { 404 // FIXME: We should be able to see-through aliases. 405 SCOPED_TRACE("std cont data"); 406 EXPECT_TRUE(tooling::runToolOnCode( 407 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 408 {{{"std", "cont", "data"}}, false}, 409 })), 410 UseAliasInSpellingCode)); 411 } 412 } 413 } 414 415 TEST(CallDescription, AliasSingleNamespace) { 416 constexpr StringRef Code = R"code( 417 namespace aaa { 418 namespace bbb { 419 namespace ccc { 420 void bar(); 421 }} // namespace bbb::ccc 422 namespace bbb_alias = bbb; 423 } // namespace aaa 424 void foo() { 425 aaa::bbb_alias::ccc::bar(); 426 })code"; 427 { 428 SCOPED_TRACE("aaa bbb ccc bar"); 429 EXPECT_TRUE(tooling::runToolOnCode( 430 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 431 {{{"aaa", "bbb", "ccc", "bar"}}, true}, 432 })), 433 Code)); 434 } 435 { 436 // FIXME: We should be able to see-through namespace aliases. 437 SCOPED_TRACE("aaa bbb_alias ccc bar"); 438 EXPECT_TRUE(tooling::runToolOnCode( 439 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 440 {{{"aaa", "bbb_alias", "ccc", "bar"}}, false}, 441 })), 442 Code)); 443 } 444 } 445 446 TEST(CallDescription, AliasMultipleNamespaces) { 447 constexpr StringRef Code = R"code( 448 namespace aaa { 449 namespace bbb { 450 namespace ccc { 451 void bar(); 452 }}} // namespace aaa::bbb::ccc 453 namespace aaa_bbb_ccc = aaa::bbb::ccc; 454 void foo() { 455 using namespace aaa_bbb_ccc; 456 bar(); 457 })code"; 458 { 459 SCOPED_TRACE("aaa bbb ccc bar"); 460 EXPECT_TRUE(tooling::runToolOnCode( 461 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 462 {{{"aaa", "bbb", "ccc", "bar"}}, true}, 463 })), 464 Code)); 465 } 466 { 467 // FIXME: We should be able to see-through namespace aliases. 468 SCOPED_TRACE("aaa_bbb_ccc bar"); 469 EXPECT_TRUE(tooling::runToolOnCode( 470 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 471 {{{"aaa_bbb_ccc", "bar"}}, false}, 472 })), 473 Code)); 474 } 475 } 476 477 TEST(CallDescription, NegativeMatchQualifiedNames) { 478 EXPECT_TRUE(tooling::runToolOnCode( 479 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ 480 {{{"foo", "bar"}}, false}, 481 {{{"bar", "foo"}}, false}, 482 {{"foo"}, true}, 483 })), 484 "void foo(); struct bar { void foo(); }; void test() { foo(); }")); 485 } 486 487 TEST(CallDescription, MatchBuiltins) { 488 // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins. 489 EXPECT_TRUE(tooling::runToolOnCode( 490 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( 491 {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "memset", 3}, true}})), 492 "void foo() {" 493 " int x;" 494 " __builtin___memset_chk(&x, 0, sizeof(x)," 495 " __builtin_object_size(&x, 0));" 496 "}")); 497 498 { 499 SCOPED_TRACE("multiple similar builtins"); 500 EXPECT_TRUE(tooling::runToolOnCode( 501 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( 502 {{{CDF_MaybeBuiltin, "memcpy", 3}, false}, 503 {{CDF_MaybeBuiltin, "wmemcpy", 3}, true}})), 504 R"(void foo(wchar_t *x, wchar_t *y) { 505 __builtin_wmemcpy(x, y, sizeof(wchar_t)); 506 })")); 507 } 508 { 509 SCOPED_TRACE("multiple similar builtins reversed order"); 510 EXPECT_TRUE(tooling::runToolOnCode( 511 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( 512 {{{CDF_MaybeBuiltin, "wmemcpy", 3}, true}, 513 {{CDF_MaybeBuiltin, "memcpy", 3}, false}})), 514 R"(void foo(wchar_t *x, wchar_t *y) { 515 __builtin_wmemcpy(x, y, sizeof(wchar_t)); 516 })")); 517 } 518 { 519 SCOPED_TRACE("lookbehind and lookahead mismatches"); 520 EXPECT_TRUE(tooling::runToolOnCode( 521 std::unique_ptr<FrontendAction>( 522 new CallDescriptionAction<>({{{CDF_MaybeBuiltin, "func"}, false}})), 523 R"( 524 void funcXXX(); 525 void XXXfunc(); 526 void XXXfuncXXX(); 527 void test() { 528 funcXXX(); 529 XXXfunc(); 530 XXXfuncXXX(); 531 })")); 532 } 533 { 534 SCOPED_TRACE("lookbehind and lookahead matches"); 535 EXPECT_TRUE(tooling::runToolOnCode( 536 std::unique_ptr<FrontendAction>( 537 new CallDescriptionAction<>({{{CDF_MaybeBuiltin, "func"}, true}})), 538 R"( 539 void func(); 540 void func_XXX(); 541 void XXX_func(); 542 void XXX_func_XXX(); 543 544 void test() { 545 func(); // exact match 546 func_XXX(); 547 XXX_func(); 548 XXX_func_XXX(); 549 })")); 550 } 551 } 552 553 //===----------------------------------------------------------------------===// 554 // Testing through a checker interface. 555 // 556 // Above, the static analyzer isn't run properly, only the bare minimum to 557 // create CallEvents. This causes CallEvents through function pointers to not 558 // refer to the pointee function, but this works fine if we run 559 // AnalysisASTConsumer. 560 //===----------------------------------------------------------------------===// 561 562 class CallDescChecker 563 : public Checker<check::PreCall, check::PreStmt<CallExpr>> { 564 CallDescriptionSet Set = {{"bar", 0}}; 565 566 public: 567 void checkPreCall(const CallEvent &Call, CheckerContext &C) const { 568 if (Set.contains(Call)) { 569 C.getBugReporter().EmitBasicReport( 570 Call.getDecl(), this, "CallEvent match", categories::LogicError, 571 "CallEvent match", 572 PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()}); 573 } 574 } 575 576 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const { 577 if (Set.containsAsWritten(*CE)) { 578 C.getBugReporter().EmitBasicReport( 579 CE->getCalleeDecl(), this, "CallExpr match", categories::LogicError, 580 "CallExpr match", 581 PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()}); 582 } 583 } 584 }; 585 586 void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer, 587 AnalyzerOptions &AnOpts) { 588 AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}}; 589 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { 590 Registry.addChecker<CallDescChecker>("test.CallDescChecker", "Description", 591 ""); 592 }); 593 } 594 595 TEST(CallDescription, CheckCallExprMatching) { 596 // Imprecise matching shouldn't catch the call to bar, because its obscured 597 // by a function pointer. 598 constexpr StringRef FnPtrCode = R"code( 599 void bar(); 600 void foo() { 601 void (*fnptr)() = bar; 602 fnptr(); 603 })code"; 604 std::string Diags; 605 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags, 606 /*OnlyEmitWarnings*/ true)); 607 EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags); 608 609 // This should be caught properly by imprecise matching, as the call is done 610 // purely through syntactic means. 611 constexpr StringRef Code = R"code( 612 void bar(); 613 void foo() { 614 bar(); 615 })code"; 616 Diags.clear(); 617 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags, 618 /*OnlyEmitWarnings*/ true)); 619 EXPECT_EQ("test.CallDescChecker: CallEvent match\n" 620 "test.CallDescChecker: CallExpr match\n", 621 Diags); 622 } 623 624 } // namespace 625 } // namespace ento 626 } // namespace clang 627