StringConstructorCheck.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. //===--- StringConstructorCheck.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 "StringConstructorCheck.h"
  9. #include "../utils/OptionsUtils.h"
  10. #include "clang/AST/ASTContext.h"
  11. #include "clang/ASTMatchers/ASTMatchFinder.h"
  12. #include "clang/Tooling/FixIt.h"
  13. using namespace clang::ast_matchers;
  14. namespace clang::tidy::bugprone {
  15. namespace {
  16. AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) {
  17. return Node.getValue().getZExtValue() > N;
  18. }
  19. const char DefaultStringNames[] =
  20. "::std::basic_string;::std::basic_string_view";
  21. static std::vector<StringRef>
  22. removeNamespaces(const std::vector<StringRef> &Names) {
  23. std::vector<StringRef> Result;
  24. Result.reserve(Names.size());
  25. for (StringRef Name : Names) {
  26. std::string::size_type ColonPos = Name.rfind(':');
  27. Result.push_back(
  28. Name.substr(ColonPos == std::string::npos ? 0 : ColonPos + 1));
  29. }
  30. return Result;
  31. }
  32. } // namespace
  33. StringConstructorCheck::StringConstructorCheck(StringRef Name,
  34. ClangTidyContext *Context)
  35. : ClangTidyCheck(Name, Context),
  36. IsStringviewNullptrCheckEnabled(
  37. Context->isCheckEnabled("bugprone-stringview-nullptr")),
  38. WarnOnLargeLength(Options.get("WarnOnLargeLength", true)),
  39. LargeLengthThreshold(Options.get("LargeLengthThreshold", 0x800000)),
  40. StringNames(utils::options::parseStringList(
  41. Options.get("StringNames", DefaultStringNames))) {}
  42. void StringConstructorCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  43. Options.store(Opts, "WarnOnLargeLength", WarnOnLargeLength);
  44. Options.store(Opts, "LargeLengthThreshold", LargeLengthThreshold);
  45. Options.store(Opts, "StringNames", DefaultStringNames);
  46. }
  47. void StringConstructorCheck::registerMatchers(MatchFinder *Finder) {
  48. const auto ZeroExpr = expr(ignoringParenImpCasts(integerLiteral(equals(0))));
  49. const auto CharExpr = expr(ignoringParenImpCasts(characterLiteral()));
  50. const auto NegativeExpr = expr(ignoringParenImpCasts(
  51. unaryOperator(hasOperatorName("-"),
  52. hasUnaryOperand(integerLiteral(unless(equals(0)))))));
  53. const auto LargeLengthExpr = expr(ignoringParenImpCasts(
  54. integerLiteral(isBiggerThan(LargeLengthThreshold))));
  55. const auto CharPtrType = type(anyOf(pointerType(), arrayType()));
  56. // Match a string-literal; even through a declaration with initializer.
  57. const auto BoundStringLiteral = stringLiteral().bind("str");
  58. const auto ConstStrLiteralDecl = varDecl(
  59. isDefinition(), hasType(constantArrayType()), hasType(isConstQualified()),
  60. hasInitializer(ignoringParenImpCasts(BoundStringLiteral)));
  61. const auto ConstPtrStrLiteralDecl = varDecl(
  62. isDefinition(),
  63. hasType(pointerType(pointee(isAnyCharacter(), isConstQualified()))),
  64. hasInitializer(ignoringParenImpCasts(BoundStringLiteral)));
  65. const auto ConstStrLiteral = expr(ignoringParenImpCasts(anyOf(
  66. BoundStringLiteral, declRefExpr(hasDeclaration(anyOf(
  67. ConstPtrStrLiteralDecl, ConstStrLiteralDecl))))));
  68. // Check the fill constructor. Fills the string with n consecutive copies of
  69. // character c. [i.e string(size_t n, char c);].
  70. Finder->addMatcher(
  71. cxxConstructExpr(
  72. hasDeclaration(cxxMethodDecl(hasName("basic_string"))),
  73. hasArgument(0, hasType(qualType(isInteger()))),
  74. hasArgument(1, hasType(qualType(isInteger()))),
  75. anyOf(
  76. // Detect the expression: string('x', 40);
  77. hasArgument(0, CharExpr.bind("swapped-parameter")),
  78. // Detect the expression: string(0, ...);
  79. hasArgument(0, ZeroExpr.bind("empty-string")),
  80. // Detect the expression: string(-4, ...);
  81. hasArgument(0, NegativeExpr.bind("negative-length")),
  82. // Detect the expression: string(0x1234567, ...);
  83. hasArgument(0, LargeLengthExpr.bind("large-length"))))
  84. .bind("constructor"),
  85. this);
  86. // Check the literal string constructor with char pointer and length
  87. // parameters. [i.e. string (const char* s, size_t n);]
  88. Finder->addMatcher(
  89. cxxConstructExpr(
  90. hasDeclaration(cxxConstructorDecl(ofClass(
  91. cxxRecordDecl(hasAnyName(removeNamespaces(StringNames)))))),
  92. hasArgument(0, hasType(CharPtrType)),
  93. hasArgument(1, hasType(isInteger())),
  94. anyOf(
  95. // Detect the expression: string("...", 0);
  96. hasArgument(1, ZeroExpr.bind("empty-string")),
  97. // Detect the expression: string("...", -4);
  98. hasArgument(1, NegativeExpr.bind("negative-length")),
  99. // Detect the expression: string("lit", 0x1234567);
  100. hasArgument(1, LargeLengthExpr.bind("large-length")),
  101. // Detect the expression: string("lit", 5)
  102. allOf(hasArgument(0, ConstStrLiteral.bind("literal-with-length")),
  103. hasArgument(1, ignoringParenImpCasts(
  104. integerLiteral().bind("int"))))))
  105. .bind("constructor"),
  106. this);
  107. // Check the literal string constructor with char pointer.
  108. // [i.e. string (const char* s);]
  109. Finder->addMatcher(
  110. traverse(
  111. TK_AsIs,
  112. cxxConstructExpr(
  113. hasDeclaration(cxxConstructorDecl(ofClass(anyOf(
  114. cxxRecordDecl(hasName("basic_string_view"))
  115. .bind("basic_string_view_decl"),
  116. cxxRecordDecl(hasAnyName(removeNamespaces(StringNames))))))),
  117. hasArgument(0, expr().bind("from-ptr")),
  118. // do not match std::string(ptr, int)
  119. // match std::string(ptr, alloc)
  120. // match std::string(ptr)
  121. anyOf(hasArgument(1, unless(hasType(isInteger()))),
  122. argumentCountIs(1)))
  123. .bind("constructor")),
  124. this);
  125. }
  126. void StringConstructorCheck::check(const MatchFinder::MatchResult &Result) {
  127. const ASTContext &Ctx = *Result.Context;
  128. const auto *E = Result.Nodes.getNodeAs<CXXConstructExpr>("constructor");
  129. assert(E && "missing constructor expression");
  130. SourceLocation Loc = E->getBeginLoc();
  131. if (Result.Nodes.getNodeAs<Expr>("swapped-parameter")) {
  132. const Expr *P0 = E->getArg(0);
  133. const Expr *P1 = E->getArg(1);
  134. diag(Loc, "string constructor parameters are probably swapped;"
  135. " expecting string(count, character)")
  136. << tooling::fixit::createReplacement(*P0, *P1, Ctx)
  137. << tooling::fixit::createReplacement(*P1, *P0, Ctx);
  138. } else if (Result.Nodes.getNodeAs<Expr>("empty-string")) {
  139. diag(Loc, "constructor creating an empty string");
  140. } else if (Result.Nodes.getNodeAs<Expr>("negative-length")) {
  141. diag(Loc, "negative value used as length parameter");
  142. } else if (Result.Nodes.getNodeAs<Expr>("large-length")) {
  143. if (WarnOnLargeLength)
  144. diag(Loc, "suspicious large length parameter");
  145. } else if (Result.Nodes.getNodeAs<Expr>("literal-with-length")) {
  146. const auto *Str = Result.Nodes.getNodeAs<StringLiteral>("str");
  147. const auto *Lit = Result.Nodes.getNodeAs<IntegerLiteral>("int");
  148. if (Lit->getValue().ugt(Str->getLength())) {
  149. diag(Loc, "length is bigger than string literal size");
  150. }
  151. } else if (const auto *Ptr = Result.Nodes.getNodeAs<Expr>("from-ptr")) {
  152. Expr::EvalResult ConstPtr;
  153. if (!Ptr->isInstantiationDependent() &&
  154. Ptr->EvaluateAsRValue(ConstPtr, Ctx) &&
  155. ((ConstPtr.Val.isInt() && ConstPtr.Val.getInt().isZero()) ||
  156. (ConstPtr.Val.isLValue() && ConstPtr.Val.isNullPointer()))) {
  157. if (IsStringviewNullptrCheckEnabled &&
  158. Result.Nodes.getNodeAs<CXXRecordDecl>("basic_string_view_decl")) {
  159. // Filter out `basic_string_view` to avoid conflicts with
  160. // `bugprone-stringview-nullptr`
  161. return;
  162. }
  163. diag(Loc, "constructing string from nullptr is undefined behaviour");
  164. }
  165. }
  166. }
  167. } // namespace clang::tidy::bugprone