xref: /llvm-project/clang-tools-extra/clang-tidy/cert/NonTrivialTypesLibcMemoryCallsCheck.cpp (revision df0c8f25145047731fb95b4ce7153ce6fb5b6f5d)
1 //===--- NonTrivialTypesLibcMemoryCallsCheck.cpp - clang-tidy ----------===//
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 "NonTrivialTypesLibcMemoryCallsCheck.h"
10 #include "../utils/OptionsUtils.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/ASTMatchers/ASTMatchersInternal.h"
15 #include "clang/ASTMatchers/ASTMatchersMacros.h"
16 #include "llvm/ADT/SmallVector.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/Support/raw_ostream.h"
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang::tidy::cert {
23 
24 namespace {
AST_MATCHER(CXXRecordDecl,isTriviallyDefaultConstructible)25 AST_MATCHER(CXXRecordDecl, isTriviallyDefaultConstructible) {
26   return Node.hasTrivialDefaultConstructor();
27 }
AST_MATCHER(CXXRecordDecl,isTriviallyCopyable)28 AST_MATCHER(CXXRecordDecl, isTriviallyCopyable) {
29   return Node.hasTrivialCopyAssignment() && Node.hasTrivialCopyConstructor();
30 }
31 } // namespace
32 
33 static const char BuiltinMemSet[] = "::std::memset;"
34                                     "::memset;";
35 static const char BuiltinMemCpy[] = "::std::memcpy;"
36                                     "::memcpy;"
37                                     "::std::memmove;"
38                                     "::memmove;"
39                                     "::std::strcpy;"
40                                     "::strcpy;"
41                                     "::memccpy;"
42                                     "::stpncpy;"
43                                     "::strncpy;";
44 static const char BuiltinMemCmp[] = "::std::memcmp;"
45                                     "::memcmp;"
46                                     "::std::strcmp;"
47                                     "::strcmp;"
48                                     "::strncmp;";
49 static constexpr llvm::StringRef ComparisonOperators[] = {
50     "operator==", "operator!=", "operator<",
51     "operator>",  "operator<=", "operator>="};
52 
NonTrivialTypesLibcMemoryCallsCheck(StringRef Name,ClangTidyContext * Context)53 NonTrivialTypesLibcMemoryCallsCheck::NonTrivialTypesLibcMemoryCallsCheck(
54     StringRef Name, ClangTidyContext *Context)
55     : ClangTidyCheck(Name, Context),
56       MemSetNames(Options.get("MemSetNames", "")),
57       MemCpyNames(Options.get("MemCpyNames", "")),
58       MemCmpNames(Options.get("MemCmpNames", "")) {}
59 
storeOptions(ClangTidyOptions::OptionMap & Opts)60 void NonTrivialTypesLibcMemoryCallsCheck::storeOptions(
61     ClangTidyOptions::OptionMap &Opts) {
62   Options.store(Opts, "MemSetNames", MemSetNames);
63   Options.store(Opts, "MemCpyNames", MemCpyNames);
64   Options.store(Opts, "MemCmpNames", MemCmpNames);
65 }
66 
registerMatchers(MatchFinder * Finder)67 void NonTrivialTypesLibcMemoryCallsCheck::registerMatchers(
68     MatchFinder *Finder) {
69   using namespace ast_matchers::internal;
70   auto IsStructPointer = [](Matcher<CXXRecordDecl> Constraint = anything(),
71                             bool Bind = false) {
72     return expr(unaryOperator(
73         hasOperatorName("&"),
74         hasUnaryOperand(declRefExpr(
75             hasType(cxxRecordDecl(Constraint)),
76             hasType(Bind ? qualType().bind("Record") : qualType())))));
77   };
78   auto IsRecordSizeOf =
79       expr(sizeOfExpr(hasArgumentOfType(equalsBoundNode("Record"))));
80   auto ArgChecker = [&](Matcher<CXXRecordDecl> RecordConstraint,
81                         BindableMatcher<Stmt> SecondArg = expr()) {
82     return allOf(argumentCountIs(3),
83                  hasArgument(0, IsStructPointer(RecordConstraint, true)),
84                  hasArgument(1, SecondArg), hasArgument(2, IsRecordSizeOf));
85   };
86 
87   Finder->addMatcher(
88       callExpr(callee(namedDecl(hasAnyName(
89                    utils::options::parseListPair(BuiltinMemSet, MemSetNames)))),
90                ArgChecker(unless(isTriviallyDefaultConstructible())))
91           .bind("lazyConstruct"),
92       this);
93   Finder->addMatcher(
94       callExpr(callee(namedDecl(hasAnyName(
95                    utils::options::parseListPair(BuiltinMemCpy, MemCpyNames)))),
96                ArgChecker(unless(isTriviallyCopyable()), IsStructPointer()))
97           .bind("lazyCopy"),
98       this);
99   Finder->addMatcher(
100       callExpr(callee(namedDecl(hasAnyName(
101                    utils::options::parseListPair(BuiltinMemCmp, MemCmpNames)))),
102                ArgChecker(hasMethod(hasAnyName(ComparisonOperators)),
103                           IsStructPointer()))
104           .bind("lazyCompare"),
105       this);
106 }
107 
check(const MatchFinder::MatchResult & Result)108 void NonTrivialTypesLibcMemoryCallsCheck::check(
109     const MatchFinder::MatchResult &Result) {
110   if (const auto *Caller = Result.Nodes.getNodeAs<CallExpr>("lazyConstruct")) {
111     diag(Caller->getBeginLoc(), "calling %0 on a non-trivially default "
112                                 "constructible class is undefined")
113         << cast<NamedDecl>(Caller->getCalleeDecl());
114   }
115   if (const auto *Caller = Result.Nodes.getNodeAs<CallExpr>("lazyCopy")) {
116     diag(Caller->getBeginLoc(),
117          "calling %0 on a non-trivially copyable class is undefined")
118         << cast<NamedDecl>(Caller->getCalleeDecl());
119   }
120   if (const auto *Caller = Result.Nodes.getNodeAs<CallExpr>("lazyCompare")) {
121     diag(Caller->getBeginLoc(),
122          "consider using comparison operators instead of calling %0")
123         << cast<NamedDecl>(Caller->getCalleeDecl());
124   }
125 }
126 
127 } // namespace clang::tidy::cert
128