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