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<StringRef> FunctionNames = utils::options::parseListPair(
95 KnownStringCompareFunctions, StringCompareLikeFunctions);
96
97 // Match a call to a string compare functions.
98 const auto FunctionCompareDecl =
99 functionDecl(hasAnyName(FunctionNames)).bind("decl");
100 const auto DirectStringCompareCallExpr =
101 callExpr(hasDeclaration(FunctionCompareDecl)).bind("call");
102 const auto MacroStringCompareCallExpr = conditionalOperator(anyOf(
103 hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)),
104 hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr))));
105 // The implicit cast is not present in C.
106 const auto StringCompareCallExpr = ignoringParenImpCasts(
107 anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr));
108
109 if (WarnOnImplicitComparison) {
110 // Detect suspicious calls to string compare:
111 // 'if (strcmp())' -> 'if (strcmp() != 0)'
112 Finder->addMatcher(
113 stmt(anyOf(mapAnyOf(ifStmt, whileStmt, doStmt, forStmt)
114 .with(hasCondition(StringCompareCallExpr)),
115 binaryOperator(hasAnyOperatorName("&&", "||"),
116 hasEitherOperand(StringCompareCallExpr))))
117 .bind("missing-comparison"),
118 this);
119 }
120
121 if (WarnOnLogicalNotComparison) {
122 // Detect suspicious calls to string compared with '!' operator:
123 // 'if (!strcmp())' -> 'if (strcmp() == 0)'
124 Finder->addMatcher(unaryOperator(hasOperatorName("!"),
125 hasUnaryOperand(ignoringParenImpCasts(
126 StringCompareCallExpr)))
127 .bind("logical-not-comparison"),
128 this);
129 }
130
131 // Detect suspicious cast to an inconsistent type (i.e. not integer type).
132 Finder->addMatcher(
133 traverse(TK_AsIs,
134 implicitCastExpr(unless(hasType(isInteger())),
135 hasSourceExpression(StringCompareCallExpr))
136 .bind("invalid-conversion")),
137 this);
138
139 // Detect suspicious operator with string compare function as operand.
140 Finder->addMatcher(
141 binaryOperator(unless(anyOf(isComparisonOperator(), hasOperatorName("&&"),
142 hasOperatorName("||"), hasOperatorName("="))),
143 hasEitherOperand(StringCompareCallExpr))
144 .bind("suspicious-operator"),
145 this);
146
147 // Detect comparison to invalid constant: 'strcmp() == -1'.
148 const auto InvalidLiteral = ignoringParenImpCasts(
149 anyOf(integerLiteral(unless(equals(0))),
150 unaryOperator(
151 hasOperatorName("-"),
152 has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))),
153 characterLiteral(), cxxBoolLiteral()));
154
155 Finder->addMatcher(
156 binaryOperator(isComparisonOperator(),
157 hasOperands(StringCompareCallExpr, InvalidLiteral))
158 .bind("invalid-comparison"),
159 this);
160 }
161
check(const MatchFinder::MatchResult & Result)162 void SuspiciousStringCompareCheck::check(
163 const MatchFinder::MatchResult &Result) {
164 const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl");
165 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
166 assert(Decl != nullptr && Call != nullptr);
167
168 if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) {
169 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
170 Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
171 getLangOpts());
172
173 diag(Call->getBeginLoc(),
174 "function %0 is called without explicitly comparing result")
175 << Decl << FixItHint::CreateInsertion(EndLoc, " != 0");
176 }
177
178 if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) {
179 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
180 Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
181 getLangOpts());
182 SourceLocation NotLoc = E->getBeginLoc();
183
184 diag(Call->getBeginLoc(),
185 "function %0 is compared using logical not operator")
186 << Decl
187 << FixItHint::CreateRemoval(
188 CharSourceRange::getTokenRange(NotLoc, NotLoc))
189 << FixItHint::CreateInsertion(EndLoc, " == 0");
190 }
191
192 if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) {
193 diag(Call->getBeginLoc(),
194 "function %0 is compared to a suspicious constant")
195 << Decl;
196 }
197
198 if (const auto *BinOp =
199 Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) {
200 diag(Call->getBeginLoc(), "results of function %0 used by operator '%1'")
201 << Decl << BinOp->getOpcodeStr();
202 }
203
204 if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) {
205 diag(Call->getBeginLoc(), "function %0 has suspicious implicit cast")
206 << Decl;
207 }
208 }
209
210 } // namespace bugprone
211 } // namespace tidy
212 } // namespace clang
213