reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
//===--- SuspiciousMemsetUsageCheck.cpp - clang-tidy-----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "SuspiciousMemsetUsageCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/FixIt.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace bugprone {

void SuspiciousMemsetUsageCheck::registerMatchers(MatchFinder *Finder) {
  // Note: void *memset(void *buffer, int fill_char, size_t byte_count);
  // Look for memset(x, '0', z). Probably memset(x, 0, z) was intended.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(hasName("::memset"))),
          hasArgument(1, characterLiteral(equals(static_cast<unsigned>('0')))
                             .bind("char-zero-fill")),
          unless(
              eachOf(hasArgument(0, anyOf(hasType(pointsTo(isAnyCharacter())),
                                          hasType(arrayType(hasElementType(
                                              isAnyCharacter()))))),
                     isInTemplateInstantiation()))),
      this);

  // Look for memset with an integer literal in its fill_char argument.
  // Will check if it gets truncated.
  Finder->addMatcher(callExpr(callee(functionDecl(hasName("::memset"))),
                              hasArgument(1, integerLiteral().bind("num-fill")),
                              unless(isInTemplateInstantiation())),
                     this);

  // Look for memset(x, y, 0) as that is most likely an argument swap.
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasName("::memset"))),
               unless(hasArgument(1, anyOf(characterLiteral(equals(
                                               static_cast<unsigned>('0'))),
                                           integerLiteral()))),
               unless(isInTemplateInstantiation()))
          .bind("call"),
      this);
}

void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) {
  if (const auto *CharZeroFill =
          Result.Nodes.getNodeAs<CharacterLiteral>("char-zero-fill")) {
    // Case 1: fill_char of memset() is a character '0'. Probably an
    // integer zero was intended.

    SourceRange CharRange = CharZeroFill->getSourceRange();
    auto Diag =
        diag(CharZeroFill->getBeginLoc(), "memset fill value is char '0', "
                                          "potentially mistaken for int 0");

    // Only suggest a fix if no macros are involved.
    if (CharRange.getBegin().isMacroID())
      return;
    Diag << FixItHint::CreateReplacement(
        CharSourceRange::getTokenRange(CharRange), "0");
  }

  else if (const auto *NumFill =
               Result.Nodes.getNodeAs<IntegerLiteral>("num-fill")) {
    // Case 2: fill_char of memset() is larger in size than an unsigned char
    // so it gets truncated during conversion.

    const auto UCharMax = (1 << Result.Context->getCharWidth()) - 1;
    Expr::EvalResult EVResult;
    if (!NumFill->EvaluateAsInt(EVResult, *Result.Context))
      return;

    llvm::APSInt NumValue = EVResult.Val.getInt();
    if (NumValue >= 0 && NumValue <= UCharMax)
      return;

    diag(NumFill->getBeginLoc(), "memset fill value is out of unsigned "
                                 "character range, gets truncated");
  }

  else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call")) {
    // Case 3: byte_count of memset() is zero. This is most likely an
    // argument swap.

    const Expr *FillChar = Call->getArg(1);
    const Expr *ByteCount = Call->getArg(2);

    // Return if `byte_count` is not zero at compile time.
    Expr::EvalResult Value2;
    if (ByteCount->isValueDependent() ||
        !ByteCount->EvaluateAsInt(Value2, *Result.Context) ||
        Value2.Val.getInt() != 0)
      return;

    // Return if `fill_char` is known to be zero or negative at compile
    // time. In these cases, swapping the args would be a nop, or
    // introduce a definite bug. The code is likely correct.
    Expr::EvalResult EVResult;
    if (!FillChar->isValueDependent() &&
        FillChar->EvaluateAsInt(EVResult, *Result.Context)) {
      llvm::APSInt Value1 = EVResult.Val.getInt();
      if (Value1 == 0 || Value1.isNegative())
        return;
    }

    // `byte_count` is known to be zero at compile time, and `fill_char` is
    // either not known or known to be a positive integer. Emit a warning
    // and fix-its to swap the arguments.
    auto D = diag(Call->getBeginLoc(),
                  "memset of size zero, potentially swapped arguments");
    StringRef RHSString = tooling::fixit::getText(*ByteCount, *Result.Context);
    StringRef LHSString = tooling::fixit::getText(*FillChar, *Result.Context);
    if (LHSString.empty() || RHSString.empty())
      return;

    D << tooling::fixit::createReplacement(*FillChar, RHSString)
      << tooling::fixit::createReplacement(*ByteCount, LHSString);
  }
}

} // namespace bugprone
} // namespace tidy
} // namespace clang