SuspiciousStringCompareCheck.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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. #include "SuspiciousStringCompareCheck.h"
  9. #include "../utils/Matchers.h"
  10. #include "../utils/OptionsUtils.h"
  11. #include "clang/AST/ASTContext.h"
  12. #include "clang/ASTMatchers/ASTMatchFinder.h"
  13. #include "clang/Lex/Lexer.h"
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::bugprone {
  16. // Semicolon separated list of known string compare-like functions. The list
  17. // must ends with a semicolon.
  18. static const char KnownStringCompareFunctions[] = "__builtin_memcmp;"
  19. "__builtin_strcasecmp;"
  20. "__builtin_strcmp;"
  21. "__builtin_strncasecmp;"
  22. "__builtin_strncmp;"
  23. "_mbscmp;"
  24. "_mbscmp_l;"
  25. "_mbsicmp;"
  26. "_mbsicmp_l;"
  27. "_mbsnbcmp;"
  28. "_mbsnbcmp_l;"
  29. "_mbsnbicmp;"
  30. "_mbsnbicmp_l;"
  31. "_mbsncmp;"
  32. "_mbsncmp_l;"
  33. "_mbsnicmp;"
  34. "_mbsnicmp_l;"
  35. "_memicmp;"
  36. "_memicmp_l;"
  37. "_stricmp;"
  38. "_stricmp_l;"
  39. "_strnicmp;"
  40. "_strnicmp_l;"
  41. "_wcsicmp;"
  42. "_wcsicmp_l;"
  43. "_wcsnicmp;"
  44. "_wcsnicmp_l;"
  45. "lstrcmp;"
  46. "lstrcmpi;"
  47. "memcmp;"
  48. "memicmp;"
  49. "strcasecmp;"
  50. "strcmp;"
  51. "strcmpi;"
  52. "stricmp;"
  53. "strncasecmp;"
  54. "strncmp;"
  55. "strnicmp;"
  56. "wcscasecmp;"
  57. "wcscmp;"
  58. "wcsicmp;"
  59. "wcsncmp;"
  60. "wcsnicmp;"
  61. "wmemcmp;";
  62. SuspiciousStringCompareCheck::SuspiciousStringCompareCheck(
  63. StringRef Name, ClangTidyContext *Context)
  64. : ClangTidyCheck(Name, Context),
  65. WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", true)),
  66. WarnOnLogicalNotComparison(
  67. Options.get("WarnOnLogicalNotComparison", false)),
  68. StringCompareLikeFunctions(
  69. Options.get("StringCompareLikeFunctions", "")) {}
  70. void SuspiciousStringCompareCheck::storeOptions(
  71. ClangTidyOptions::OptionMap &Opts) {
  72. Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison);
  73. Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison);
  74. Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions);
  75. }
  76. void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) {
  77. // Match relational operators.
  78. const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!"));
  79. const auto ComparisonBinaryOperator = binaryOperator(isComparisonOperator());
  80. const auto ComparisonOperator =
  81. expr(anyOf(ComparisonUnaryOperator, ComparisonBinaryOperator));
  82. // Add the list of known string compare-like functions and add user-defined
  83. // functions.
  84. std::vector<StringRef> FunctionNames = utils::options::parseListPair(
  85. KnownStringCompareFunctions, StringCompareLikeFunctions);
  86. // Match a call to a string compare functions.
  87. const auto FunctionCompareDecl =
  88. functionDecl(hasAnyName(FunctionNames)).bind("decl");
  89. const auto DirectStringCompareCallExpr =
  90. callExpr(hasDeclaration(FunctionCompareDecl)).bind("call");
  91. const auto MacroStringCompareCallExpr = conditionalOperator(anyOf(
  92. hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)),
  93. hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr))));
  94. // The implicit cast is not present in C.
  95. const auto StringCompareCallExpr = ignoringParenImpCasts(
  96. anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr));
  97. if (WarnOnImplicitComparison) {
  98. // Detect suspicious calls to string compare:
  99. // 'if (strcmp())' -> 'if (strcmp() != 0)'
  100. Finder->addMatcher(
  101. stmt(anyOf(mapAnyOf(ifStmt, whileStmt, doStmt, forStmt)
  102. .with(hasCondition(StringCompareCallExpr)),
  103. binaryOperator(hasAnyOperatorName("&&", "||"),
  104. hasEitherOperand(StringCompareCallExpr))))
  105. .bind("missing-comparison"),
  106. this);
  107. }
  108. if (WarnOnLogicalNotComparison) {
  109. // Detect suspicious calls to string compared with '!' operator:
  110. // 'if (!strcmp())' -> 'if (strcmp() == 0)'
  111. Finder->addMatcher(unaryOperator(hasOperatorName("!"),
  112. hasUnaryOperand(ignoringParenImpCasts(
  113. StringCompareCallExpr)))
  114. .bind("logical-not-comparison"),
  115. this);
  116. }
  117. // Detect suspicious cast to an inconsistent type (i.e. not integer type).
  118. Finder->addMatcher(
  119. traverse(TK_AsIs,
  120. implicitCastExpr(unless(hasType(isInteger())),
  121. hasSourceExpression(StringCompareCallExpr))
  122. .bind("invalid-conversion")),
  123. this);
  124. // Detect suspicious operator with string compare function as operand.
  125. Finder->addMatcher(
  126. binaryOperator(unless(anyOf(isComparisonOperator(), hasOperatorName("&&"),
  127. hasOperatorName("||"), hasOperatorName("="))),
  128. hasEitherOperand(StringCompareCallExpr))
  129. .bind("suspicious-operator"),
  130. this);
  131. // Detect comparison to invalid constant: 'strcmp() == -1'.
  132. const auto InvalidLiteral = ignoringParenImpCasts(
  133. anyOf(integerLiteral(unless(equals(0))),
  134. unaryOperator(
  135. hasOperatorName("-"),
  136. has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))),
  137. characterLiteral(), cxxBoolLiteral()));
  138. Finder->addMatcher(
  139. binaryOperator(isComparisonOperator(),
  140. hasOperands(StringCompareCallExpr, InvalidLiteral))
  141. .bind("invalid-comparison"),
  142. this);
  143. }
  144. void SuspiciousStringCompareCheck::check(
  145. const MatchFinder::MatchResult &Result) {
  146. const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl");
  147. const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
  148. assert(Decl != nullptr && Call != nullptr);
  149. if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) {
  150. SourceLocation EndLoc = Lexer::getLocForEndOfToken(
  151. Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
  152. getLangOpts());
  153. diag(Call->getBeginLoc(),
  154. "function %0 is called without explicitly comparing result")
  155. << Decl << FixItHint::CreateInsertion(EndLoc, " != 0");
  156. }
  157. if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) {
  158. SourceLocation EndLoc = Lexer::getLocForEndOfToken(
  159. Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
  160. getLangOpts());
  161. SourceLocation NotLoc = E->getBeginLoc();
  162. diag(Call->getBeginLoc(),
  163. "function %0 is compared using logical not operator")
  164. << Decl
  165. << FixItHint::CreateRemoval(
  166. CharSourceRange::getTokenRange(NotLoc, NotLoc))
  167. << FixItHint::CreateInsertion(EndLoc, " == 0");
  168. }
  169. if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) {
  170. diag(Call->getBeginLoc(),
  171. "function %0 is compared to a suspicious constant")
  172. << Decl;
  173. }
  174. if (const auto *BinOp =
  175. Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) {
  176. diag(Call->getBeginLoc(), "results of function %0 used by operator '%1'")
  177. << Decl << BinOp->getOpcodeStr();
  178. }
  179. if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) {
  180. diag(Call->getBeginLoc(), "function %0 has suspicious implicit cast")
  181. << Decl;
  182. }
  183. }
  184. } // namespace clang::tidy::bugprone