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