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
25namespace clang {
26namespace ento {
27namespace {
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.
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),
39 Total(std::count_if(first: Data.begin(), last: Data.end(),
40 pred: [](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).
61template <typename MatchedExprT>
62class 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 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(E: cast<CXXConstructExpr>(Val: E),
98 /*Target=*/nullptr, State, LCtx: SFC,
99 ElemRef);
100 llvm_unreachable("Only these expressions are supported for now.");
101 }();
102
103 // If the call actually matched, check if we really expected it to match.
104 const bool *LookupResult = RM.lookup(Call: *Call);
105 EXPECT_TRUE(!LookupResult || *LookupResult);
106
107 // ResultMap is responsible for making sure that we've found *all* calls.
108 }
109
110public:
111 CallDescriptionConsumer(CompilerInstance &C,
112 ResultMap &RM)
113 : ExprEngineConsumer(C), RM(RM) {}
114
115 bool HandleTopLevelDecl(DeclGroupRef DG) override {
116 for (const auto *D : DG)
117 performTest(D);
118 return true;
119 }
120};
121
122template <typename MatchedExprT = CallExpr>
123class CallDescriptionAction : public ASTFrontendAction {
124 ResultMap RM;
125
126public:
127 CallDescriptionAction(
128 std::initializer_list<std::pair<CallDescription, bool>> Data)
129 : RM(Data) {}
130
131 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
132 StringRef File) override {
133 return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,
134 RM);
135 }
136};
137
138TEST(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(); }"));
145}
146
147TEST(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); }"));
154}
155
156TEST(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); }"));
163}
164
165constexpr 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();
174 };
175 } // namespace __1
176 using string = __1::basic_string<char>;
177 } // namespace std
178)code";
179
180TEST(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));
193}
194
195TEST(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));
208}
209
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.
215
216TEST(CallDescription, MatchConversionOperator) {
217 constexpr StringRef Code = R"code(
218 namespace aaa {
219 namespace bbb {
220 struct Bar {
221 operator int();
222 };
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));
234}
235
236TEST(CallDescription, RejectOverQualifiedNames) {
237 constexpr auto Code = R"code(
238 namespace my {
239 namespace std {
240 struct container {
241 const char *data() const;
242 };
243 } // namespace std
244 } // namespace my
245
246 void foo() {
247 using namespace my;
248 std::container v;
249 v.data();
250 })code";
251
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));
258}
259
260TEST(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";
270
271 {
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));
278 }
279 {
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));
287 }
288}
289
290TEST(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";
301
302 {
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));
309 }
310 {
311 SCOPED_TRACE("v1 bar");
312 EXPECT_TRUE(tooling::runToolOnCode(
313 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
314 {{{"v1", "bar"}}, true},
315 })),
316 Code));
317 }
318}
319
320TEST(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; };
328 };
329 } // namespace inline anonymous
330 } // namespace anonymous
331 } // namespace std
332 } // namespace anonymous
333
334 void foo() {
335 std::container v;
336 v.data();
337 })code";
338
339 EXPECT_TRUE(tooling::runToolOnCode(
340 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
341 {{{"std", "container", "data"}}, true},
342 })),
343 Code));
344}
345
346TEST(CallDescription, AliasNames) {
347 constexpr StringRef AliasNamesCode = R"code(
348 namespace std {
349 struct container {
350 const char *data() const;
351 };
352 using cont = container;
353 } // std
354)code";
355
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();
370
371 // Test if the code spells the alias, wile we match against the struct name,
372 // and again matching against the alias.
373 {
374 SCOPED_TRACE("Using alias in spelling");
375 {
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));
382 }
383 {
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));
391 }
392 }
393
394 // Test if the code spells the struct name, wile we match against the struct
395 // name, and again matching against the alias.
396 {
397 SCOPED_TRACE("Using struct name in spelling");
398 {
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));
405 }
406 {
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));
414 }
415 }
416}
417
418TEST(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";
430 {
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));
437 }
438 {
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));
446 }
447}
448
449TEST(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";
461 {
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));
468 }
469 {
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));
477 }
478}
479
480TEST(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(); }"));
488}
489
490TEST(CallDescription, MatchBuiltins) {
491 // Test the matching modes CDM::CLibrary and CDM::CLibraryMaybeHardened,
492 // which can recognize builtin variants of C library functions.
493 {
494 SCOPED_TRACE("hardened variants of functions");
495 EXPECT_TRUE(tooling::runToolOnCode(
496 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
497 {{{CDM::Unspecified, {"memset"}, 3}, false},
498 {{CDM::CLibrary, {"memset"}, 3}, false},
499 {{CDM::CLibraryMaybeHardened, {"memset"}, 3}, true}})),
500 "void foo() {"
501 " int x;"
502 " __builtin___memset_chk(&x, 0, sizeof(x),"
503 " __builtin_object_size(&x, 0));"
504 "}"));
505 }
506 {
507 SCOPED_TRACE("multiple similar builtins");
508 EXPECT_TRUE(tooling::runToolOnCode(
509 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
510 {{{CDM::CLibrary, {"memcpy"}, 3}, false},
511 {{CDM::CLibrary, {"wmemcpy"}, 3}, true}})),
512 R"(void foo(wchar_t *x, wchar_t *y) {
513 __builtin_wmemcpy(x, y, sizeof(wchar_t));
514 })"));
515 }
516 {
517 SCOPED_TRACE("multiple similar builtins reversed order");
518 EXPECT_TRUE(tooling::runToolOnCode(
519 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
520 {{{CDM::CLibrary, {"wmemcpy"}, 3}, true},
521 {{CDM::CLibrary, {"memcpy"}, 3}, false}})),
522 R"(void foo(wchar_t *x, wchar_t *y) {
523 __builtin_wmemcpy(x, y, sizeof(wchar_t));
524 })"));
525 }
526 {
527 SCOPED_TRACE("multiple similar builtins with hardened variant");
528 EXPECT_TRUE(tooling::runToolOnCode(
529 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
530 {{{CDM::CLibraryMaybeHardened, {"memcpy"}, 3}, false},
531 {{CDM::CLibraryMaybeHardened, {"wmemcpy"}, 3}, true}})),
532 R"(typedef __typeof(sizeof(int)) size_t;
533 extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,
534 const wchar_t *__restrict __s2,
535 size_t __n, size_t __ns1);
536 void foo(wchar_t *x, wchar_t *y) {
537 __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);
538 })"));
539 }
540 {
541 SCOPED_TRACE(
542 "multiple similar builtins with hardened variant reversed order");
543 EXPECT_TRUE(tooling::runToolOnCode(
544 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
545 {{{CDM::CLibraryMaybeHardened, {"wmemcpy"}, 3}, true},
546 {{CDM::CLibraryMaybeHardened, {"memcpy"}, 3}, false}})),
547 R"(typedef __typeof(sizeof(int)) size_t;
548 extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,
549 const wchar_t *__restrict __s2,
550 size_t __n, size_t __ns1);
551 void foo(wchar_t *x, wchar_t *y) {
552 __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);
553 })"));
554 }
555 {
556 SCOPED_TRACE("lookbehind and lookahead mismatches");
557 EXPECT_TRUE(tooling::runToolOnCode(
558 std::unique_ptr<FrontendAction>(
559 new CallDescriptionAction<>({{{CDM::CLibrary, {"func"}}, false}})),
560 R"(
561 void funcXXX();
562 void XXXfunc();
563 void XXXfuncXXX();
564 void test() {
565 funcXXX();
566 XXXfunc();
567 XXXfuncXXX();
568 })"));
569 }
570 {
571 SCOPED_TRACE("lookbehind and lookahead matches");
572 EXPECT_TRUE(tooling::runToolOnCode(
573 std::unique_ptr<FrontendAction>(
574 new CallDescriptionAction<>({{{CDM::CLibrary, {"func"}}, true}})),
575 R"(
576 void func();
577 void func_XXX();
578 void XXX_func();
579 void XXX_func_XXX();
580
581 void test() {
582 func(); // exact match
583 func_XXX();
584 XXX_func();
585 XXX_func_XXX();
586 })"));
587 }
588}
589
590//===----------------------------------------------------------------------===//
591// Testing through a checker interface.
592//
593// Above, the static analyzer isn't run properly, only the bare minimum to
594// create CallEvents. This causes CallEvents through function pointers to not
595// refer to the pointee function, but this works fine if we run
596// AnalysisASTConsumer.
597//===----------------------------------------------------------------------===//
598
599class CallDescChecker
600 : public Checker<check::PreCall, check::PreStmt<CallExpr>> {
601 CallDescriptionSet Set = {{{"bar"}, 0}};
602
603public:
604 void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
605 if (Set.contains(Call)) {
606 C.getBugReporter().EmitBasicReport(
607 DeclWithIssue: Call.getDecl(), Checker: this, BugName: "CallEvent match", BugCategory: categories::LogicError,
608 BugStr: "CallEvent match",
609 Loc: PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()});
610 }
611 }
612
613 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
614 if (Set.containsAsWritten(CE: *CE)) {
615 C.getBugReporter().EmitBasicReport(
616 DeclWithIssue: CE->getCalleeDecl(), Checker: this, BugName: "CallExpr match", BugCategory: categories::LogicError,
617 BugStr: "CallExpr match",
618 Loc: PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()});
619 }
620 }
621};
622
623void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer,
624 AnalyzerOptions &AnOpts) {
625 AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}};
626 AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) {
627 Registry.addChecker<CallDescChecker>(FullName: "test.CallDescChecker", Desc: "Description",
628 DocsUri: "");
629 });
630}
631
632TEST(CallDescription, CheckCallExprMatching) {
633 // Imprecise matching shouldn't catch the call to bar, because its obscured
634 // by a function pointer.
635 constexpr StringRef FnPtrCode = R"code(
636 void bar();
637 void foo() {
638 void (*fnptr)() = bar;
639 fnptr();
640 })code";
641 std::string Diags;
642 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags,
643 /*OnlyEmitWarnings*/ true));
644 EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags);
645
646 // This should be caught properly by imprecise matching, as the call is done
647 // purely through syntactic means.
648 constexpr StringRef Code = R"code(
649 void bar();
650 void foo() {
651 bar();
652 })code";
653 Diags.clear();
654 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags,
655 /*OnlyEmitWarnings*/ true));
656 EXPECT_EQ("test.CallDescChecker: CallEvent match\n"
657 "test.CallDescChecker: CallExpr match\n",
658 Diags);
659}
660
661} // namespace
662} // namespace ento
663} // namespace clang
664

source code of clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp