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