brintos

brintos / llvm-project-archived public Read only

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