1 //===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "CheckerRegistration.h"
10 #include "Reusables.h"
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>
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.
34 CallDescriptionMap
<bool> Impl
;
37 ResultMap(std::initializer_list
<std::pair
<CallDescription
, bool>> Data
)
39 Total(std::count_if(Data
.begin(), Data
.end(),
40 [](const std::pair
<CallDescription
, bool> &Pair
) {
41 return Pair
.second
== true;
43 Impl(std::move(Data
)) {}
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
)
53 // Fail the test if we haven't found all the true-calls we were looking for.
54 ~ResultMap() { EXPECT_EQ(Found
, Total
); }
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
{
64 void performTest(const Decl
*D
) {
65 using namespace ast_matchers
;
66 using T
= MatchedExprT
;
71 const StackFrameContext
*SFC
=
72 Eng
.getAnalysisDeclContextManager().getStackFrame(D
);
73 const ProgramStateRef State
= Eng
.getInitialState(SFC
);
75 // FIXME: Maybe use std::variant and std::visit for these.
76 const auto MatcherCreator
= []() {
77 if (std::is_same
<T
, CallExpr
>::value
)
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.");
88 const Expr
*E
= findNode
<T
>(D
, MatcherCreator());
90 CallEventManager
&CEMgr
= Eng
.getStateManager().getCallEventManager();
91 CallEventRef
<> Call
= [=, &CEMgr
]() -> CallEventRef
<CallEvent
> {
92 CFGBlock::ConstCFGElementRef ElemRef
= {SFC
->getCallSiteBlock(),
94 if (std::is_base_of
<CallExpr
, T
>::value
)
95 return CEMgr
.getCall(E
, State
, SFC
, ElemRef
);
96 if (std::is_same
<T
, CXXConstructExpr
>::value
)
97 return CEMgr
.getCXXConstructorCall(cast
<CXXConstructExpr
>(E
),
98 /*Target=*/nullptr, State
, SFC
,
100 llvm_unreachable("Only these expressions are supported for now.");
103 // If the call actually matched, check if we really expected it to match.
104 const bool *LookupResult
= RM
.lookup(*Call
);
105 EXPECT_TRUE(!LookupResult
|| *LookupResult
);
107 // ResultMap is responsible for making sure that we've found *all* calls.
111 CallDescriptionConsumer(CompilerInstance
&C
,
113 : ExprEngineConsumer(C
), RM(RM
) {}
115 bool HandleTopLevelDecl(DeclGroupRef DG
) override
{
116 for (const auto *D
: DG
)
122 template <typename MatchedExprT
= CallExpr
>
123 class CallDescriptionAction
: public ASTFrontendAction
{
127 CallDescriptionAction(
128 std::initializer_list
<std::pair
<CallDescription
, bool>> Data
)
131 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&Compiler
,
132 StringRef File
) override
{
133 return std::make_unique
<CallDescriptionConsumer
<MatchedExprT
>>(Compiler
,
138 TEST(CallDescription
, SimpleNameMatching
) {
139 EXPECT_TRUE(tooling::runToolOnCode(
140 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
141 {{CDM::SimpleFunc
, {"bar"}},
142 false}, // false: there's no call to 'bar' in this code.
143 {{CDM::SimpleFunc
, {"foo"}},
144 true}, // true: there's a call to 'foo' in this code.
146 "void foo(); void bar() { foo(); }"));
149 TEST(CallDescription
, RequiredArguments
) {
150 EXPECT_TRUE(tooling::runToolOnCode(
151 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
152 {{CDM::SimpleFunc
, {"foo"}, 1}, true},
153 {{CDM::SimpleFunc
, {"foo"}, 2}, false},
155 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
158 TEST(CallDescription
, LackOfRequiredArguments
) {
159 EXPECT_TRUE(tooling::runToolOnCode(
160 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
161 {{CDM::SimpleFunc
, {"foo"}, std::nullopt
}, true},
162 {{CDM::SimpleFunc
, {"foo"}, 2}, false},
164 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
167 constexpr StringRef MockStdStringHeader
= R
"code(
168 namespace std { inline namespace __1 {
169 template<typename T> class basic_string {
173 explicit basic_string(const char*, const Allocator & = Allocator());
178 using string = __1::basic_string<char>;
182 TEST(CallDescription
, QualifiedNames
) {
183 constexpr StringRef AdditionalCode
= R
"code(
186 basic_string<char> s;
189 const std::string Code
= (Twine
{MockStdStringHeader
} + AdditionalCode
).str();
190 EXPECT_TRUE(tooling::runToolOnCode(
191 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
192 {{CDM::CXXMethod
, {"std", "basic_string", "c_str"}}, true},
197 TEST(CallDescription
, MatchConstructor
) {
198 constexpr StringRef AdditionalCode
= R
"code(
201 basic_string<char> s("hello
");
203 const std::string Code
= (Twine
{MockStdStringHeader
} + AdditionalCode
).str();
204 EXPECT_TRUE(tooling::runToolOnCode(
205 std::unique_ptr
<FrontendAction
>(
206 new CallDescriptionAction
<CXXConstructExpr
>({
207 {{CDM::CXXMethod
, {"std", "basic_string", "basic_string"}, 2, 2},
213 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}
214 // This feature is actually implemented, but the test infra is not yet
215 // sophisticated enough for testing this. To do that, we will need to
216 // implement a much more advanced dispatching mechanism using the CFG for
217 // the implicit destructor events.
219 TEST(CallDescription
, MatchConversionOperator
) {
220 constexpr StringRef Code
= R
"code(
232 EXPECT_TRUE(tooling::runToolOnCode(
233 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
234 {{CDM::CXXMethod
, {"aaa", "bbb", "Bar", "operator int"}}, true},
239 TEST(CallDescription
, RejectOverQualifiedNames
) {
240 constexpr auto Code
= R
"code(
244 const char *data() const;
255 // FIXME: We should **not** match.
256 EXPECT_TRUE(tooling::runToolOnCode(
257 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
258 {{CDM::CXXMethod
, {"std", "container", "data"}}, true},
263 TEST(CallDescription
, DontSkipNonInlineNamespaces
) {
264 constexpr auto Code
= R
"code(
266 /*not inline*/ namespace v1 {
275 SCOPED_TRACE("my v1 bar");
276 EXPECT_TRUE(tooling::runToolOnCode(
277 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
278 {{CDM::SimpleFunc
, {"my", "v1", "bar"}}, true},
283 // FIXME: We should **not** skip non-inline namespaces.
284 SCOPED_TRACE("my bar");
285 EXPECT_TRUE(tooling::runToolOnCode(
286 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
287 {{CDM::SimpleFunc
, {"my", "bar"}}, true},
293 TEST(CallDescription
, SkipTopInlineNamespaces
) {
294 constexpr auto Code
= R
"code(
295 inline namespace my {
306 SCOPED_TRACE("my v1 bar");
307 EXPECT_TRUE(tooling::runToolOnCode(
308 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
309 {{CDM::SimpleFunc
, {"my", "v1", "bar"}}, true},
314 SCOPED_TRACE("v1 bar");
315 EXPECT_TRUE(tooling::runToolOnCode(
316 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
317 {{CDM::SimpleFunc
, {"v1", "bar"}}, true},
323 TEST(CallDescription
, SkipAnonimousNamespaces
) {
324 constexpr auto Code
= R
"code(
330 const char *data() const { return nullptr; };
332 } // namespace inline anonymous
333 } // namespace anonymous
335 } // namespace anonymous
342 EXPECT_TRUE(tooling::runToolOnCode(
343 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
344 {{CDM::CXXMethod
, {"std", "container", "data"}}, true},
349 TEST(CallDescription
, AliasNames
) {
350 constexpr StringRef AliasNamesCode
= R
"code(
353 const char *data() const;
355 using cont = container;
359 constexpr StringRef UseAliasInSpelling
= R
"code(
364 constexpr StringRef UseStructNameInSpelling
= R
"code(
369 const std::string UseAliasInSpellingCode
=
370 (Twine
{AliasNamesCode
} + UseAliasInSpelling
).str();
371 const std::string UseStructNameInSpellingCode
=
372 (Twine
{AliasNamesCode
} + UseStructNameInSpelling
).str();
374 // Test if the code spells the alias, wile we match against the struct name,
375 // and again matching against the alias.
377 SCOPED_TRACE("Using alias in spelling");
379 SCOPED_TRACE("std container data");
380 EXPECT_TRUE(tooling::runToolOnCode(
381 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
382 {{CDM::CXXMethod
, {"std", "container", "data"}}, true},
384 UseAliasInSpellingCode
));
387 // FIXME: We should be able to see-through aliases.
388 SCOPED_TRACE("std cont data");
389 EXPECT_TRUE(tooling::runToolOnCode(
390 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
391 {{CDM::CXXMethod
, {"std", "cont", "data"}}, false},
393 UseAliasInSpellingCode
));
397 // Test if the code spells the struct name, wile we match against the struct
398 // name, and again matching against the alias.
400 SCOPED_TRACE("Using struct name in spelling");
402 SCOPED_TRACE("std container data");
403 EXPECT_TRUE(tooling::runToolOnCode(
404 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
405 {{CDM::CXXMethod
, {"std", "container", "data"}}, true},
407 UseAliasInSpellingCode
));
410 // FIXME: We should be able to see-through aliases.
411 SCOPED_TRACE("std cont data");
412 EXPECT_TRUE(tooling::runToolOnCode(
413 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
414 {{CDM::CXXMethod
, {"std", "cont", "data"}}, false},
416 UseAliasInSpellingCode
));
421 TEST(CallDescription
, AliasSingleNamespace
) {
422 constexpr StringRef Code
= R
"code(
427 }} // namespace bbb::ccc
428 namespace bbb_alias = bbb;
431 aaa::bbb_alias::ccc::bar();
434 SCOPED_TRACE("aaa bbb ccc bar");
435 EXPECT_TRUE(tooling::runToolOnCode(
436 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
437 {{CDM::SimpleFunc
, {"aaa", "bbb", "ccc", "bar"}}, true},
442 // FIXME: We should be able to see-through namespace aliases.
443 SCOPED_TRACE("aaa bbb_alias ccc bar");
444 EXPECT_TRUE(tooling::runToolOnCode(
445 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
446 {{CDM::SimpleFunc
, {"aaa", "bbb_alias", "ccc", "bar"}}, false},
452 TEST(CallDescription
, AliasMultipleNamespaces
) {
453 constexpr StringRef Code
= R
"code(
458 }}} // namespace aaa::bbb::ccc
459 namespace aaa_bbb_ccc = aaa::bbb::ccc;
461 using namespace aaa_bbb_ccc;
465 SCOPED_TRACE("aaa bbb ccc bar");
466 EXPECT_TRUE(tooling::runToolOnCode(
467 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
468 {{CDM::SimpleFunc
, {"aaa", "bbb", "ccc", "bar"}}, true},
473 // FIXME: We should be able to see-through namespace aliases.
474 SCOPED_TRACE("aaa_bbb_ccc bar");
475 EXPECT_TRUE(tooling::runToolOnCode(
476 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
477 {{CDM::SimpleFunc
, {"aaa_bbb_ccc", "bar"}}, false},
483 TEST(CallDescription
, NegativeMatchQualifiedNames
) {
484 EXPECT_TRUE(tooling::runToolOnCode(
485 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
486 {{CDM::Unspecified
, {"foo", "bar"}}, false},
487 {{CDM::Unspecified
, {"bar", "foo"}}, false},
488 {{CDM::Unspecified
, {"foo"}}, true},
490 "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
493 TEST(CallDescription
, MatchBuiltins
) {
494 // Test the matching modes CDM::CLibrary and CDM::CLibraryMaybeHardened,
495 // which can recognize builtin variants of C library functions.
497 SCOPED_TRACE("hardened variants of functions");
498 EXPECT_TRUE(tooling::runToolOnCode(
499 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
500 {{{CDM::Unspecified
, {"memset"}, 3}, false},
501 {{CDM::CLibrary
, {"memset"}, 3}, false},
502 {{CDM::CLibraryMaybeHardened
, {"memset"}, 3}, true}})),
505 " __builtin___memset_chk(&x, 0, sizeof(x),"
506 " __builtin_object_size(&x, 0));"
510 SCOPED_TRACE("multiple similar builtins");
511 EXPECT_TRUE(tooling::runToolOnCode(
512 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
513 {{{CDM::CLibrary
, {"memcpy"}, 3}, false},
514 {{CDM::CLibrary
, {"wmemcpy"}, 3}, true}})),
515 R
"(void foo(wchar_t *x, wchar_t *y) {
516 __builtin_wmemcpy(x, y, sizeof(wchar_t));
520 SCOPED_TRACE("multiple similar builtins reversed order");
521 EXPECT_TRUE(tooling::runToolOnCode(
522 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
523 {{{CDM::CLibrary
, {"wmemcpy"}, 3}, true},
524 {{CDM::CLibrary
, {"memcpy"}, 3}, false}})),
525 R
"(void foo(wchar_t *x, wchar_t *y) {
526 __builtin_wmemcpy(x, y, sizeof(wchar_t));
530 SCOPED_TRACE("multiple similar builtins with hardened variant");
531 EXPECT_TRUE(tooling::runToolOnCode(
532 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
533 {{{CDM::CLibraryMaybeHardened
, {"memcpy"}, 3}, false},
534 {{CDM::CLibraryMaybeHardened
, {"wmemcpy"}, 3}, true}})),
535 R
"(typedef __typeof(sizeof(int)) size_t;
536 extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,
537 const wchar_t *__restrict __s2,
538 size_t __n, size_t __ns1);
539 void foo(wchar_t *x, wchar_t *y) {
540 __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);
545 "multiple similar builtins with hardened variant reversed order");
546 EXPECT_TRUE(tooling::runToolOnCode(
547 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
548 {{{CDM::CLibraryMaybeHardened
, {"wmemcpy"}, 3}, true},
549 {{CDM::CLibraryMaybeHardened
, {"memcpy"}, 3}, false}})),
550 R
"(typedef __typeof(sizeof(int)) size_t;
551 extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,
552 const wchar_t *__restrict __s2,
553 size_t __n, size_t __ns1);
554 void foo(wchar_t *x, wchar_t *y) {
555 __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);
559 SCOPED_TRACE("lookbehind and lookahead mismatches");
560 EXPECT_TRUE(tooling::runToolOnCode(
561 std::unique_ptr
<FrontendAction
>(
562 new CallDescriptionAction
<>({{{CDM::CLibrary
, {"func"}}, false}})),
574 SCOPED_TRACE("lookbehind and lookahead matches");
575 EXPECT_TRUE(tooling::runToolOnCode(
576 std::unique_ptr
<FrontendAction
>(
577 new CallDescriptionAction
<>({{{CDM::CLibrary
, {"func"}}, true}})),
585 func(); // exact match
593 //===----------------------------------------------------------------------===//
594 // Testing through a checker interface.
596 // Above, the static analyzer isn't run properly, only the bare minimum to
597 // create CallEvents. This causes CallEvents through function pointers to not
598 // refer to the pointee function, but this works fine if we run
599 // AnalysisASTConsumer.
600 //===----------------------------------------------------------------------===//
602 class CallDescChecker
603 : public Checker
<check::PreCall
, check::PreStmt
<CallExpr
>> {
604 CallDescriptionSet Set
= {{CDM::SimpleFunc
, {"bar"}, 0}};
607 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const {
608 if (Set
.contains(Call
)) {
609 C
.getBugReporter().EmitBasicReport(
610 Call
.getDecl(), this, "CallEvent match", categories::LogicError
,
612 PathDiagnosticLocation
{Call
.getDecl(), C
.getSourceManager()});
616 void checkPreStmt(const CallExpr
*CE
, CheckerContext
&C
) const {
617 if (Set
.containsAsWritten(*CE
)) {
618 C
.getBugReporter().EmitBasicReport(
619 CE
->getCalleeDecl(), this, "CallExpr match", categories::LogicError
,
621 PathDiagnosticLocation
{CE
->getCalleeDecl(), C
.getSourceManager()});
626 void addCallDescChecker(AnalysisASTConsumer
&AnalysisConsumer
,
627 AnalyzerOptions
&AnOpts
) {
628 AnOpts
.CheckersAndPackages
= {{"test.CallDescChecker", true}};
629 AnalysisConsumer
.AddCheckerRegistrationFn([](CheckerRegistry
&Registry
) {
630 Registry
.addChecker
<CallDescChecker
>("test.CallDescChecker", "Description",
635 TEST(CallDescription
, CheckCallExprMatching
) {
636 // Imprecise matching shouldn't catch the call to bar, because its obscured
637 // by a function pointer.
638 constexpr StringRef FnPtrCode
= R
"code(
641 void (*fnptr)() = bar;
645 EXPECT_TRUE(runCheckerOnCode
<addCallDescChecker
>(FnPtrCode
.str(), Diags
,
646 /*OnlyEmitWarnings*/ true));
647 EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags
);
649 // This should be caught properly by imprecise matching, as the call is done
650 // purely through syntactic means.
651 constexpr StringRef Code
= R
"code(
657 EXPECT_TRUE(runCheckerOnCode
<addCallDescChecker
>(Code
.str(), Diags
,
658 /*OnlyEmitWarnings*/ true));
659 EXPECT_EQ("test.CallDescChecker: CallEvent match\n"
660 "test.CallDescChecker: CallExpr match\n",