clang-tools 10.0.0

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