clang-tools  7.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 bugprone {
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 bugprone
217 } // namespace tidy
218 } // namespace clang
llvm::StringRef Name
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:460
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
static const char KnownStringCompareFunctions[]
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
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
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.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:427