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