1013 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 "NotNullTerminatedResultCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Frontend/CompilerInstance.h"13#include "clang/Lex/Lexer.h"14#include "clang/Lex/PPCallbacks.h"15#include "clang/Lex/Preprocessor.h"16#include <optional>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::bugprone {21 22constexpr llvm::StringLiteral FunctionExprName = "FunctionExpr";23constexpr llvm::StringLiteral CastExprName = "CastExpr";24constexpr llvm::StringLiteral UnknownDestName = "UnknownDest";25constexpr llvm::StringLiteral DestArrayTyName = "DestArrayTy";26constexpr llvm::StringLiteral DestVarDeclName = "DestVarDecl";27constexpr llvm::StringLiteral DestMallocExprName = "DestMalloc";28constexpr llvm::StringLiteral DestExprName = "DestExpr";29constexpr llvm::StringLiteral SrcVarDeclName = "SrcVarDecl";30constexpr llvm::StringLiteral SrcExprName = "SrcExpr";31constexpr llvm::StringLiteral LengthExprName = "LengthExpr";32constexpr llvm::StringLiteral WrongLengthExprName = "WrongLength";33constexpr llvm::StringLiteral UnknownLengthName = "UnknownLength";34 35enum class LengthHandleKind { Increase, Decrease };36 37namespace {38static Preprocessor *PP;39} // namespace40 41// Returns the expression of destination's capacity which is part of a42// 'VariableArrayType', 'ConstantArrayTypeLoc' or an argument of a 'malloc()'43// family function call.44static const Expr *getDestCapacityExpr(const MatchFinder::MatchResult &Result) {45 if (const auto *DestMalloc = Result.Nodes.getNodeAs<Expr>(DestMallocExprName))46 return DestMalloc;47 48 if (const auto *DestVAT =49 Result.Nodes.getNodeAs<VariableArrayType>(DestArrayTyName))50 return DestVAT->getSizeExpr();51 52 if (const auto *DestVD = Result.Nodes.getNodeAs<VarDecl>(DestVarDeclName))53 if (const TypeLoc DestTL = DestVD->getTypeSourceInfo()->getTypeLoc())54 if (const auto DestCTL = DestTL.getAs<ConstantArrayTypeLoc>())55 return DestCTL.getSizeExpr();56 57 return nullptr;58}59 60// Returns the length of \p E as an 'IntegerLiteral' or a 'StringLiteral'61// without the null-terminator.62static unsigned getLength(const Expr *E,63 const MatchFinder::MatchResult &Result) {64 if (!E)65 return 0;66 67 E = E->IgnoreImpCasts();68 69 if (const auto *LengthDRE = dyn_cast<DeclRefExpr>(E))70 if (const auto *LengthVD = dyn_cast<VarDecl>(LengthDRE->getDecl()))71 if (!isa<ParmVarDecl>(LengthVD))72 if (const Expr *LengthInit = LengthVD->getInit();73 LengthInit && !LengthInit->isValueDependent()) {74 Expr::EvalResult Length;75 if (LengthInit->EvaluateAsInt(Length, *Result.Context))76 return Length.Val.getInt().getZExtValue();77 }78 79 if (const auto *LengthIL = dyn_cast<IntegerLiteral>(E))80 return LengthIL->getValue().getZExtValue();81 82 if (const auto *StrDRE = dyn_cast<DeclRefExpr>(E))83 if (const auto *StrVD = dyn_cast<VarDecl>(StrDRE->getDecl()))84 if (const Expr *StrInit = StrVD->getInit())85 if (const auto *StrSL =86 dyn_cast<StringLiteral>(StrInit->IgnoreImpCasts()))87 return StrSL->getLength();88 89 if (const auto *SrcSL = dyn_cast<StringLiteral>(E))90 return SrcSL->getLength();91 92 return 0;93}94 95// Returns the capacity of the destination array.96// For example in 'char dest[13]; memcpy(dest, ...)' it returns 13.97static int getDestCapacity(const MatchFinder::MatchResult &Result) {98 if (const auto *DestCapacityExpr = getDestCapacityExpr(Result))99 return getLength(DestCapacityExpr, Result);100 101 return 0;102}103 104// Returns the 'strlen()' if it is the given length.105static const CallExpr *getStrlenExpr(const MatchFinder::MatchResult &Result) {106 if (const auto *StrlenExpr =107 Result.Nodes.getNodeAs<CallExpr>(WrongLengthExprName))108 if (const Decl *D = StrlenExpr->getCalleeDecl())109 if (const FunctionDecl *FD = D->getAsFunction())110 if (const IdentifierInfo *II = FD->getIdentifier())111 if (II->isStr("strlen") || II->isStr("wcslen"))112 return StrlenExpr;113 114 return nullptr;115}116 117// Returns the length which is given in the memory/string handler function.118// For example in 'memcpy(dest, "foobar", 3)' it returns 3.119static int getGivenLength(const MatchFinder::MatchResult &Result) {120 if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))121 return 0;122 123 if (const int Length =124 getLength(Result.Nodes.getNodeAs<Expr>(WrongLengthExprName), Result))125 return Length;126 127 if (const int Length =128 getLength(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result))129 return Length;130 131 // Special case, for example 'strlen("foo")'.132 if (const CallExpr *StrlenCE = getStrlenExpr(Result))133 if (const Expr *Arg = StrlenCE->getArg(0)->IgnoreImpCasts())134 if (const int ArgLength = getLength(Arg, Result))135 return ArgLength;136 137 return 0;138}139 140// Returns a string representation of \p E.141static StringRef exprToStr(const Expr *E,142 const MatchFinder::MatchResult &Result) {143 if (!E)144 return "";145 146 return Lexer::getSourceText(147 CharSourceRange::getTokenRange(E->getSourceRange()),148 *Result.SourceManager, Result.Context->getLangOpts(), nullptr);149}150 151// Returns the proper token based end location of \p E.152static SourceLocation exprLocEnd(const Expr *E,153 const MatchFinder::MatchResult &Result) {154 return Lexer::getLocForEndOfToken(E->getEndLoc(), 0, *Result.SourceManager,155 Result.Context->getLangOpts());156}157 158//===----------------------------------------------------------------------===//159// Rewrite decision helper functions.160//===----------------------------------------------------------------------===//161 162// Increment by integer '1' can result in overflow if it is the maximal value.163// After that it would be extended to 'size_t' and its value would be wrong,164// therefore we have to inject '+ 1UL' instead.165static bool isInjectUL(const MatchFinder::MatchResult &Result) {166 return getGivenLength(Result) == std::numeric_limits<int>::max();167}168 169// If the capacity of the destination array is unknown it is denoted as unknown.170static bool isKnownDest(const MatchFinder::MatchResult &Result) {171 return !Result.Nodes.getNodeAs<Expr>(UnknownDestName);172}173 174// True if the capacity of the destination array is based on the given length,175// therefore we assume that it cannot overflow (e.g. 'malloc(given_length + 1)'176static bool isDestBasedOnGivenLength(const MatchFinder::MatchResult &Result) {177 const StringRef DestCapacityExprStr =178 exprToStr(getDestCapacityExpr(Result), Result).trim();179 const StringRef LengthExprStr =180 exprToStr(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result).trim();181 182 return !DestCapacityExprStr.empty() && !LengthExprStr.empty() &&183 DestCapacityExprStr.contains(LengthExprStr);184}185 186// Writing and reading from the same memory cannot remove the null-terminator.187static bool isDestAndSrcEquals(const MatchFinder::MatchResult &Result) {188 if (const auto *DestDRE = Result.Nodes.getNodeAs<DeclRefExpr>(DestExprName))189 if (const auto *SrcDRE = Result.Nodes.getNodeAs<DeclRefExpr>(SrcExprName))190 return DestDRE->getDecl()->getCanonicalDecl() ==191 SrcDRE->getDecl()->getCanonicalDecl();192 193 return false;194}195 196// For example 'std::string str = "foo"; memcpy(dst, str.data(), str.length())'.197static bool isStringDataAndLength(const MatchFinder::MatchResult &Result) {198 const auto *DestExpr =199 Result.Nodes.getNodeAs<CXXMemberCallExpr>(DestExprName);200 const auto *SrcExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>(SrcExprName);201 const auto *LengthExpr =202 Result.Nodes.getNodeAs<CXXMemberCallExpr>(WrongLengthExprName);203 204 StringRef DestStr = "", SrcStr = "", LengthStr = "";205 if (DestExpr)206 if (const CXXMethodDecl *DestMD = DestExpr->getMethodDecl())207 DestStr = DestMD->getName();208 209 if (SrcExpr)210 if (const CXXMethodDecl *SrcMD = SrcExpr->getMethodDecl())211 SrcStr = SrcMD->getName();212 213 if (LengthExpr)214 if (const CXXMethodDecl *LengthMD = LengthExpr->getMethodDecl())215 LengthStr = LengthMD->getName();216 217 return (LengthStr == "length" || LengthStr == "size") &&218 (SrcStr == "data" || DestStr == "data");219}220 221static bool222isGivenLengthEqualToSrcLength(const MatchFinder::MatchResult &Result) {223 if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))224 return false;225 226 if (isStringDataAndLength(Result))227 return true;228 229 const int GivenLength = getGivenLength(Result);230 const int SrcLength =231 getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);232 233 if (GivenLength != 0 && SrcLength != 0 && GivenLength == SrcLength)234 return true;235 236 if (const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName))237 if (isa<BinaryOperator>(LengthExpr->IgnoreParenImpCasts()))238 return false;239 240 // Check the strlen()'s argument's 'VarDecl' is equal to the source 'VarDecl'.241 if (const CallExpr *StrlenCE = getStrlenExpr(Result))242 if (const auto *ArgDRE =243 dyn_cast<DeclRefExpr>(StrlenCE->getArg(0)->IgnoreImpCasts()))244 if (const auto *SrcVD = Result.Nodes.getNodeAs<VarDecl>(SrcVarDeclName))245 return dyn_cast<VarDecl>(ArgDRE->getDecl()) == SrcVD;246 247 return false;248}249 250static bool isCorrectGivenLength(const MatchFinder::MatchResult &Result) {251 if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))252 return false;253 254 return !isGivenLengthEqualToSrcLength(Result);255}256 257// If we rewrite the function call we need to create extra space to hold the258// null terminator. The new necessary capacity overflows without that '+ 1'259// size and we need to correct the given capacity.260static bool isDestCapacityOverflows(const MatchFinder::MatchResult &Result) {261 if (!isKnownDest(Result))262 return true;263 264 const Expr *DestCapacityExpr = getDestCapacityExpr(Result);265 const int DestCapacity = getLength(DestCapacityExpr, Result);266 const int GivenLength = getGivenLength(Result);267 268 if (GivenLength != 0 && DestCapacity != 0)269 return isGivenLengthEqualToSrcLength(Result) && DestCapacity == GivenLength;270 271 // Assume that the destination array's capacity cannot overflow if the272 // expression of the memory allocation contains '+ 1'.273 const StringRef DestCapacityExprStr = exprToStr(DestCapacityExpr, Result);274 if (DestCapacityExprStr.contains("+1") || DestCapacityExprStr.contains("+ 1"))275 return false;276 277 return true;278}279 280static bool281isFixedGivenLengthAndUnknownSrc(const MatchFinder::MatchResult &Result) {282 if (Result.Nodes.getNodeAs<IntegerLiteral>(WrongLengthExprName))283 return !getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);284 285 return false;286}287 288//===----------------------------------------------------------------------===//289// Code injection functions.290//===----------------------------------------------------------------------===//291 292// Increase or decrease \p LengthExpr by one.293static void lengthExprHandle(const Expr *LengthExpr,294 LengthHandleKind LengthHandle,295 const MatchFinder::MatchResult &Result,296 DiagnosticBuilder &Diag) {297 LengthExpr = LengthExpr->IgnoreParenImpCasts();298 299 // See whether we work with a macro.300 bool IsMacroDefinition = false;301 const StringRef LengthExprStr = exprToStr(LengthExpr, Result);302 Preprocessor::macro_iterator It = PP->macro_begin();303 while (It != PP->macro_end() && !IsMacroDefinition) {304 if (It->first->getName() == LengthExprStr)305 IsMacroDefinition = true;306 307 ++It;308 }309 310 // Try to obtain an 'IntegerLiteral' and adjust it.311 if (!IsMacroDefinition) {312 if (const auto *LengthIL = dyn_cast<IntegerLiteral>(LengthExpr)) {313 const uint64_t NewLength =314 LengthIL->getValue().getZExtValue() +315 (LengthHandle == LengthHandleKind::Increase ? 1 : -1);316 317 const auto NewLengthFix = FixItHint::CreateReplacement(318 LengthIL->getSourceRange(),319 (Twine(NewLength) + (isInjectUL(Result) ? "UL" : "")).str());320 Diag << NewLengthFix;321 return;322 }323 }324 325 // Try to obtain and remove the '+ 1' string as a decrement fix.326 const auto *BO = dyn_cast<BinaryOperator>(LengthExpr);327 if (BO && BO->getOpcode() == BO_Add &&328 LengthHandle == LengthHandleKind::Decrease) {329 const Expr *LhsExpr = BO->getLHS()->IgnoreImpCasts();330 const Expr *RhsExpr = BO->getRHS()->IgnoreImpCasts();331 332 if (const auto *LhsIL = dyn_cast<IntegerLiteral>(LhsExpr)) {333 if (LhsIL->getValue().getZExtValue() == 1) {334 Diag << FixItHint::CreateRemoval(335 {LhsIL->getBeginLoc(),336 RhsExpr->getBeginLoc().getLocWithOffset(-1)});337 return;338 }339 }340 341 if (const auto *RhsIL = dyn_cast<IntegerLiteral>(RhsExpr)) {342 if (RhsIL->getValue().getZExtValue() == 1) {343 Diag << FixItHint::CreateRemoval(344 {LhsExpr->getEndLoc().getLocWithOffset(1), RhsIL->getEndLoc()});345 return;346 }347 }348 }349 350 // Try to inject the '+ 1'/'- 1' string.351 const bool NeedInnerParen = BO && BO->getOpcode() != BO_Add;352 353 if (NeedInnerParen)354 Diag << FixItHint::CreateInsertion(LengthExpr->getBeginLoc(), "(");355 356 SmallString<8> Injection;357 if (NeedInnerParen)358 Injection += ')';359 Injection += LengthHandle == LengthHandleKind::Increase ? " + 1" : " - 1";360 if (isInjectUL(Result))361 Injection += "UL";362 363 Diag << FixItHint::CreateInsertion(exprLocEnd(LengthExpr, Result), Injection);364}365 366static void lengthArgHandle(LengthHandleKind LengthHandle,367 const MatchFinder::MatchResult &Result,368 DiagnosticBuilder &Diag) {369 const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName);370 lengthExprHandle(LengthExpr, LengthHandle, Result, Diag);371}372 373static void lengthArgPosHandle(unsigned ArgPos, LengthHandleKind LengthHandle,374 const MatchFinder::MatchResult &Result,375 DiagnosticBuilder &Diag) {376 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);377 lengthExprHandle(FunctionExpr->getArg(ArgPos), LengthHandle, Result, Diag);378}379 380// The string handler functions are only operates with plain 'char'/'wchar_t'381// without 'unsigned/signed', therefore we need to cast it.382static bool isDestExprFix(const MatchFinder::MatchResult &Result,383 DiagnosticBuilder &Diag) {384 const auto *Dest = Result.Nodes.getNodeAs<Expr>(DestExprName);385 if (!Dest)386 return false;387 388 const std::string TempTyStr = Dest->getType().getAsString();389 const StringRef TyStr = TempTyStr;390 if (TyStr.starts_with("char") || TyStr.starts_with("wchar_t"))391 return false;392 393 Diag << FixItHint::CreateInsertion(Dest->getBeginLoc(), "(char *)");394 return true;395}396 397// If the destination array is the same length as the given length we have to398// increase the capacity by one to create space for the null terminator.399static bool isDestCapacityFix(const MatchFinder::MatchResult &Result,400 DiagnosticBuilder &Diag) {401 const bool IsOverflows = isDestCapacityOverflows(Result);402 if (IsOverflows)403 if (const Expr *CapacityExpr = getDestCapacityExpr(Result))404 lengthExprHandle(CapacityExpr, LengthHandleKind::Increase, Result, Diag);405 406 return IsOverflows;407}408 409static void removeArg(int ArgPos, const MatchFinder::MatchResult &Result,410 DiagnosticBuilder &Diag) {411 // This is the following structure: (src, '\0', strlen(src))412 // ArgToRemove: ~~~~~~~~~~~413 // LHSArg: ~~~~414 // RemoveArgFix: ~~~~~~~~~~~~~415 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);416 const Expr *ArgToRemove = FunctionExpr->getArg(ArgPos);417 const Expr *LHSArg = FunctionExpr->getArg(ArgPos - 1);418 const auto RemoveArgFix = FixItHint::CreateRemoval(419 SourceRange(exprLocEnd(LHSArg, Result),420 exprLocEnd(ArgToRemove, Result).getLocWithOffset(-1)));421 Diag << RemoveArgFix;422}423 424static void renameFunc(StringRef NewFuncName,425 const MatchFinder::MatchResult &Result,426 DiagnosticBuilder &Diag) {427 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);428 const int FuncNameLength =429 FunctionExpr->getDirectCallee()->getIdentifier()->getLength();430 const SourceRange FuncNameRange(431 FunctionExpr->getBeginLoc(),432 FunctionExpr->getBeginLoc().getLocWithOffset(FuncNameLength - 1));433 434 const auto FuncNameFix =435 FixItHint::CreateReplacement(FuncNameRange, NewFuncName);436 Diag << FuncNameFix;437}438 439static void renameMemcpy(StringRef Name, bool IsCopy, bool IsSafe,440 const MatchFinder::MatchResult &Result,441 DiagnosticBuilder &Diag) {442 SmallString<10> NewFuncName;443 NewFuncName = (Name[0] != 'w') ? "str" : "wcs";444 NewFuncName += IsCopy ? "cpy" : "ncpy";445 NewFuncName += IsSafe ? "_s" : "";446 renameFunc(NewFuncName, Result, Diag);447}448 449static void insertDestCapacityArg(bool IsOverflows, StringRef Name,450 const MatchFinder::MatchResult &Result,451 DiagnosticBuilder &Diag) {452 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);453 SmallString<64> NewSecondArg;454 455 if (const int DestLength = getDestCapacity(Result)) {456 NewSecondArg = Twine(IsOverflows ? DestLength + 1 : DestLength).str();457 } else {458 NewSecondArg =459 (Twine(exprToStr(getDestCapacityExpr(Result), Result)) +460 (IsOverflows ? (!isInjectUL(Result) ? " + 1" : " + 1UL") : ""))461 .str();462 }463 464 NewSecondArg += ", ";465 const auto InsertNewArgFix = FixItHint::CreateInsertion(466 FunctionExpr->getArg(1)->getBeginLoc(), NewSecondArg);467 Diag << InsertNewArgFix;468}469 470static void insertNullTerminatorExpr(StringRef Name,471 const MatchFinder::MatchResult &Result,472 DiagnosticBuilder &Diag) {473 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);474 const int FuncLocStartColumn = Result.SourceManager->getPresumedColumnNumber(475 FunctionExpr->getBeginLoc());476 const SourceRange SpaceRange(477 FunctionExpr->getBeginLoc().getLocWithOffset(-FuncLocStartColumn + 1),478 FunctionExpr->getBeginLoc());479 const StringRef SpaceBeforeStmtStr = Lexer::getSourceText(480 CharSourceRange::getCharRange(SpaceRange), *Result.SourceManager,481 Result.Context->getLangOpts(), nullptr);482 483 SmallString<128> NewAddNullTermExprStr;484 NewAddNullTermExprStr =485 (Twine('\n') + SpaceBeforeStmtStr +486 exprToStr(Result.Nodes.getNodeAs<Expr>(DestExprName), Result) + "[" +487 exprToStr(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result) +488 "] = " + ((Name[0] != 'w') ? R"('\0';)" : R"(L'\0';)"))489 .str();490 491 const auto AddNullTerminatorExprFix = FixItHint::CreateInsertion(492 exprLocEnd(FunctionExpr, Result).getLocWithOffset(1),493 NewAddNullTermExprStr);494 Diag << AddNullTerminatorExprFix;495}496 497//===----------------------------------------------------------------------===//498// Checker logic with the matchers.499//===----------------------------------------------------------------------===//500 501NotNullTerminatedResultCheck::NotNullTerminatedResultCheck(502 StringRef Name, ClangTidyContext *Context)503 : ClangTidyCheck(Name, Context),504 WantToUseSafeFunctions(Options.get("WantToUseSafeFunctions", true)) {}505 506void NotNullTerminatedResultCheck::storeOptions(507 ClangTidyOptions::OptionMap &Opts) {508 Options.store(Opts, "WantToUseSafeFunctions", WantToUseSafeFunctions);509}510 511void NotNullTerminatedResultCheck::registerPPCallbacks(512 const SourceManager &SM, Preprocessor *Pp, Preprocessor *ModuleExpanderPP) {513 PP = Pp;514}515 516namespace {517AST_MATCHER_P(Expr, hasDefinition, ast_matchers::internal::Matcher<Expr>,518 InnerMatcher) {519 const Expr *SimpleNode = &Node;520 SimpleNode = SimpleNode->IgnoreParenImpCasts();521 522 if (InnerMatcher.matches(*SimpleNode, Finder, Builder))523 return true;524 525 auto DREHasInit = ignoringImpCasts(526 declRefExpr(to(varDecl(hasInitializer(ignoringImpCasts(InnerMatcher))))));527 528 if (DREHasInit.matches(*SimpleNode, Finder, Builder))529 return true;530 531 const char *const VarDeclName = "variable-declaration";532 auto DREHasDefinition = ignoringImpCasts(declRefExpr(533 to(varDecl().bind(VarDeclName)),534 hasAncestor(compoundStmt(hasDescendant(binaryOperator(535 hasLHS(declRefExpr(to(varDecl(equalsBoundNode(VarDeclName))))),536 hasRHS(ignoringImpCasts(InnerMatcher))))))));537 538 if (DREHasDefinition.matches(*SimpleNode, Finder, Builder))539 return true;540 541 return false;542}543} // namespace544 545void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) {546 auto IncOp =547 binaryOperator(hasOperatorName("+"),548 hasEitherOperand(ignoringParenImpCasts(integerLiteral())));549 550 auto DecOp =551 binaryOperator(hasOperatorName("-"),552 hasEitherOperand(ignoringParenImpCasts(integerLiteral())));553 554 auto HasIncOp = anyOf(ignoringImpCasts(IncOp), hasDescendant(IncOp));555 auto HasDecOp = anyOf(ignoringImpCasts(DecOp), hasDescendant(DecOp));556 557 auto Container = ignoringImpCasts(cxxMemberCallExpr(hasDescendant(declRefExpr(558 hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(recordDecl(559 hasAnyName("::std::vector", "::std::list", "::std::deque"))))))))));560 561 auto StringTy = type(hasUnqualifiedDesugaredType(recordType(562 hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));563 564 auto AnyOfStringTy =565 anyOf(hasType(StringTy), hasType(qualType(pointsTo(StringTy))));566 567 auto CharTyArray = hasType(qualType(hasCanonicalType(568 arrayType(hasElementType(isAnyCharacter())).bind(DestArrayTyName))));569 570 auto CharTyPointer = hasType(571 qualType(hasCanonicalType(pointerType(pointee(isAnyCharacter())))));572 573 auto AnyOfCharTy = anyOf(CharTyArray, CharTyPointer);574 575 //===--------------------------------------------------------------------===//576 // The following six cases match problematic length expressions.577 //===--------------------------------------------------------------------===//578 579 // - Example: char src[] = "foo"; strlen(src);580 auto Strlen =581 callExpr(callee(functionDecl(hasAnyName("::strlen", "::wcslen"))))582 .bind(WrongLengthExprName);583 584 // - Example: std::string str = "foo"; str.size();585 auto SizeOrLength =586 cxxMemberCallExpr(on(expr(AnyOfStringTy).bind("Foo")),587 has(memberExpr(member(hasAnyName("size", "length")))))588 .bind(WrongLengthExprName);589 590 // - Example: char src[] = "foo"; sizeof(src);591 auto SizeOfCharExpr = unaryExprOrTypeTraitExpr(has(expr(AnyOfCharTy)));592 593 auto WrongLength =594 ignoringImpCasts(anyOf(Strlen, SizeOrLength, hasDescendant(Strlen),595 hasDescendant(SizeOrLength)));596 597 // - Example: length = strlen(src);598 auto DREWithoutInc =599 ignoringImpCasts(declRefExpr(to(varDecl(hasInitializer(WrongLength)))));600 601 auto AnyOfCallOrDREWithoutInc = anyOf(DREWithoutInc, WrongLength);602 603 // - Example: int getLength(const char *str) { return strlen(str); }604 auto CallExprReturnWithoutInc = ignoringImpCasts(callExpr(callee(functionDecl(605 hasBody(has(returnStmt(hasReturnValue(AnyOfCallOrDREWithoutInc))))))));606 607 // - Example: int length = getLength(src);608 auto DREHasReturnWithoutInc = ignoringImpCasts(609 declRefExpr(to(varDecl(hasInitializer(CallExprReturnWithoutInc)))));610 611 auto AnyOfWrongLengthInit =612 anyOf(WrongLength, AnyOfCallOrDREWithoutInc, CallExprReturnWithoutInc,613 DREHasReturnWithoutInc);614 615 //===--------------------------------------------------------------------===//616 // The following five cases match the 'destination' array length's617 // expression which is used in 'memcpy()' and 'memmove()' matchers.618 //===--------------------------------------------------------------------===//619 620 // Note: Sometimes the size of char is explicitly written out.621 auto SizeExpr = anyOf(SizeOfCharExpr, integerLiteral(equals(1)));622 623 auto MallocLengthExpr = allOf(624 callee(functionDecl(625 hasAnyName("::alloca", "::calloc", "malloc", "realloc"))),626 hasAnyArgument(allOf(unless(SizeExpr), expr().bind(DestMallocExprName))));627 628 // - Example: (char *)malloc(length);629 auto DestMalloc = anyOf(callExpr(MallocLengthExpr),630 hasDescendant(callExpr(MallocLengthExpr)));631 632 // - Example: new char[length];633 auto DestCXXNewExpr = ignoringImpCasts(634 cxxNewExpr(hasArraySize(expr().bind(DestMallocExprName))));635 636 auto AnyOfDestInit = anyOf(DestMalloc, DestCXXNewExpr);637 638 // - Example: char dest[13]; or char dest[length];639 auto DestArrayTyDecl = declRefExpr(640 to(anyOf(varDecl(CharTyArray).bind(DestVarDeclName),641 varDecl(hasInitializer(AnyOfDestInit)).bind(DestVarDeclName))));642 643 // - Example: foo[bar[baz]].qux; (or just ParmVarDecl)644 auto DestUnknownDecl =645 declRefExpr(to(varDecl(AnyOfCharTy).bind(DestVarDeclName)),646 expr().bind(UnknownDestName))647 .bind(DestExprName);648 649 auto AnyOfDestDecl = ignoringImpCasts(650 anyOf(allOf(hasDefinition(anyOf(AnyOfDestInit, DestArrayTyDecl,651 hasDescendant(DestArrayTyDecl))),652 expr().bind(DestExprName)),653 anyOf(DestUnknownDecl, hasDescendant(DestUnknownDecl))));654 655 auto NullTerminatorExpr = binaryOperator(656 hasLHS(anyOf(hasDescendant(declRefExpr(to(varDecl(657 equalsBoundNode(std::string(DestVarDeclName)))))),658 hasDescendant(declRefExpr(659 equalsBoundNode(std::string(DestExprName)))))),660 hasRHS(ignoringImpCasts(661 anyOf(characterLiteral(equals(0U)), integerLiteral(equals(0))))));662 663 auto SrcDecl =664 declRefExpr(to(decl().bind(SrcVarDeclName)),665 anyOf(hasAncestor(cxxMemberCallExpr().bind(SrcExprName)),666 expr().bind(SrcExprName)));667 668 auto AnyOfSrcDecl =669 ignoringImpCasts(anyOf(stringLiteral().bind(SrcExprName),670 hasDescendant(stringLiteral().bind(SrcExprName)),671 SrcDecl, hasDescendant(SrcDecl)));672 673 //===--------------------------------------------------------------------===//674 // Match the problematic function calls.675 //===--------------------------------------------------------------------===//676 677 struct CallContext {678 CallContext(StringRef Name, std::optional<unsigned> DestinationPos,679 std::optional<unsigned> SourcePos, unsigned LengthPos,680 bool WithIncrease)681 : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos),682 LengthPos(LengthPos), WithIncrease(WithIncrease) {};683 684 StringRef Name;685 std::optional<unsigned> DestinationPos;686 std::optional<unsigned> SourcePos;687 unsigned LengthPos;688 bool WithIncrease;689 };690 691 auto MatchDestination = [=](CallContext CC) {692 return hasArgument(*CC.DestinationPos,693 allOf(AnyOfDestDecl,694 unless(hasAncestor(compoundStmt(695 hasDescendant(NullTerminatorExpr)))),696 unless(Container)));697 };698 699 auto MatchSource = [=](CallContext CC) {700 return hasArgument(*CC.SourcePos, AnyOfSrcDecl);701 };702 703 auto MatchGivenLength = [=](CallContext CC) {704 return hasArgument(705 CC.LengthPos,706 allOf(707 anyOf(ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)),708 allOf(unless(hasDefinition(SizeOfCharExpr)),709 allOf(CC.WithIncrease710 ? ignoringImpCasts(hasDefinition(HasIncOp))711 : ignoringImpCasts(712 allOf(unless(hasDefinition(HasIncOp)),713 hasDefinition(optionally(714 binaryOperator().bind(715 UnknownLengthName))))),716 AnyOfWrongLengthInit))),717 expr().bind(LengthExprName)));718 };719 720 auto MatchCall = [=](CallContext CC) {721 const std::string CharHandlerFuncName = "::" + CC.Name.str();722 723 // Try to match with 'wchar_t' based function calls.724 const std::string WcharHandlerFuncName =725 "::" + (CC.Name.starts_with("mem") ? "w" + CC.Name.str()726 : "wcs" + CC.Name.substr(3).str());727 728 return allOf(callee(functionDecl(729 hasAnyName(CharHandlerFuncName, WcharHandlerFuncName))),730 MatchGivenLength(CC));731 };732 733 auto Match = [=](CallContext CC) {734 if (CC.DestinationPos && CC.SourcePos)735 return allOf(MatchCall(CC), MatchDestination(CC), MatchSource(CC));736 737 if (CC.DestinationPos && !CC.SourcePos)738 return allOf(MatchCall(CC), MatchDestination(CC),739 hasArgument(*CC.DestinationPos, anything()));740 741 if (!CC.DestinationPos && CC.SourcePos)742 return allOf(MatchCall(CC), MatchSource(CC),743 hasArgument(*CC.SourcePos, anything()));744 745 llvm_unreachable("Unhandled match");746 };747 748 // void *memcpy(void *dest, const void *src, size_t count)749 auto Memcpy = Match({"memcpy", 0, 1, 2, false});750 751 // errno_t memcpy_s(void *dest, size_t ds, const void *src, size_t count)752 auto MemcpyS = Match({"memcpy_s", 0, 2, 3, false});753 754 // void *memchr(const void *src, int c, size_t count)755 auto Memchr = Match({"memchr", std::nullopt, 0, 2, false});756 757 // void *memmove(void *dest, const void *src, size_t count)758 auto Memmove = Match({"memmove", 0, 1, 2, false});759 760 // errno_t memmove_s(void *dest, size_t ds, const void *src, size_t count)761 auto MemmoveS = Match({"memmove_s", 0, 2, 3, false});762 763 // int strncmp(const char *str1, const char *str2, size_t count);764 auto StrncmpRHS = Match({"strncmp", std::nullopt, 1, 2, true});765 auto StrncmpLHS = Match({"strncmp", std::nullopt, 0, 2, true});766 767 // size_t strxfrm(char *dest, const char *src, size_t count);768 auto Strxfrm = Match({"strxfrm", 0, 1, 2, false});769 770 // errno_t strerror_s(char *buffer, size_t bufferSize, int errnum);771 auto StrerrorS = Match({"strerror_s", 0, std::nullopt, 1, false});772 773 auto AnyOfMatchers = anyOf(Memcpy, MemcpyS, Memmove, MemmoveS, StrncmpRHS,774 StrncmpLHS, Strxfrm, StrerrorS);775 776 Finder->addMatcher(callExpr(AnyOfMatchers).bind(FunctionExprName), this);777 778 // Need to remove the CastExpr from 'memchr()' as 'strchr()' returns 'char *'.779 Finder->addMatcher(780 callExpr(Memchr,781 unless(hasAncestor(castExpr(unless(implicitCastExpr())))))782 .bind(FunctionExprName),783 this);784 Finder->addMatcher(785 castExpr(allOf(unless(implicitCastExpr()),786 has(callExpr(Memchr).bind(FunctionExprName))))787 .bind(CastExprName),788 this);789}790 791void NotNullTerminatedResultCheck::check(792 const MatchFinder::MatchResult &Result) {793 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);794 if (FunctionExpr->getBeginLoc().isMacroID())795 return;796 797 if (WantToUseSafeFunctions && PP->isMacroDefined("__STDC_LIB_EXT1__")) {798 std::optional<bool> AreSafeFunctionsWanted;799 800 Preprocessor::macro_iterator It = PP->macro_begin();801 while (It != PP->macro_end() && !AreSafeFunctionsWanted) {802 if (It->first->getName() == "__STDC_WANT_LIB_EXT1__") {803 const auto *MI = PP->getMacroInfo(It->first);804 // PP->getMacroInfo() returns nullptr if macro has no definition.805 if (MI) {806 const auto &T = MI->tokens().back();807 if (T.isLiteral() && T.getLiteralData()) {808 const StringRef ValueStr =809 StringRef(T.getLiteralData(), T.getLength());810 llvm::APInt IntValue;811 ValueStr.getAsInteger(10, IntValue);812 AreSafeFunctionsWanted = IntValue.getZExtValue();813 }814 }815 }816 817 ++It;818 }819 820 if (AreSafeFunctionsWanted)821 UseSafeFunctions = *AreSafeFunctionsWanted;822 }823 824 const StringRef Name = FunctionExpr->getDirectCallee()->getName();825 if (Name.starts_with("mem") || Name.starts_with("wmem"))826 memoryHandlerFunctionFix(Name, Result);827 else if (Name == "strerror_s")828 strerrorSFix(Result);829 else if (Name.ends_with("ncmp"))830 ncmpFix(Name, Result);831 else if (Name.ends_with("xfrm"))832 xfrmFix(Name, Result);833}834 835void NotNullTerminatedResultCheck::memoryHandlerFunctionFix(836 StringRef Name, const MatchFinder::MatchResult &Result) {837 if (isCorrectGivenLength(Result))838 return;839 840 if (Name.ends_with("chr")) {841 memchrFix(Name, Result);842 return;843 }844 845 if ((Name.contains("cpy") || Name.contains("move")) &&846 (isDestAndSrcEquals(Result) || isFixedGivenLengthAndUnknownSrc(Result)))847 return;848 849 auto Diag =850 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),851 "the result from calling '%0' is not null-terminated")852 << Name;853 854 if (Name.ends_with("cpy")) {855 memcpyFix(Name, Result, Diag);856 } else if (Name.ends_with("cpy_s")) {857 memcpySFix(Name, Result, Diag);858 } else if (Name.ends_with("move")) {859 memmoveFix(Name, Result, Diag);860 } else if (Name.ends_with("move_s")) {861 isDestCapacityFix(Result, Diag);862 lengthArgHandle(LengthHandleKind::Increase, Result, Diag);863 }864}865 866void NotNullTerminatedResultCheck::memcpyFix(867 StringRef Name, const MatchFinder::MatchResult &Result,868 DiagnosticBuilder &Diag) {869 const bool IsOverflows = isDestCapacityFix(Result, Diag);870 const bool IsDestFixed = isDestExprFix(Result, Diag);871 872 const bool IsCopy =873 isGivenLengthEqualToSrcLength(Result) || isDestBasedOnGivenLength(Result);874 875 const bool IsSafe = UseSafeFunctions && IsOverflows && isKnownDest(Result) &&876 !isDestBasedOnGivenLength(Result);877 878 const bool IsDestLengthNotRequired =879 IsSafe && getLangOpts().CPlusPlus &&880 Result.Nodes.getNodeAs<ArrayType>(DestArrayTyName) && !IsDestFixed;881 882 renameMemcpy(Name, IsCopy, IsSafe, Result, Diag);883 884 if (IsSafe && !IsDestLengthNotRequired)885 insertDestCapacityArg(IsOverflows, Name, Result, Diag);886 887 if (IsCopy)888 removeArg(2, Result, Diag);889 890 if (!IsCopy && !IsSafe)891 insertNullTerminatorExpr(Name, Result, Diag);892}893 894void NotNullTerminatedResultCheck::memcpySFix(895 StringRef Name, const MatchFinder::MatchResult &Result,896 DiagnosticBuilder &Diag) {897 const bool IsOverflows = isDestCapacityFix(Result, Diag);898 const bool IsDestFixed = isDestExprFix(Result, Diag);899 900 const bool RemoveDestLength =901 getLangOpts().CPlusPlus &&902 Result.Nodes.getNodeAs<ArrayType>(DestArrayTyName) && !IsDestFixed;903 const bool IsCopy = isGivenLengthEqualToSrcLength(Result);904 const bool IsSafe = IsOverflows;905 906 renameMemcpy(Name, IsCopy, IsSafe, Result, Diag);907 908 if (!IsSafe || (IsSafe && RemoveDestLength))909 removeArg(1, Result, Diag);910 else if (IsOverflows && isKnownDest(Result))911 lengthArgPosHandle(1, LengthHandleKind::Increase, Result, Diag);912 913 if (IsCopy)914 removeArg(3, Result, Diag);915 916 if (!IsCopy && !IsSafe)917 insertNullTerminatorExpr(Name, Result, Diag);918}919 920void NotNullTerminatedResultCheck::memchrFix(921 StringRef Name, const MatchFinder::MatchResult &Result) {922 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);923 if (const auto *GivenCL = dyn_cast<CharacterLiteral>(FunctionExpr->getArg(1)))924 if (GivenCL->getValue() != 0)925 return;926 927 auto Diag = diag(FunctionExpr->getArg(2)->IgnoreParenCasts()->getBeginLoc(),928 "the length is too short to include the null terminator");929 930 if (const auto *CastExpr = Result.Nodes.getNodeAs<Expr>(CastExprName)) {931 const auto CastRemoveFix = FixItHint::CreateRemoval(932 SourceRange(CastExpr->getBeginLoc(),933 FunctionExpr->getBeginLoc().getLocWithOffset(-1)));934 Diag << CastRemoveFix;935 }936 937 const StringRef NewFuncName = (Name[0] != 'w') ? "strchr" : "wcschr";938 renameFunc(NewFuncName, Result, Diag);939 removeArg(2, Result, Diag);940}941 942void NotNullTerminatedResultCheck::memmoveFix(943 StringRef Name, const MatchFinder::MatchResult &Result,944 DiagnosticBuilder &Diag) const {945 const bool IsOverflows = isDestCapacityFix(Result, Diag);946 947 if (UseSafeFunctions && isKnownDest(Result)) {948 renameFunc((Name[0] != 'w') ? "memmove_s" : "wmemmove_s", Result, Diag);949 insertDestCapacityArg(IsOverflows, Name, Result, Diag);950 }951 952 lengthArgHandle(LengthHandleKind::Increase, Result, Diag);953}954 955void NotNullTerminatedResultCheck::strerrorSFix(956 const MatchFinder::MatchResult &Result) {957 auto Diag =958 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),959 "the result from calling 'strerror_s' is not null-terminated and "960 "missing the last character of the error message");961 962 isDestCapacityFix(Result, Diag);963 lengthArgHandle(LengthHandleKind::Increase, Result, Diag);964}965 966void NotNullTerminatedResultCheck::ncmpFix(967 StringRef Name, const MatchFinder::MatchResult &Result) {968 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);969 const Expr *FirstArgExpr = FunctionExpr->getArg(0)->IgnoreImpCasts();970 const Expr *SecondArgExpr = FunctionExpr->getArg(1)->IgnoreImpCasts();971 bool IsLengthTooLong = false;972 973 if (const CallExpr *StrlenExpr = getStrlenExpr(Result)) {974 const Expr *LengthExprArg = StrlenExpr->getArg(0);975 const StringRef FirstExprStr = exprToStr(FirstArgExpr, Result).trim();976 const StringRef SecondExprStr = exprToStr(SecondArgExpr, Result).trim();977 const StringRef LengthArgStr = exprToStr(LengthExprArg, Result).trim();978 IsLengthTooLong =979 LengthArgStr == FirstExprStr || LengthArgStr == SecondExprStr;980 } else {981 const int SrcLength =982 getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);983 const int GivenLength = getGivenLength(Result);984 if (SrcLength != 0 && GivenLength != 0)985 IsLengthTooLong = GivenLength > SrcLength;986 }987 988 if (!IsLengthTooLong && !isStringDataAndLength(Result))989 return;990 991 auto Diag = diag(FunctionExpr->getArg(2)->IgnoreParenCasts()->getBeginLoc(),992 "comparison length is too long and might lead to a "993 "buffer overflow");994 995 lengthArgHandle(LengthHandleKind::Decrease, Result, Diag);996}997 998void NotNullTerminatedResultCheck::xfrmFix(999 StringRef Name, const MatchFinder::MatchResult &Result) {1000 if (!isDestCapacityOverflows(Result))1001 return;1002 1003 auto Diag =1004 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),1005 "the result from calling '%0' is not null-terminated")1006 << Name;1007 1008 isDestCapacityFix(Result, Diag);1009 lengthArgHandle(LengthHandleKind::Increase, Result, Diag);1010}1011 1012} // namespace clang::tidy::bugprone1013