SuspiciousMemsetUsageCheck.cpp 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. //===--- SuspiciousMemsetUsageCheck.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 "SuspiciousMemsetUsageCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/ASTMatchers/ASTMatchers.h"
  12. #include "clang/Lex/Lexer.h"
  13. #include "clang/Tooling/FixIt.h"
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::bugprone {
  16. void SuspiciousMemsetUsageCheck::registerMatchers(MatchFinder *Finder) {
  17. // Match the standard memset:
  18. // void *memset(void *buffer, int fill_char, size_t byte_count);
  19. auto MemsetDecl =
  20. functionDecl(hasName("::memset"),
  21. parameterCountIs(3),
  22. hasParameter(0, hasType(pointerType(pointee(voidType())))),
  23. hasParameter(1, hasType(isInteger())),
  24. hasParameter(2, hasType(isInteger())));
  25. // Look for memset(x, '0', z). Probably memset(x, 0, z) was intended.
  26. Finder->addMatcher(
  27. callExpr(
  28. callee(MemsetDecl), argumentCountIs(3),
  29. hasArgument(1, characterLiteral(equals(static_cast<unsigned>('0')))
  30. .bind("char-zero-fill")),
  31. unless(hasArgument(
  32. 0, anyOf(hasType(pointsTo(isAnyCharacter())),
  33. hasType(arrayType(hasElementType(isAnyCharacter()))))))),
  34. this);
  35. // Look for memset with an integer literal in its fill_char argument.
  36. // Will check if it gets truncated.
  37. Finder->addMatcher(
  38. callExpr(callee(MemsetDecl), argumentCountIs(3),
  39. hasArgument(1, integerLiteral().bind("num-fill"))),
  40. this);
  41. // Look for memset(x, y, 0) as that is most likely an argument swap.
  42. Finder->addMatcher(
  43. callExpr(callee(MemsetDecl), argumentCountIs(3),
  44. unless(hasArgument(1, anyOf(characterLiteral(equals(
  45. static_cast<unsigned>('0'))),
  46. integerLiteral()))))
  47. .bind("call"),
  48. this);
  49. }
  50. void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) {
  51. if (const auto *CharZeroFill =
  52. Result.Nodes.getNodeAs<CharacterLiteral>("char-zero-fill")) {
  53. // Case 1: fill_char of memset() is a character '0'. Probably an
  54. // integer zero was intended.
  55. SourceRange CharRange = CharZeroFill->getSourceRange();
  56. auto Diag =
  57. diag(CharZeroFill->getBeginLoc(), "memset fill value is char '0', "
  58. "potentially mistaken for int 0");
  59. // Only suggest a fix if no macros are involved.
  60. if (CharRange.getBegin().isMacroID())
  61. return;
  62. Diag << FixItHint::CreateReplacement(
  63. CharSourceRange::getTokenRange(CharRange), "0");
  64. }
  65. else if (const auto *NumFill =
  66. Result.Nodes.getNodeAs<IntegerLiteral>("num-fill")) {
  67. // Case 2: fill_char of memset() is larger in size than an unsigned char
  68. // so it gets truncated during conversion.
  69. const auto UCharMax = (1 << Result.Context->getCharWidth()) - 1;
  70. Expr::EvalResult EVResult;
  71. if (!NumFill->EvaluateAsInt(EVResult, *Result.Context))
  72. return;
  73. llvm::APSInt NumValue = EVResult.Val.getInt();
  74. if (NumValue >= 0 && NumValue <= UCharMax)
  75. return;
  76. diag(NumFill->getBeginLoc(), "memset fill value is out of unsigned "
  77. "character range, gets truncated");
  78. }
  79. else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call")) {
  80. // Case 3: byte_count of memset() is zero. This is most likely an
  81. // argument swap.
  82. const Expr *FillChar = Call->getArg(1);
  83. const Expr *ByteCount = Call->getArg(2);
  84. // Return if `byte_count` is not zero at compile time.
  85. Expr::EvalResult Value2;
  86. if (ByteCount->isValueDependent() ||
  87. !ByteCount->EvaluateAsInt(Value2, *Result.Context) ||
  88. Value2.Val.getInt() != 0)
  89. return;
  90. // Return if `fill_char` is known to be zero or negative at compile
  91. // time. In these cases, swapping the args would be a nop, or
  92. // introduce a definite bug. The code is likely correct.
  93. Expr::EvalResult EVResult;
  94. if (!FillChar->isValueDependent() &&
  95. FillChar->EvaluateAsInt(EVResult, *Result.Context)) {
  96. llvm::APSInt Value1 = EVResult.Val.getInt();
  97. if (Value1 == 0 || Value1.isNegative())
  98. return;
  99. }
  100. // `byte_count` is known to be zero at compile time, and `fill_char` is
  101. // either not known or known to be a positive integer. Emit a warning
  102. // and fix-its to swap the arguments.
  103. auto D = diag(Call->getBeginLoc(),
  104. "memset of size zero, potentially swapped arguments");
  105. StringRef RHSString = tooling::fixit::getText(*ByteCount, *Result.Context);
  106. StringRef LHSString = tooling::fixit::getText(*FillChar, *Result.Context);
  107. if (LHSString.empty() || RHSString.empty())
  108. return;
  109. D << tooling::fixit::createReplacement(*FillChar, RHSString)
  110. << tooling::fixit::createReplacement(*ByteCount, LHSString);
  111. }
  112. }
  113. } // namespace clang::tidy::bugprone