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), Total(llvm::count(Range: llvm::make_second_range(c&: Data), Element: 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 that
54// we were supposed to find ("true" in the provided ResultMap) and that we
55// don't find the ones that we weren't supposed to find
56// ("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(E: cast<CXXConstructExpr>(Val: E),
94 /*Target=*/nullptr, State, LCtx: 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: *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 __1
174 using string = __1::basic_string<char>;
175 } // namespace std
176)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 yet
211// sophisticated enough for testing this. To do that, we will need to
212// implement a much more advanced dispatching mechanism using the CFG for
213// 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 } // bbb
223 } // aaa
224 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 std
243 } // namespace my
244
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 v1
265 } // namespace my
266 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 v1
295 } // namespace my
296 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 anonymous
329 } // namespace anonymous
330 } // namespace std
331 } // namespace anonymous
332
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 } // std
353)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 struct
387 // 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::ccc
417 namespace bbb_alias = bbb;
418 } // namespace aaa
419 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::ccc
448 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 match
575 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 to
586// create CallEvents. This causes CallEvents through function pointers to not
587// refer to the pointee function, but this works fine if we run
588// AnalysisASTConsumer.
589//===----------------------------------------------------------------------===//
590
591class CallDescChecker
592 : 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 DeclWithIssue: Call.getDecl(), Checker: this, BugName: "CallEvent match", BugCategory: categories::LogicError,
600 BugStr: "CallEvent match",
601 Loc: PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()});
602 }
603 }
604
605 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
606 if (Set.containsAsWritten(CE: *CE)) {
607 C.getBugReporter().EmitBasicReport(
608 DeclWithIssue: CE->getCalleeDecl(), Checker: this, BugName: "CallExpr match", BugCategory: categories::LogicError,
609 BugStr: "CallExpr match",
610 Loc: 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(Fn: [](CheckerRegistry &Registry) {
619 Registry.addChecker<CallDescChecker>(FullName: "test.CallDescChecker", Desc: "Description",
620 DocsUri: "");
621 });
622}
623
624TEST(CallDescription, CheckCallExprMatching) {
625 // Imprecise matching shouldn't catch the call to bar, because its obscured
626 // 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 done
639 // 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} // namespace
654} // namespace ento
655} // namespace clang
656

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

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