clang-tools  11.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 
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", true)),
73  WarnOnLogicalNotComparison(
74  Options.get("WarnOnLogicalNotComparison", false)),
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 = binaryOperator(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(hasAnyOperatorName("&&", "||"),
121  hasEitherOperand(StringCompareCallExpr))))
122  .bind("missing-comparison"),
123  this);
124  }
125 
126  if (WarnOnLogicalNotComparison) {
127  // Detect suspicious calls to string compared with '!' operator:
128  // 'if (!strcmp())' -> 'if (strcmp() == 0)'
129  Finder->addMatcher(unaryOperator(hasOperatorName("!"),
130  hasUnaryOperand(ignoringParenImpCasts(
131  StringCompareCallExpr)))
132  .bind("logical-not-comparison"),
133  this);
134  }
135 
136  // Detect suspicious cast to an inconsistant type (i.e. not integer type).
137  Finder->addMatcher(
138  traverse(ast_type_traits::TK_AsIs,
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(unless(anyOf(isComparisonOperator(), hasOperatorName("&&"),
147  hasOperatorName("||"), hasOperatorName("="))),
148  hasEitherOperand(StringCompareCallExpr))
149  .bind("suspicious-operator"),
150  this);
151 
152  // Detect comparison to invalid constant: 'strcmp() == -1'.
153  const auto InvalidLiteral = ignoringParenImpCasts(
154  anyOf(integerLiteral(unless(equals(0))),
155  unaryOperator(
156  hasOperatorName("-"),
157  has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))),
158  characterLiteral(), cxxBoolLiteral()));
159 
160  Finder->addMatcher(
161  binaryOperator(isComparisonOperator(),
162  hasOperands(StringCompareCallExpr, InvalidLiteral))
163  .bind("invalid-comparison"),
164  this);
165 }
166 
168  const MatchFinder::MatchResult &Result) {
169  const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl");
170  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
171  assert(Decl != nullptr && Call != nullptr);
172 
173  if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) {
174  SourceLocation EndLoc = Lexer::getLocForEndOfToken(
175  Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
176  getLangOpts());
177 
178  diag(Call->getBeginLoc(),
179  "function %0 is called without explicitly comparing result")
180  << Decl << FixItHint::CreateInsertion(EndLoc, " != 0");
181  }
182 
183  if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) {
184  SourceLocation EndLoc = Lexer::getLocForEndOfToken(
185  Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
186  getLangOpts());
187  SourceLocation NotLoc = E->getBeginLoc();
188 
189  diag(Call->getBeginLoc(),
190  "function %0 is compared using logical not operator")
191  << Decl
192  << FixItHint::CreateRemoval(
193  CharSourceRange::getTokenRange(NotLoc, NotLoc))
194  << FixItHint::CreateInsertion(EndLoc, " == 0");
195  }
196 
197  if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) {
198  diag(Call->getBeginLoc(),
199  "function %0 is compared to a suspicious constant")
200  << Decl;
201  }
202 
203  if (const auto *BinOp =
204  Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) {
205  diag(Call->getBeginLoc(), "results of function %0 used by operator '%1'")
206  << Decl << BinOp->getOpcodeStr();
207  }
208 
209  if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) {
210  diag(Call->getBeginLoc(), "function %0 has suspicious implicit cast")
211  << Decl;
212  }
213 }
214 
215 } // namespace bugprone
216 } // namespace tidy
217 } // namespace clang
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
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:114
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:475
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::utils::options::parseStringList
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
Definition: OptionsUtils.cpp:18
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:471
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:76
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp: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:167
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:55
clang::tidy::bugprone::SuspiciousStringCompareCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: SuspiciousStringCompareCheck.cpp:85
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:78
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
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:152
SuspiciousStringCompareCheck.h
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:111