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 
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 
80  Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison);
81  Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison);
82  Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions);
83 }
84 
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 
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
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
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
StringHandle Name
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
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:127
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
std::map< std::string, std::string > OptionMap
static const char KnownStringCompareFunctions[]
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&#39;s name.
Definition: ClangTidy.cpp:416