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