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 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 "}"));
501
502 {
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 })"));
511 }
512 {
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 })"));
521 }
522 {
523 SCOPED_TRACE("lookbehind and lookahead mismatches");
524 EXPECT_TRUE(tooling::runToolOnCode(
525 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
526 {{{CDF_MaybeBuiltin, {"func"}}, false}})),
527 R"(
528 void funcXXX();
529 void XXXfunc();
530 void XXXfuncXXX();
531 void test() {
532 funcXXX();
533 XXXfunc();
534 XXXfuncXXX();
535 })"));
536 }
537 {
538 SCOPED_TRACE("lookbehind and lookahead matches");
539 EXPECT_TRUE(tooling::runToolOnCode(
540 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
541 {{{CDF_MaybeBuiltin, {"func"}}, true}})),
542 R"(
543 void func();
544 void func_XXX();
545 void XXX_func();
546 void XXX_func_XXX();
547
548 void test() {
549 func(); // exact match
550 func_XXX();
551 XXX_func();
552 XXX_func_XXX();
553 })"));
554 }
555}
556
557//===----------------------------------------------------------------------===//
558// Testing through a checker interface.
559//
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//===----------------------------------------------------------------------===//
565
566class CallDescChecker
567 : public Checker<check::PreCall, check::PreStmt<CallExpr>> {
568 CallDescriptionSet Set = {{{"bar"}, 0}};
569
570public:
571 void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
572 if (Set.contains(Call)) {
573 C.getBugReporter().EmitBasicReport(
574 DeclWithIssue: Call.getDecl(), Checker: this, BugName: "CallEvent match", BugCategory: categories::LogicError,
575 BugStr: "CallEvent match",
576 Loc: PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()});
577 }
578 }
579
580 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
581 if (Set.containsAsWritten(CE: *CE)) {
582 C.getBugReporter().EmitBasicReport(
583 DeclWithIssue: CE->getCalleeDecl(), Checker: this, BugName: "CallExpr match", BugCategory: categories::LogicError,
584 BugStr: "CallExpr match",
585 Loc: PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()});
586 }
587 }
588};
589
590void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer,
591 AnalyzerOptions &AnOpts) {
592 AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}};
593 AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) {
594 Registry.addChecker<CallDescChecker>(FullName: "test.CallDescChecker", Desc: "Description",
595 DocsUri: "");
596 });
597}
598
599TEST(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);
612
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);
626}
627
628} // namespace
629} // namespace ento
630} // namespace clang
631

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