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 69 SuspiciousStringCompareCheck::SuspiciousStringCompareCheck( 70 StringRef Name, ClangTidyContext *Context) 71 : ClangTidyCheck(Name, Context), 72 WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", 1)), 73 WarnOnLogicalNotComparison(Options.get("WarnOnLogicalNotComparison", 0)), 74 StringCompareLikeFunctions( 75 Options.get("StringCompareLikeFunctions", "")) {} 76 77 void SuspiciousStringCompareCheck::storeOptions( 78 ClangTidyOptions::OptionMap &Opts) { 79 Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison); 80 Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison); 81 Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions); 82 } 83 84 void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) { 85 // Match relational operators. 86 const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!")); 87 const auto ComparisonBinaryOperator = 88 binaryOperator(matchers::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( 121 anyOf(hasOperatorName("&&"), hasOperatorName("||")), 122 hasEitherOperand(StringCompareCallExpr)))) 123 .bind("missing-comparison"), 124 this); 125 } 126 127 if (WarnOnLogicalNotComparison) { 128 // Detect suspicious calls to string compared with '!' operator: 129 // 'if (!strcmp())' -> 'if (strcmp() == 0)' 130 Finder->addMatcher(unaryOperator(hasOperatorName("!"), 131 hasUnaryOperand(ignoringParenImpCasts( 132 StringCompareCallExpr))) 133 .bind("logical-not-comparison"), 134 this); 135 } 136 137 // Detect suspicious cast to an inconsistant type (i.e. not integer type). 138 Finder->addMatcher( 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( 147 unless(anyOf(matchers::isComparisonOperator(), hasOperatorName("&&"), 148 hasOperatorName("||"), hasOperatorName("="))), 149 hasEitherOperand(StringCompareCallExpr)) 150 .bind("suspicious-operator"), 151 this); 152 153 // Detect comparison to invalid constant: 'strcmp() == -1'. 154 const auto InvalidLiteral = ignoringParenImpCasts( 155 anyOf(integerLiteral(unless(equals(0))), 156 unaryOperator( 157 hasOperatorName("-"), 158 has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))), 159 characterLiteral(), cxxBoolLiteral())); 160 161 Finder->addMatcher(binaryOperator(matchers::isComparisonOperator(), 162 hasEitherOperand(StringCompareCallExpr), 163 hasEitherOperand(InvalidLiteral)) 164 .bind("invalid-comparison"), 165 this); 166 } 167 168 void SuspiciousStringCompareCheck::check( 169 const MatchFinder::MatchResult &Result) { 170 const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl"); 171 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call"); 172 assert(Decl != nullptr && Call != nullptr); 173 174 if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) { 175 SourceLocation EndLoc = Lexer::getLocForEndOfToken( 176 Call->getRParenLoc(), 0, Result.Context->getSourceManager(), 177 getLangOpts()); 178 179 diag(Call->getBeginLoc(), 180 "function %0 is called without explicitly comparing result") 181 << Decl << FixItHint::CreateInsertion(EndLoc, " != 0"); 182 } 183 184 if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) { 185 SourceLocation EndLoc = Lexer::getLocForEndOfToken( 186 Call->getRParenLoc(), 0, Result.Context->getSourceManager(), 187 getLangOpts()); 188 SourceLocation NotLoc = E->getBeginLoc(); 189 190 diag(Call->getBeginLoc(), 191 "function %0 is compared using logical not operator") 192 << Decl 193 << 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->getBeginLoc(), 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->getBeginLoc(), "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->getBeginLoc(), "function %0 has suspicious implicit cast") 212 << Decl; 213 } 214 } 215 216 } // namespace bugprone 217 } // namespace tidy 218 } // namespace clang 219