clang-tools 6.0.0

SuspiciousStringCompareCheck.cpp
Go to the documentation of this file.
1 //===--- SuspiciousStringCompareCheck.cpp - clang-tidy---------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "SuspiciousStringCompareCheck.h"
11 #include "../utils/Matchers.h"
12 #include "../utils/OptionsUtils.h"
13 #include "clang/AST/ASTContext.h"
14 #include "clang/ASTMatchers/ASTMatchFinder.h"
15 #include "clang/Lex/Lexer.h"
16
17 using namespace clang::ast_matchers;
18
19 namespace clang {
20 namespace tidy {
21 namespace misc {
22
23 // Semicolon separated list of known string compare-like functions. The list
24 // must ends with a semicolon.
25 static const char KnownStringCompareFunctions[] = "__builtin_memcmp;"
26 "__builtin_strcasecmp;"
27 "__builtin_strcmp;"
28 "__builtin_strncasecmp;"
29 "__builtin_strncmp;"
30 "_mbscmp;"
31 "_mbscmp_l;"
32 "_mbsicmp;"
33 "_mbsicmp_l;"
34 "_mbsnbcmp;"
35 "_mbsnbcmp_l;"
36 "_mbsnbicmp;"
37 "_mbsnbicmp_l;"
38 "_mbsncmp;"
39 "_mbsncmp_l;"
40 "_mbsnicmp;"
41 "_mbsnicmp_l;"
42 "_memicmp;"
43 "_memicmp_l;"
44 "_stricmp;"
45 "_stricmp_l;"
46 "_strnicmp;"
47 "_strnicmp_l;"
48 "_wcsicmp;"
49 "_wcsicmp_l;"
50 "_wcsnicmp;"
51 "_wcsnicmp_l;"
52 "lstrcmp;"
53 "lstrcmpi;"
54 "memcmp;"
55 "memicmp;"
56 "strcasecmp;"
57 "strcmp;"
58 "strcmpi;"
59 "stricmp;"
60 "strncasecmp;"
61 "strncmp;"
62 "strnicmp;"
63 "wcscasecmp;"
64 "wcscmp;"
65 "wcsicmp;"
66 "wcsncmp;"
67 "wcsnicmp;"
68 "wmemcmp;";
69
70 SuspiciousStringCompareCheck::SuspiciousStringCompareCheck(
71 StringRef Name, ClangTidyContext *Context)
72 : ClangTidyCheck(Name, Context),
73 WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", 1)),
74 WarnOnLogicalNotComparison(Options.get("WarnOnLogicalNotComparison", 0)),
75 StringCompareLikeFunctions(
76 Options.get("StringCompareLikeFunctions", "")) {}
77
78 void SuspiciousStringCompareCheck::storeOptions(
79 ClangTidyOptions::OptionMap &Opts) {
80 Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison);
81 Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison);
82 Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions);
83 }
84
85 void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) {
86 // Match relational operators.
87 const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!"));
88 const auto ComparisonBinaryOperator =
89 binaryOperator(matchers::isComparisonOperator());
90 const auto ComparisonOperator =
91 expr(anyOf(ComparisonUnaryOperator, ComparisonBinaryOperator));
92
93 // Add the list of known string compare-like functions and add user-defined
94 // functions.
95 std::vector<std::string> FunctionNames = utils::options::parseStringList(
96 (llvm::Twine(KnownStringCompareFunctions) + StringCompareLikeFunctions)
97 .str());
98
99 // Match a call to a string compare functions.
100 const auto FunctionCompareDecl =
101 functionDecl(hasAnyName(std::vector<StringRef>(FunctionNames.begin(),
102 FunctionNames.end())))
103 .bind("decl");
104 const auto DirectStringCompareCallExpr =
105 callExpr(hasDeclaration(FunctionCompareDecl)).bind("call");
106 const auto MacroStringCompareCallExpr = conditionalOperator(anyOf(
107 hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)),
108 hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr))));
109 // The implicit cast is not present in C.
110 const auto StringCompareCallExpr = ignoringParenImpCasts(
111 anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr));
112
113 if (WarnOnImplicitComparison) {
114 // Detect suspicious calls to string compare:
115 // 'if (strcmp())' -> 'if (strcmp() != 0)'
116 Finder->addMatcher(
117 stmt(anyOf(ifStmt(hasCondition(StringCompareCallExpr)),
118 whileStmt(hasCondition(StringCompareCallExpr)),
119 doStmt(hasCondition(StringCompareCallExpr)),
120 forStmt(hasCondition(StringCompareCallExpr)),
121 binaryOperator(
122 anyOf(hasOperatorName("&&"), hasOperatorName("||")),
123 hasEitherOperand(StringCompareCallExpr))))
124 .bind("missing-comparison"),
125 this);
126 }
127
128 if (WarnOnLogicalNotComparison) {
129 // Detect suspicious calls to string compared with '!' operator:
130 // 'if (!strcmp())' -> 'if (strcmp() == 0)'
131 Finder->addMatcher(unaryOperator(hasOperatorName("!"),
132 hasUnaryOperand(ignoringParenImpCasts(
133 StringCompareCallExpr)))
134 .bind("logical-not-comparison"),
135 this);
136 }
137
138 // Detect suspicious cast to an inconsistant type (i.e. not integer type).
139 Finder->addMatcher(
140 implicitCastExpr(unless(hasType(isInteger())),
141 hasSourceExpression(StringCompareCallExpr))
142 .bind("invalid-conversion"),
143 this);
144
145 // Detect suspicious operator with string compare function as operand.
146 Finder->addMatcher(
147 binaryOperator(
148 unless(anyOf(matchers::isComparisonOperator(), hasOperatorName("&&"),
149 hasOperatorName("||"), hasOperatorName("="))),
150 hasEitherOperand(StringCompareCallExpr))
151 .bind("suspicious-operator"),
152 this);
153
154 // Detect comparison to invalid constant: 'strcmp() == -1'.
155 const auto InvalidLiteral = ignoringParenImpCasts(
156 anyOf(integerLiteral(unless(equals(0))),
157 unaryOperator(
158 hasOperatorName("-"),
159 has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))),
160 characterLiteral(), cxxBoolLiteral()));
161
162 Finder->addMatcher(binaryOperator(matchers::isComparisonOperator(),
163 hasEitherOperand(StringCompareCallExpr),
164 hasEitherOperand(InvalidLiteral))
165 .bind("invalid-comparison"),
166 this);
167 }
168
169 void SuspiciousStringCompareCheck::check(
170 const MatchFinder::MatchResult &Result) {
171 const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl");
172 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
173 assert(Decl != nullptr && Call != nullptr);
174
175 if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) {
176 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
177 Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
178 getLangOpts());
179
180 diag(Call->getLocStart(),
181 "function %0 is called without explicitly comparing result")
182 << Decl << FixItHint::CreateInsertion(EndLoc, " != 0");
183 }
184
185 if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) {
186 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
187 Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
188 getLangOpts());
189 SourceLocation NotLoc = E->getLocStart();
190
191 diag(Call->getLocStart(),
192 "function %0 is compared using logical not operator")
193 << Decl << FixItHint::CreateRemoval(
194 CharSourceRange::getTokenRange(NotLoc, NotLoc))
195 << FixItHint::CreateInsertion(EndLoc, " == 0");
196 }
197
198 if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) {
199 diag(Call->getLocStart(),
200 "function %0 is compared to a suspicious constant")
201 << Decl;
202 }
203
204 if (const auto *BinOp =
205 Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) {
206 diag(Call->getLocStart(), "results of function %0 used by operator '%1'")
207 << Decl << BinOp->getOpcodeStr();
208 }
209
210 if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) {
211 diag(Call->getLocStart(), "function %0 has suspicious implicit cast")
212 << Decl;
213 }
214 }
215
216 } // namespace misc
217 } // namespace tidy
218 } // namespace clang
clang::ast_matchers
Definition: ClangTidyDiagnosticConsumer.h:27
clang::tidy::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidy.cpp:449
clang::tidy::misc::SuspiciousStringCompareCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: SuspiciousStringCompareCheck.cpp:78
Name
StringHandle Name
Definition: PreprocessorTracker.cpp:525
clang::tidy::ClangTidyCheck::getLangOpts
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
clang::tidy::misc::SuspiciousStringCompareCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: SuspiciousStringCompareCheck.cpp:169
clang::tidy::misc::SuspiciousStringCompareCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: SuspiciousStringCompareCheck.cpp:85
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
clang::tidy::utils::options::parseStringList
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
Definition: OptionsUtils.cpp:19
SuspiciousStringCompareCheck.h
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidy.h:183
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:99
clang::tidy::misc::KnownStringCompareFunctions
static const char KnownStringCompareFunctions[]
Definition: SuspiciousStringCompareCheck.cpp:25
clang
Definition: AndroidTidyModule.cpp:28
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:104
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:416