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