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 {{{"bar"}}, false}, // false: there's no call to 'bar' in this code.
142 {{{"foo"}}, true}, // true: there's a call to 'foo' in this code.
144 "void foo(); void bar() { foo(); }"));
147 TEST(CallDescription
, RequiredArguments
) {
148 EXPECT_TRUE(tooling::runToolOnCode(
149 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
150 {{{"foo"}, 1}, true},
151 {{{"foo"}, 2}, false},
153 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
156 TEST(CallDescription
, LackOfRequiredArguments
) {
157 EXPECT_TRUE(tooling::runToolOnCode(
158 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
159 {{{"foo"}, std::nullopt
}, true},
160 {{{"foo"}, 2}, false},
162 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
165 constexpr StringRef MockStdStringHeader
= R
"code(
166 namespace std { inline namespace __1 {
167 template<typename T> class basic_string {
171 explicit basic_string(const char*, const Allocator & = Allocator());
176 using string = __1::basic_string<char>;
180 TEST(CallDescription
, QualifiedNames
) {
181 constexpr StringRef AdditionalCode
= R
"code(
184 basic_string<char> s;
187 const std::string Code
= (Twine
{MockStdStringHeader
} + AdditionalCode
).str();
188 EXPECT_TRUE(tooling::runToolOnCode(
189 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
190 {{{"std", "basic_string", "c_str"}}, true},
195 TEST(CallDescription
, MatchConstructor
) {
196 constexpr StringRef AdditionalCode
= R
"code(
199 basic_string<char> s("hello
");
201 const std::string Code
= (Twine
{MockStdStringHeader
} + AdditionalCode
).str();
202 EXPECT_TRUE(tooling::runToolOnCode(
203 std::unique_ptr
<FrontendAction
>(
204 new CallDescriptionAction
<CXXConstructExpr
>({
205 {{{"std", "basic_string", "basic_string"}, 2, 2}, true},
210 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}
211 // This feature is actually implemented, but the test infra is not yet
212 // sophisticated enough for testing this. To do that, we will need to
213 // implement a much more advanced dispatching mechanism using the CFG for
214 // the implicit destructor events.
216 TEST(CallDescription
, MatchConversionOperator
) {
217 constexpr StringRef Code
= R
"code(
229 EXPECT_TRUE(tooling::runToolOnCode(
230 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
231 {{{"aaa", "bbb", "Bar", "operator int"}}, true},
236 TEST(CallDescription
, RejectOverQualifiedNames
) {
237 constexpr auto Code
= R
"code(
241 const char *data() const;
252 // FIXME: We should **not** match.
253 EXPECT_TRUE(tooling::runToolOnCode(
254 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
255 {{{"std", "container", "data"}}, true},
260 TEST(CallDescription
, DontSkipNonInlineNamespaces
) {
261 constexpr auto Code
= R
"code(
263 /*not inline*/ namespace v1 {
272 SCOPED_TRACE("my v1 bar");
273 EXPECT_TRUE(tooling::runToolOnCode(
274 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
275 {{{"my", "v1", "bar"}}, true},
280 // FIXME: We should **not** skip non-inline namespaces.
281 SCOPED_TRACE("my bar");
282 EXPECT_TRUE(tooling::runToolOnCode(
283 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
284 {{{"my", "bar"}}, true},
290 TEST(CallDescription
, SkipTopInlineNamespaces
) {
291 constexpr auto Code
= R
"code(
292 inline namespace my {
303 SCOPED_TRACE("my v1 bar");
304 EXPECT_TRUE(tooling::runToolOnCode(
305 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
306 {{{"my", "v1", "bar"}}, true},
311 SCOPED_TRACE("v1 bar");
312 EXPECT_TRUE(tooling::runToolOnCode(
313 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
314 {{{"v1", "bar"}}, true},
320 TEST(CallDescription
, SkipAnonimousNamespaces
) {
321 constexpr auto Code
= R
"code(
327 const char *data() const { return nullptr; };
329 } // namespace inline anonymous
330 } // namespace anonymous
332 } // namespace anonymous
339 EXPECT_TRUE(tooling::runToolOnCode(
340 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
341 {{{"std", "container", "data"}}, true},
346 TEST(CallDescription
, AliasNames
) {
347 constexpr StringRef AliasNamesCode
= R
"code(
350 const char *data() const;
352 using cont = container;
356 constexpr StringRef UseAliasInSpelling
= R
"code(
361 constexpr StringRef UseStructNameInSpelling
= R
"code(
366 const std::string UseAliasInSpellingCode
=
367 (Twine
{AliasNamesCode
} + UseAliasInSpelling
).str();
368 const std::string UseStructNameInSpellingCode
=
369 (Twine
{AliasNamesCode
} + UseStructNameInSpelling
).str();
371 // Test if the code spells the alias, wile we match against the struct name,
372 // and again matching against the alias.
374 SCOPED_TRACE("Using alias in spelling");
376 SCOPED_TRACE("std container data");
377 EXPECT_TRUE(tooling::runToolOnCode(
378 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
379 {{{"std", "container", "data"}}, true},
381 UseAliasInSpellingCode
));
384 // FIXME: We should be able to see-through aliases.
385 SCOPED_TRACE("std cont data");
386 EXPECT_TRUE(tooling::runToolOnCode(
387 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
388 {{{"std", "cont", "data"}}, false},
390 UseAliasInSpellingCode
));
394 // Test if the code spells the struct name, wile we match against the struct
395 // name, and again matching against the alias.
397 SCOPED_TRACE("Using struct name in spelling");
399 SCOPED_TRACE("std container data");
400 EXPECT_TRUE(tooling::runToolOnCode(
401 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
402 {{{"std", "container", "data"}}, true},
404 UseAliasInSpellingCode
));
407 // FIXME: We should be able to see-through aliases.
408 SCOPED_TRACE("std cont data");
409 EXPECT_TRUE(tooling::runToolOnCode(
410 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
411 {{{"std", "cont", "data"}}, false},
413 UseAliasInSpellingCode
));
418 TEST(CallDescription
, AliasSingleNamespace
) {
419 constexpr StringRef Code
= R
"code(
424 }} // namespace bbb::ccc
425 namespace bbb_alias = bbb;
428 aaa::bbb_alias::ccc::bar();
431 SCOPED_TRACE("aaa bbb ccc bar");
432 EXPECT_TRUE(tooling::runToolOnCode(
433 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
434 {{{"aaa", "bbb", "ccc", "bar"}}, true},
439 // FIXME: We should be able to see-through namespace aliases.
440 SCOPED_TRACE("aaa bbb_alias ccc bar");
441 EXPECT_TRUE(tooling::runToolOnCode(
442 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
443 {{{"aaa", "bbb_alias", "ccc", "bar"}}, false},
449 TEST(CallDescription
, AliasMultipleNamespaces
) {
450 constexpr StringRef Code
= R
"code(
455 }}} // namespace aaa::bbb::ccc
456 namespace aaa_bbb_ccc = aaa::bbb::ccc;
458 using namespace aaa_bbb_ccc;
462 SCOPED_TRACE("aaa bbb ccc bar");
463 EXPECT_TRUE(tooling::runToolOnCode(
464 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
465 {{{"aaa", "bbb", "ccc", "bar"}}, true},
470 // FIXME: We should be able to see-through namespace aliases.
471 SCOPED_TRACE("aaa_bbb_ccc bar");
472 EXPECT_TRUE(tooling::runToolOnCode(
473 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
474 {{{"aaa_bbb_ccc", "bar"}}, false},
480 TEST(CallDescription
, NegativeMatchQualifiedNames
) {
481 EXPECT_TRUE(tooling::runToolOnCode(
482 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>({
483 {{{"foo", "bar"}}, false},
484 {{{"bar", "foo"}}, false},
487 "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
490 TEST(CallDescription
, MatchBuiltins
) {
491 // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
492 EXPECT_TRUE(tooling::runToolOnCode(
493 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
494 {{{{"memset"}, 3}, false},
495 {{CDF_MaybeBuiltin
, {"memset"}, 3}, true}})),
498 " __builtin___memset_chk(&x, 0, sizeof(x),"
499 " __builtin_object_size(&x, 0));"
503 SCOPED_TRACE("multiple similar builtins");
504 EXPECT_TRUE(tooling::runToolOnCode(
505 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
506 {{{CDF_MaybeBuiltin
, {"memcpy"}, 3}, false},
507 {{CDF_MaybeBuiltin
, {"wmemcpy"}, 3}, true}})),
508 R
"(void foo(wchar_t *x, wchar_t *y) {
509 __builtin_wmemcpy(x, y, sizeof(wchar_t));
513 SCOPED_TRACE("multiple similar builtins reversed order");
514 EXPECT_TRUE(tooling::runToolOnCode(
515 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
516 {{{CDF_MaybeBuiltin
, {"wmemcpy"}, 3}, true},
517 {{CDF_MaybeBuiltin
, {"memcpy"}, 3}, false}})),
518 R
"(void foo(wchar_t *x, wchar_t *y) {
519 __builtin_wmemcpy(x, y, sizeof(wchar_t));
523 SCOPED_TRACE("lookbehind and lookahead mismatches");
524 EXPECT_TRUE(tooling::runToolOnCode(
525 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
526 {{{CDF_MaybeBuiltin
, {"func"}}, false}})),
538 SCOPED_TRACE("lookbehind and lookahead matches");
539 EXPECT_TRUE(tooling::runToolOnCode(
540 std::unique_ptr
<FrontendAction
>(new CallDescriptionAction
<>(
541 {{{CDF_MaybeBuiltin
, {"func"}}, true}})),
549 func(); // exact match
557 //===----------------------------------------------------------------------===//
558 // Testing through a checker interface.
560 // Above, the static analyzer isn't run properly, only the bare minimum to
561 // create CallEvents. This causes CallEvents through function pointers to not
562 // refer to the pointee function, but this works fine if we run
563 // AnalysisASTConsumer.
564 //===----------------------------------------------------------------------===//
566 class CallDescChecker
567 : public Checker
<check::PreCall
, check::PreStmt
<CallExpr
>> {
568 CallDescriptionSet Set
= {{{"bar"}, 0}};
571 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const {
572 if (Set
.contains(Call
)) {
573 C
.getBugReporter().EmitBasicReport(
574 Call
.getDecl(), this, "CallEvent match", categories::LogicError
,
576 PathDiagnosticLocation
{Call
.getDecl(), C
.getSourceManager()});
580 void checkPreStmt(const CallExpr
*CE
, CheckerContext
&C
) const {
581 if (Set
.containsAsWritten(*CE
)) {
582 C
.getBugReporter().EmitBasicReport(
583 CE
->getCalleeDecl(), this, "CallExpr match", categories::LogicError
,
585 PathDiagnosticLocation
{CE
->getCalleeDecl(), C
.getSourceManager()});
590 void addCallDescChecker(AnalysisASTConsumer
&AnalysisConsumer
,
591 AnalyzerOptions
&AnOpts
) {
592 AnOpts
.CheckersAndPackages
= {{"test.CallDescChecker", true}};
593 AnalysisConsumer
.AddCheckerRegistrationFn([](CheckerRegistry
&Registry
) {
594 Registry
.addChecker
<CallDescChecker
>("test.CallDescChecker", "Description",
599 TEST(CallDescription
, CheckCallExprMatching
) {
600 // Imprecise matching shouldn't catch the call to bar, because its obscured
601 // by a function pointer.
602 constexpr StringRef FnPtrCode
= R
"code(
605 void (*fnptr)() = bar;
609 EXPECT_TRUE(runCheckerOnCode
<addCallDescChecker
>(FnPtrCode
.str(), Diags
,
610 /*OnlyEmitWarnings*/ true));
611 EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags
);
613 // This should be caught properly by imprecise matching, as the call is done
614 // purely through syntactic means.
615 constexpr StringRef Code
= R
"code(
621 EXPECT_TRUE(runCheckerOnCode
<addCallDescChecker
>(Code
.str(), Diags
,
622 /*OnlyEmitWarnings*/ true));
623 EXPECT_EQ("test.CallDescChecker: CallEvent match\n"
624 "test.CallDescChecker: CallExpr match\n",