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