Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / clang / unittests / StaticAnalyzer / CallDescriptionTest.cpp
blobe8b237b891ca8b120a5e64b9354bd6020ea25436
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 //===----------------------------------------------------------------------===//
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>
25 namespace clang {
26 namespace ento {
27 namespace {
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;
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)) {}
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;
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 {
63 ResultMap &RM;
64 void performTest(const Decl *D) {
65 using namespace ast_matchers;
66 using T = MatchedExprT;
68 if (!D->hasBody())
69 return;
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)
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.");
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(),
93 SFC->getIndex()};
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,
99 ElemRef);
100 llvm_unreachable("Only these expressions are supported for now.");
101 }();
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.
110 public:
111 CallDescriptionConsumer(CompilerInstance &C,
112 ResultMap &RM)
113 : ExprEngineConsumer(C), RM(RM) {}
115 bool HandleTopLevelDecl(DeclGroupRef DG) override {
116 for (const auto *D : DG)
117 performTest(D);
118 return true;
122 template <typename MatchedExprT = CallExpr>
123 class CallDescriptionAction : public ASTFrontendAction {
124 ResultMap RM;
126 public:
127 CallDescriptionAction(
128 std::initializer_list<std::pair<CallDescription, bool>> Data)
129 : RM(Data) {}
131 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
132 StringRef File) override {
133 return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,
134 RM);
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.
143 })),
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},
152 })),
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},
161 })),
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 {
168 class Allocator {};
169 public:
170 basic_string();
171 explicit basic_string(const char*, const Allocator & = Allocator());
172 ~basic_string();
173 T *c_str();
175 } // namespace __1
176 using string = __1::basic_string<char>;
177 } // namespace std
178 )code";
180 TEST(CallDescription, QualifiedNames) {
181 constexpr StringRef AdditionalCode = R"code(
182 void foo() {
183 using namespace std;
184 basic_string<char> s;
185 s.c_str();
186 })code";
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},
191 })),
192 Code));
195 TEST(CallDescription, MatchConstructor) {
196 constexpr StringRef AdditionalCode = R"code(
197 void foo() {
198 using namespace std;
199 basic_string<char> s("hello");
200 })code";
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},
206 })),
207 Code));
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(
218 namespace aaa {
219 namespace bbb {
220 struct Bar {
221 operator int();
223 } // bbb
224 } // aaa
225 void foo() {
226 aaa::bbb::Bar x;
227 int tmp = x;
228 })code";
229 EXPECT_TRUE(tooling::runToolOnCode(
230 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
231 {{{"aaa", "bbb", "Bar", "operator int"}}, true},
232 })),
233 Code));
236 TEST(CallDescription, RejectOverQualifiedNames) {
237 constexpr auto Code = R"code(
238 namespace my {
239 namespace std {
240 struct container {
241 const char *data() const;
243 } // namespace std
244 } // namespace my
246 void foo() {
247 using namespace my;
248 std::container v;
249 v.data();
250 })code";
252 // FIXME: We should **not** match.
253 EXPECT_TRUE(tooling::runToolOnCode(
254 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
255 {{{"std", "container", "data"}}, true},
256 })),
257 Code));
260 TEST(CallDescription, DontSkipNonInlineNamespaces) {
261 constexpr auto Code = R"code(
262 namespace my {
263 /*not inline*/ namespace v1 {
264 void bar();
265 } // namespace v1
266 } // namespace my
267 void foo() {
268 my::v1::bar();
269 })code";
272 SCOPED_TRACE("my v1 bar");
273 EXPECT_TRUE(tooling::runToolOnCode(
274 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
275 {{{"my", "v1", "bar"}}, true},
276 })),
277 Code));
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},
285 })),
286 Code));
290 TEST(CallDescription, SkipTopInlineNamespaces) {
291 constexpr auto Code = R"code(
292 inline namespace my {
293 namespace v1 {
294 void bar();
295 } // namespace v1
296 } // namespace my
297 void foo() {
298 using namespace v1;
299 bar();
300 })code";
303 SCOPED_TRACE("my v1 bar");
304 EXPECT_TRUE(tooling::runToolOnCode(
305 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
306 {{{"my", "v1", "bar"}}, true},
307 })),
308 Code));
311 SCOPED_TRACE("v1 bar");
312 EXPECT_TRUE(tooling::runToolOnCode(
313 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
314 {{{"v1", "bar"}}, true},
315 })),
316 Code));
320 TEST(CallDescription, SkipAnonimousNamespaces) {
321 constexpr auto Code = R"code(
322 namespace {
323 namespace std {
324 namespace {
325 inline namespace {
326 struct container {
327 const char *data() const { return nullptr; };
329 } // namespace inline anonymous
330 } // namespace anonymous
331 } // namespace std
332 } // namespace anonymous
334 void foo() {
335 std::container v;
336 v.data();
337 })code";
339 EXPECT_TRUE(tooling::runToolOnCode(
340 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
341 {{{"std", "container", "data"}}, true},
342 })),
343 Code));
346 TEST(CallDescription, AliasNames) {
347 constexpr StringRef AliasNamesCode = R"code(
348 namespace std {
349 struct container {
350 const char *data() const;
352 using cont = container;
353 } // std
354 )code";
356 constexpr StringRef UseAliasInSpelling = R"code(
357 void foo() {
358 std::cont v;
359 v.data();
360 })code";
361 constexpr StringRef UseStructNameInSpelling = R"code(
362 void foo() {
363 std::container v;
364 v.data();
365 })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},
380 })),
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},
389 })),
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},
403 })),
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},
412 })),
413 UseAliasInSpellingCode));
418 TEST(CallDescription, AliasSingleNamespace) {
419 constexpr StringRef Code = R"code(
420 namespace aaa {
421 namespace bbb {
422 namespace ccc {
423 void bar();
424 }} // namespace bbb::ccc
425 namespace bbb_alias = bbb;
426 } // namespace aaa
427 void foo() {
428 aaa::bbb_alias::ccc::bar();
429 })code";
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},
435 })),
436 Code));
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},
444 })),
445 Code));
449 TEST(CallDescription, AliasMultipleNamespaces) {
450 constexpr StringRef Code = R"code(
451 namespace aaa {
452 namespace bbb {
453 namespace ccc {
454 void bar();
455 }}} // namespace aaa::bbb::ccc
456 namespace aaa_bbb_ccc = aaa::bbb::ccc;
457 void foo() {
458 using namespace aaa_bbb_ccc;
459 bar();
460 })code";
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},
466 })),
467 Code));
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},
475 })),
476 Code));
480 TEST(CallDescription, NegativeMatchQualifiedNames) {
481 EXPECT_TRUE(tooling::runToolOnCode(
482 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
483 {{{"foo", "bar"}}, false},
484 {{{"bar", "foo"}}, false},
485 {{{"foo"}}, true},
486 })),
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}})),
496 "void foo() {"
497 " int x;"
498 " __builtin___memset_chk(&x, 0, sizeof(x),"
499 " __builtin_object_size(&x, 0));"
500 "}"));
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));
510 })"));
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));
520 })"));
523 SCOPED_TRACE("lookbehind and lookahead mismatches");
524 EXPECT_TRUE(tooling::runToolOnCode(
525 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
526 {{{CDF_MaybeBuiltin, {"func"}}, false}})),
528 void funcXXX();
529 void XXXfunc();
530 void XXXfuncXXX();
531 void test() {
532 funcXXX();
533 XXXfunc();
534 XXXfuncXXX();
535 })"));
538 SCOPED_TRACE("lookbehind and lookahead matches");
539 EXPECT_TRUE(tooling::runToolOnCode(
540 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
541 {{{CDF_MaybeBuiltin, {"func"}}, true}})),
543 void func();
544 void func_XXX();
545 void XXX_func();
546 void XXX_func_XXX();
548 void test() {
549 func(); // exact match
550 func_XXX();
551 XXX_func();
552 XXX_func_XXX();
553 })"));
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}};
570 public:
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,
575 "CallEvent match",
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,
584 "CallExpr match",
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",
595 "");
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(
603 void bar();
604 void foo() {
605 void (*fnptr)() = bar;
606 fnptr();
607 })code";
608 std::string Diags;
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(
616 void bar();
617 void foo() {
618 bar();
619 })code";
620 Diags.clear();
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",
625 Diags);
628 } // namespace
629 } // namespace ento
630 } // namespace clang