• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SuspiciousStringCompareCheck(StringRef Name,ClangTidyContext * Context)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 
storeOptions(ClangTidyOptions::OptionMap & Opts)78 void SuspiciousStringCompareCheck::storeOptions(
79     ClangTidyOptions::OptionMap &Opts) {
80   Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison);
81   Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison);
82   Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions);
83 }
84 
registerMatchers(MatchFinder * Finder)85 void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) {
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 
check(const MatchFinder::MatchResult & Result)167 void SuspiciousStringCompareCheck::check(
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
218