123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011 |
- //===--- NotNullTerminatedResultCheck.cpp - clang-tidy ----------*- C++ -*-===//
- //
- // 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 "NotNullTerminatedResultCheck.h"
- #include "clang/AST/ASTContext.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include "clang/Frontend/CompilerInstance.h"
- #include "clang/Lex/Lexer.h"
- #include "clang/Lex/PPCallbacks.h"
- #include "clang/Lex/Preprocessor.h"
- #include <optional>
- using namespace clang::ast_matchers;
- namespace clang::tidy::bugprone {
- constexpr llvm::StringLiteral FunctionExprName = "FunctionExpr";
- constexpr llvm::StringLiteral CastExprName = "CastExpr";
- constexpr llvm::StringLiteral UnknownDestName = "UnknownDest";
- constexpr llvm::StringLiteral DestArrayTyName = "DestArrayTy";
- constexpr llvm::StringLiteral DestVarDeclName = "DestVarDecl";
- constexpr llvm::StringLiteral DestMallocExprName = "DestMalloc";
- constexpr llvm::StringLiteral DestExprName = "DestExpr";
- constexpr llvm::StringLiteral SrcVarDeclName = "SrcVarDecl";
- constexpr llvm::StringLiteral SrcExprName = "SrcExpr";
- constexpr llvm::StringLiteral LengthExprName = "LengthExpr";
- constexpr llvm::StringLiteral WrongLengthExprName = "WrongLength";
- constexpr llvm::StringLiteral UnknownLengthName = "UnknownLength";
- enum class LengthHandleKind { Increase, Decrease };
- namespace {
- static Preprocessor *PP;
- } // namespace
- // Returns the expression of destination's capacity which is part of a
- // 'VariableArrayType', 'ConstantArrayTypeLoc' or an argument of a 'malloc()'
- // family function call.
- static const Expr *getDestCapacityExpr(const MatchFinder::MatchResult &Result) {
- if (const auto *DestMalloc = Result.Nodes.getNodeAs<Expr>(DestMallocExprName))
- return DestMalloc;
- if (const auto *DestVAT =
- Result.Nodes.getNodeAs<VariableArrayType>(DestArrayTyName))
- return DestVAT->getSizeExpr();
- if (const auto *DestVD = Result.Nodes.getNodeAs<VarDecl>(DestVarDeclName))
- if (const TypeLoc DestTL = DestVD->getTypeSourceInfo()->getTypeLoc())
- if (const auto DestCTL = DestTL.getAs<ConstantArrayTypeLoc>())
- return DestCTL.getSizeExpr();
- return nullptr;
- }
- // Returns the length of \p E as an 'IntegerLiteral' or a 'StringLiteral'
- // without the null-terminator.
- static unsigned getLength(const Expr *E,
- const MatchFinder::MatchResult &Result) {
- if (!E)
- return 0;
- Expr::EvalResult Length;
- E = E->IgnoreImpCasts();
- if (const auto *LengthDRE = dyn_cast<DeclRefExpr>(E))
- if (const auto *LengthVD = dyn_cast<VarDecl>(LengthDRE->getDecl()))
- if (!isa<ParmVarDecl>(LengthVD))
- if (const Expr *LengthInit = LengthVD->getInit())
- if (LengthInit->EvaluateAsInt(Length, *Result.Context))
- return Length.Val.getInt().getZExtValue();
- if (const auto *LengthIL = dyn_cast<IntegerLiteral>(E))
- return LengthIL->getValue().getZExtValue();
- if (const auto *StrDRE = dyn_cast<DeclRefExpr>(E))
- if (const auto *StrVD = dyn_cast<VarDecl>(StrDRE->getDecl()))
- if (const Expr *StrInit = StrVD->getInit())
- if (const auto *StrSL =
- dyn_cast<StringLiteral>(StrInit->IgnoreImpCasts()))
- return StrSL->getLength();
- if (const auto *SrcSL = dyn_cast<StringLiteral>(E))
- return SrcSL->getLength();
- return 0;
- }
- // Returns the capacity of the destination array.
- // For example in 'char dest[13]; memcpy(dest, ...)' it returns 13.
- static int getDestCapacity(const MatchFinder::MatchResult &Result) {
- if (const auto *DestCapacityExpr = getDestCapacityExpr(Result))
- return getLength(DestCapacityExpr, Result);
- return 0;
- }
- // Returns the 'strlen()' if it is the given length.
- static const CallExpr *getStrlenExpr(const MatchFinder::MatchResult &Result) {
- if (const auto *StrlenExpr =
- Result.Nodes.getNodeAs<CallExpr>(WrongLengthExprName))
- if (const Decl *D = StrlenExpr->getCalleeDecl())
- if (const FunctionDecl *FD = D->getAsFunction())
- if (const IdentifierInfo *II = FD->getIdentifier())
- if (II->isStr("strlen") || II->isStr("wcslen"))
- return StrlenExpr;
- return nullptr;
- }
- // Returns the length which is given in the memory/string handler function.
- // For example in 'memcpy(dest, "foobar", 3)' it returns 3.
- static int getGivenLength(const MatchFinder::MatchResult &Result) {
- if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))
- return 0;
- if (int Length =
- getLength(Result.Nodes.getNodeAs<Expr>(WrongLengthExprName), Result))
- return Length;
- if (int Length =
- getLength(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result))
- return Length;
- // Special case, for example 'strlen("foo")'.
- if (const CallExpr *StrlenCE = getStrlenExpr(Result))
- if (const Expr *Arg = StrlenCE->getArg(0)->IgnoreImpCasts())
- if (int ArgLength = getLength(Arg, Result))
- return ArgLength;
- return 0;
- }
- // Returns a string representation of \p E.
- static StringRef exprToStr(const Expr *E,
- const MatchFinder::MatchResult &Result) {
- if (!E)
- return "";
- return Lexer::getSourceText(
- CharSourceRange::getTokenRange(E->getSourceRange()),
- *Result.SourceManager, Result.Context->getLangOpts(), nullptr);
- }
- // Returns the proper token based end location of \p E.
- static SourceLocation exprLocEnd(const Expr *E,
- const MatchFinder::MatchResult &Result) {
- return Lexer::getLocForEndOfToken(E->getEndLoc(), 0, *Result.SourceManager,
- Result.Context->getLangOpts());
- }
- //===----------------------------------------------------------------------===//
- // Rewrite decision helper functions.
- //===----------------------------------------------------------------------===//
- // Increment by integer '1' can result in overflow if it is the maximal value.
- // After that it would be extended to 'size_t' and its value would be wrong,
- // therefore we have to inject '+ 1UL' instead.
- static bool isInjectUL(const MatchFinder::MatchResult &Result) {
- return getGivenLength(Result) == std::numeric_limits<int>::max();
- }
- // If the capacity of the destination array is unknown it is denoted as unknown.
- static bool isKnownDest(const MatchFinder::MatchResult &Result) {
- return !Result.Nodes.getNodeAs<Expr>(UnknownDestName);
- }
- // True if the capacity of the destination array is based on the given length,
- // therefore we assume that it cannot overflow (e.g. 'malloc(given_length + 1)'
- static bool isDestBasedOnGivenLength(const MatchFinder::MatchResult &Result) {
- StringRef DestCapacityExprStr =
- exprToStr(getDestCapacityExpr(Result), Result).trim();
- StringRef LengthExprStr =
- exprToStr(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result).trim();
- return DestCapacityExprStr != "" && LengthExprStr != "" &&
- DestCapacityExprStr.contains(LengthExprStr);
- }
- // Writing and reading from the same memory cannot remove the null-terminator.
- static bool isDestAndSrcEquals(const MatchFinder::MatchResult &Result) {
- if (const auto *DestDRE = Result.Nodes.getNodeAs<DeclRefExpr>(DestExprName))
- if (const auto *SrcDRE = Result.Nodes.getNodeAs<DeclRefExpr>(SrcExprName))
- return DestDRE->getDecl()->getCanonicalDecl() ==
- SrcDRE->getDecl()->getCanonicalDecl();
- return false;
- }
- // For example 'std::string str = "foo"; memcpy(dst, str.data(), str.length())'.
- static bool isStringDataAndLength(const MatchFinder::MatchResult &Result) {
- const auto *DestExpr =
- Result.Nodes.getNodeAs<CXXMemberCallExpr>(DestExprName);
- const auto *SrcExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>(SrcExprName);
- const auto *LengthExpr =
- Result.Nodes.getNodeAs<CXXMemberCallExpr>(WrongLengthExprName);
- StringRef DestStr = "", SrcStr = "", LengthStr = "";
- if (DestExpr)
- if (const CXXMethodDecl *DestMD = DestExpr->getMethodDecl())
- DestStr = DestMD->getName();
- if (SrcExpr)
- if (const CXXMethodDecl *SrcMD = SrcExpr->getMethodDecl())
- SrcStr = SrcMD->getName();
- if (LengthExpr)
- if (const CXXMethodDecl *LengthMD = LengthExpr->getMethodDecl())
- LengthStr = LengthMD->getName();
- return (LengthStr == "length" || LengthStr == "size") &&
- (SrcStr == "data" || DestStr == "data");
- }
- static bool
- isGivenLengthEqualToSrcLength(const MatchFinder::MatchResult &Result) {
- if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))
- return false;
- if (isStringDataAndLength(Result))
- return true;
- int GivenLength = getGivenLength(Result);
- int SrcLength = getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);
- if (GivenLength != 0 && SrcLength != 0 && GivenLength == SrcLength)
- return true;
- if (const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName))
- if (isa<BinaryOperator>(LengthExpr->IgnoreParenImpCasts()))
- return false;
- // Check the strlen()'s argument's 'VarDecl' is equal to the source 'VarDecl'.
- if (const CallExpr *StrlenCE = getStrlenExpr(Result))
- if (const auto *ArgDRE =
- dyn_cast<DeclRefExpr>(StrlenCE->getArg(0)->IgnoreImpCasts()))
- if (const auto *SrcVD = Result.Nodes.getNodeAs<VarDecl>(SrcVarDeclName))
- return dyn_cast<VarDecl>(ArgDRE->getDecl()) == SrcVD;
- return false;
- }
- static bool isCorrectGivenLength(const MatchFinder::MatchResult &Result) {
- if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))
- return false;
- return !isGivenLengthEqualToSrcLength(Result);
- }
- // If we rewrite the function call we need to create extra space to hold the
- // null terminator. The new necessary capacity overflows without that '+ 1'
- // size and we need to correct the given capacity.
- static bool isDestCapacityOverflows(const MatchFinder::MatchResult &Result) {
- if (!isKnownDest(Result))
- return true;
- const Expr *DestCapacityExpr = getDestCapacityExpr(Result);
- int DestCapacity = getLength(DestCapacityExpr, Result);
- int GivenLength = getGivenLength(Result);
- if (GivenLength != 0 && DestCapacity != 0)
- return isGivenLengthEqualToSrcLength(Result) && DestCapacity == GivenLength;
- // Assume that the destination array's capacity cannot overflow if the
- // expression of the memory allocation contains '+ 1'.
- StringRef DestCapacityExprStr = exprToStr(DestCapacityExpr, Result);
- if (DestCapacityExprStr.contains("+1") || DestCapacityExprStr.contains("+ 1"))
- return false;
- return true;
- }
- static bool
- isFixedGivenLengthAndUnknownSrc(const MatchFinder::MatchResult &Result) {
- if (Result.Nodes.getNodeAs<IntegerLiteral>(WrongLengthExprName))
- return !getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);
- return false;
- }
- //===----------------------------------------------------------------------===//
- // Code injection functions.
- //===----------------------------------------------------------------------===//
- // Increase or decrease \p LengthExpr by one.
- static void lengthExprHandle(const Expr *LengthExpr,
- LengthHandleKind LengthHandle,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- LengthExpr = LengthExpr->IgnoreParenImpCasts();
- // See whether we work with a macro.
- bool IsMacroDefinition = false;
- StringRef LengthExprStr = exprToStr(LengthExpr, Result);
- Preprocessor::macro_iterator It = PP->macro_begin();
- while (It != PP->macro_end() && !IsMacroDefinition) {
- if (It->first->getName() == LengthExprStr)
- IsMacroDefinition = true;
- ++It;
- }
- // Try to obtain an 'IntegerLiteral' and adjust it.
- if (!IsMacroDefinition) {
- if (const auto *LengthIL = dyn_cast<IntegerLiteral>(LengthExpr)) {
- size_t NewLength = LengthIL->getValue().getZExtValue() +
- (LengthHandle == LengthHandleKind::Increase
- ? (isInjectUL(Result) ? 1UL : 1)
- : -1);
- const auto NewLengthFix = FixItHint::CreateReplacement(
- LengthIL->getSourceRange(),
- (Twine(NewLength) + (isInjectUL(Result) ? "UL" : "")).str());
- Diag << NewLengthFix;
- return;
- }
- }
- // Try to obtain and remove the '+ 1' string as a decrement fix.
- const auto *BO = dyn_cast<BinaryOperator>(LengthExpr);
- if (BO && BO->getOpcode() == BO_Add &&
- LengthHandle == LengthHandleKind::Decrease) {
- const Expr *LhsExpr = BO->getLHS()->IgnoreImpCasts();
- const Expr *RhsExpr = BO->getRHS()->IgnoreImpCasts();
- if (const auto *LhsIL = dyn_cast<IntegerLiteral>(LhsExpr)) {
- if (LhsIL->getValue().getZExtValue() == 1) {
- Diag << FixItHint::CreateRemoval(
- {LhsIL->getBeginLoc(),
- RhsExpr->getBeginLoc().getLocWithOffset(-1)});
- return;
- }
- }
- if (const auto *RhsIL = dyn_cast<IntegerLiteral>(RhsExpr)) {
- if (RhsIL->getValue().getZExtValue() == 1) {
- Diag << FixItHint::CreateRemoval(
- {LhsExpr->getEndLoc().getLocWithOffset(1), RhsIL->getEndLoc()});
- return;
- }
- }
- }
- // Try to inject the '+ 1'/'- 1' string.
- bool NeedInnerParen = BO && BO->getOpcode() != BO_Add;
- if (NeedInnerParen)
- Diag << FixItHint::CreateInsertion(LengthExpr->getBeginLoc(), "(");
- SmallString<8> Injection;
- if (NeedInnerParen)
- Injection += ')';
- Injection += LengthHandle == LengthHandleKind::Increase ? " + 1" : " - 1";
- if (isInjectUL(Result))
- Injection += "UL";
- Diag << FixItHint::CreateInsertion(exprLocEnd(LengthExpr, Result), Injection);
- }
- static void lengthArgHandle(LengthHandleKind LengthHandle,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName);
- lengthExprHandle(LengthExpr, LengthHandle, Result, Diag);
- }
- static void lengthArgPosHandle(unsigned ArgPos, LengthHandleKind LengthHandle,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- lengthExprHandle(FunctionExpr->getArg(ArgPos), LengthHandle, Result, Diag);
- }
- // The string handler functions are only operates with plain 'char'/'wchar_t'
- // without 'unsigned/signed', therefore we need to cast it.
- static bool isDestExprFix(const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *Dest = Result.Nodes.getNodeAs<Expr>(DestExprName);
- if (!Dest)
- return false;
- std::string TempTyStr = Dest->getType().getAsString();
- StringRef TyStr = TempTyStr;
- if (TyStr.startswith("char") || TyStr.startswith("wchar_t"))
- return false;
- Diag << FixItHint::CreateInsertion(Dest->getBeginLoc(), "(char *)");
- return true;
- }
- // If the destination array is the same length as the given length we have to
- // increase the capacity by one to create space for the null terminator.
- static bool isDestCapacityFix(const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- bool IsOverflows = isDestCapacityOverflows(Result);
- if (IsOverflows)
- if (const Expr *CapacityExpr = getDestCapacityExpr(Result))
- lengthExprHandle(CapacityExpr, LengthHandleKind::Increase, Result, Diag);
- return IsOverflows;
- }
- static void removeArg(int ArgPos, const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- // This is the following structure: (src, '\0', strlen(src))
- // ArgToRemove: ~~~~~~~~~~~
- // LHSArg: ~~~~
- // RemoveArgFix: ~~~~~~~~~~~~~
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- const Expr *ArgToRemove = FunctionExpr->getArg(ArgPos);
- const Expr *LHSArg = FunctionExpr->getArg(ArgPos - 1);
- const auto RemoveArgFix = FixItHint::CreateRemoval(
- SourceRange(exprLocEnd(LHSArg, Result),
- exprLocEnd(ArgToRemove, Result).getLocWithOffset(-1)));
- Diag << RemoveArgFix;
- }
- static void renameFunc(StringRef NewFuncName,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- int FuncNameLength =
- FunctionExpr->getDirectCallee()->getIdentifier()->getLength();
- SourceRange FuncNameRange(
- FunctionExpr->getBeginLoc(),
- FunctionExpr->getBeginLoc().getLocWithOffset(FuncNameLength - 1));
- const auto FuncNameFix =
- FixItHint::CreateReplacement(FuncNameRange, NewFuncName);
- Diag << FuncNameFix;
- }
- static void renameMemcpy(StringRef Name, bool IsCopy, bool IsSafe,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- SmallString<10> NewFuncName;
- NewFuncName = (Name[0] != 'w') ? "str" : "wcs";
- NewFuncName += IsCopy ? "cpy" : "ncpy";
- NewFuncName += IsSafe ? "_s" : "";
- renameFunc(NewFuncName, Result, Diag);
- }
- static void insertDestCapacityArg(bool IsOverflows, StringRef Name,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- SmallString<64> NewSecondArg;
- if (int DestLength = getDestCapacity(Result)) {
- NewSecondArg = Twine(IsOverflows ? DestLength + 1 : DestLength).str();
- } else {
- NewSecondArg =
- (Twine(exprToStr(getDestCapacityExpr(Result), Result)) +
- (IsOverflows ? (!isInjectUL(Result) ? " + 1" : " + 1UL") : ""))
- .str();
- }
- NewSecondArg += ", ";
- const auto InsertNewArgFix = FixItHint::CreateInsertion(
- FunctionExpr->getArg(1)->getBeginLoc(), NewSecondArg);
- Diag << InsertNewArgFix;
- }
- static void insertNullTerminatorExpr(StringRef Name,
- const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- int FuncLocStartColumn = Result.SourceManager->getPresumedColumnNumber(
- FunctionExpr->getBeginLoc());
- SourceRange SpaceRange(
- FunctionExpr->getBeginLoc().getLocWithOffset(-FuncLocStartColumn + 1),
- FunctionExpr->getBeginLoc());
- StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
- CharSourceRange::getCharRange(SpaceRange), *Result.SourceManager,
- Result.Context->getLangOpts(), nullptr);
- SmallString<128> NewAddNullTermExprStr;
- NewAddNullTermExprStr =
- (Twine('\n') + SpaceBeforeStmtStr +
- exprToStr(Result.Nodes.getNodeAs<Expr>(DestExprName), Result) + "[" +
- exprToStr(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result) +
- "] = " + ((Name[0] != 'w') ? "\'\\0\';" : "L\'\\0\';"))
- .str();
- const auto AddNullTerminatorExprFix = FixItHint::CreateInsertion(
- exprLocEnd(FunctionExpr, Result).getLocWithOffset(1),
- NewAddNullTermExprStr);
- Diag << AddNullTerminatorExprFix;
- }
- //===----------------------------------------------------------------------===//
- // Checker logic with the matchers.
- //===----------------------------------------------------------------------===//
- NotNullTerminatedResultCheck::NotNullTerminatedResultCheck(
- StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context),
- WantToUseSafeFunctions(Options.get("WantToUseSafeFunctions", true)) {}
- void NotNullTerminatedResultCheck::storeOptions(
- ClangTidyOptions::OptionMap &Opts) {
- Options.store(Opts, "WantToUseSafeFunctions", WantToUseSafeFunctions);
- }
- void NotNullTerminatedResultCheck::registerPPCallbacks(
- const SourceManager &SM, Preprocessor *Pp, Preprocessor *ModuleExpanderPP) {
- PP = Pp;
- }
- namespace {
- AST_MATCHER_P(Expr, hasDefinition, ast_matchers::internal::Matcher<Expr>,
- InnerMatcher) {
- const Expr *SimpleNode = &Node;
- SimpleNode = SimpleNode->IgnoreParenImpCasts();
- if (InnerMatcher.matches(*SimpleNode, Finder, Builder))
- return true;
- auto DREHasInit = ignoringImpCasts(
- declRefExpr(to(varDecl(hasInitializer(ignoringImpCasts(InnerMatcher))))));
- if (DREHasInit.matches(*SimpleNode, Finder, Builder))
- return true;
- const char *const VarDeclName = "variable-declaration";
- auto DREHasDefinition = ignoringImpCasts(declRefExpr(
- allOf(to(varDecl().bind(VarDeclName)),
- hasAncestor(compoundStmt(hasDescendant(binaryOperator(
- hasLHS(declRefExpr(to(varDecl(equalsBoundNode(VarDeclName))))),
- hasRHS(ignoringImpCasts(InnerMatcher)))))))));
- if (DREHasDefinition.matches(*SimpleNode, Finder, Builder))
- return true;
- return false;
- }
- } // namespace
- void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) {
- auto IncOp =
- binaryOperator(hasOperatorName("+"),
- hasEitherOperand(ignoringParenImpCasts(integerLiteral())));
- auto DecOp =
- binaryOperator(hasOperatorName("-"),
- hasEitherOperand(ignoringParenImpCasts(integerLiteral())));
- auto HasIncOp = anyOf(ignoringImpCasts(IncOp), hasDescendant(IncOp));
- auto HasDecOp = anyOf(ignoringImpCasts(DecOp), hasDescendant(DecOp));
- auto Container = ignoringImpCasts(cxxMemberCallExpr(hasDescendant(declRefExpr(
- hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(recordDecl(
- hasAnyName("::std::vector", "::std::list", "::std::deque"))))))))));
- auto StringTy = type(hasUnqualifiedDesugaredType(recordType(
- hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));
- auto AnyOfStringTy =
- anyOf(hasType(StringTy), hasType(qualType(pointsTo(StringTy))));
- auto CharTyArray = hasType(qualType(hasCanonicalType(
- arrayType(hasElementType(isAnyCharacter())).bind(DestArrayTyName))));
- auto CharTyPointer = hasType(
- qualType(hasCanonicalType(pointerType(pointee(isAnyCharacter())))));
- auto AnyOfCharTy = anyOf(CharTyArray, CharTyPointer);
- //===--------------------------------------------------------------------===//
- // The following six cases match problematic length expressions.
- //===--------------------------------------------------------------------===//
- // - Example: char src[] = "foo"; strlen(src);
- auto Strlen =
- callExpr(callee(functionDecl(hasAnyName("::strlen", "::wcslen"))))
- .bind(WrongLengthExprName);
- // - Example: std::string str = "foo"; str.size();
- auto SizeOrLength =
- cxxMemberCallExpr(
- allOf(on(expr(AnyOfStringTy).bind("Foo")),
- has(memberExpr(member(hasAnyName("size", "length"))))))
- .bind(WrongLengthExprName);
- // - Example: char src[] = "foo"; sizeof(src);
- auto SizeOfCharExpr = unaryExprOrTypeTraitExpr(has(expr(AnyOfCharTy)));
- auto WrongLength =
- ignoringImpCasts(anyOf(Strlen, SizeOrLength, hasDescendant(Strlen),
- hasDescendant(SizeOrLength)));
- // - Example: length = strlen(src);
- auto DREWithoutInc =
- ignoringImpCasts(declRefExpr(to(varDecl(hasInitializer(WrongLength)))));
- auto AnyOfCallOrDREWithoutInc = anyOf(DREWithoutInc, WrongLength);
- // - Example: int getLength(const char *str) { return strlen(str); }
- auto CallExprReturnWithoutInc = ignoringImpCasts(callExpr(callee(functionDecl(
- hasBody(has(returnStmt(hasReturnValue(AnyOfCallOrDREWithoutInc))))))));
- // - Example: int length = getLength(src);
- auto DREHasReturnWithoutInc = ignoringImpCasts(
- declRefExpr(to(varDecl(hasInitializer(CallExprReturnWithoutInc)))));
- auto AnyOfWrongLengthInit =
- anyOf(WrongLength, AnyOfCallOrDREWithoutInc, CallExprReturnWithoutInc,
- DREHasReturnWithoutInc);
- //===--------------------------------------------------------------------===//
- // The following five cases match the 'destination' array length's
- // expression which is used in 'memcpy()' and 'memmove()' matchers.
- //===--------------------------------------------------------------------===//
- // Note: Sometimes the size of char is explicitly written out.
- auto SizeExpr = anyOf(SizeOfCharExpr, integerLiteral(equals(1)));
- auto MallocLengthExpr = allOf(
- callee(functionDecl(
- hasAnyName("::alloca", "::calloc", "malloc", "realloc"))),
- hasAnyArgument(allOf(unless(SizeExpr), expr().bind(DestMallocExprName))));
- // - Example: (char *)malloc(length);
- auto DestMalloc = anyOf(callExpr(MallocLengthExpr),
- hasDescendant(callExpr(MallocLengthExpr)));
- // - Example: new char[length];
- auto DestCXXNewExpr = ignoringImpCasts(
- cxxNewExpr(hasArraySize(expr().bind(DestMallocExprName))));
- auto AnyOfDestInit = anyOf(DestMalloc, DestCXXNewExpr);
- // - Example: char dest[13]; or char dest[length];
- auto DestArrayTyDecl = declRefExpr(
- to(anyOf(varDecl(CharTyArray).bind(DestVarDeclName),
- varDecl(hasInitializer(AnyOfDestInit)).bind(DestVarDeclName))));
- // - Example: foo[bar[baz]].qux; (or just ParmVarDecl)
- auto DestUnknownDecl =
- declRefExpr(allOf(to(varDecl(AnyOfCharTy).bind(DestVarDeclName)),
- expr().bind(UnknownDestName)))
- .bind(DestExprName);
- auto AnyOfDestDecl = ignoringImpCasts(
- anyOf(allOf(hasDefinition(anyOf(AnyOfDestInit, DestArrayTyDecl,
- hasDescendant(DestArrayTyDecl))),
- expr().bind(DestExprName)),
- anyOf(DestUnknownDecl, hasDescendant(DestUnknownDecl))));
- auto NullTerminatorExpr = binaryOperator(
- hasLHS(anyOf(hasDescendant(declRefExpr(to(varDecl(
- equalsBoundNode(std::string(DestVarDeclName)))))),
- hasDescendant(declRefExpr(
- equalsBoundNode(std::string(DestExprName)))))),
- hasRHS(ignoringImpCasts(
- anyOf(characterLiteral(equals(0U)), integerLiteral(equals(0))))));
- auto SrcDecl = declRefExpr(
- allOf(to(decl().bind(SrcVarDeclName)),
- anyOf(hasAncestor(cxxMemberCallExpr().bind(SrcExprName)),
- expr().bind(SrcExprName))));
- auto AnyOfSrcDecl =
- ignoringImpCasts(anyOf(stringLiteral().bind(SrcExprName),
- hasDescendant(stringLiteral().bind(SrcExprName)),
- SrcDecl, hasDescendant(SrcDecl)));
- //===--------------------------------------------------------------------===//
- // Match the problematic function calls.
- //===--------------------------------------------------------------------===//
- struct CallContext {
- CallContext(StringRef Name, std::optional<unsigned> DestinationPos,
- std::optional<unsigned> SourcePos, unsigned LengthPos,
- bool WithIncrease)
- : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos),
- LengthPos(LengthPos), WithIncrease(WithIncrease){};
- StringRef Name;
- std::optional<unsigned> DestinationPos;
- std::optional<unsigned> SourcePos;
- unsigned LengthPos;
- bool WithIncrease;
- };
- auto MatchDestination = [=](CallContext CC) {
- return hasArgument(*CC.DestinationPos,
- allOf(AnyOfDestDecl,
- unless(hasAncestor(compoundStmt(
- hasDescendant(NullTerminatorExpr)))),
- unless(Container)));
- };
- auto MatchSource = [=](CallContext CC) {
- return hasArgument(*CC.SourcePos, AnyOfSrcDecl);
- };
- auto MatchGivenLength = [=](CallContext CC) {
- return hasArgument(
- CC.LengthPos,
- allOf(
- anyOf(
- ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)),
- allOf(unless(hasDefinition(SizeOfCharExpr)),
- allOf(CC.WithIncrease
- ? ignoringImpCasts(hasDefinition(HasIncOp))
- : ignoringImpCasts(allOf(
- unless(hasDefinition(HasIncOp)),
- anyOf(hasDefinition(binaryOperator().bind(
- UnknownLengthName)),
- hasDefinition(anything())))),
- AnyOfWrongLengthInit))),
- expr().bind(LengthExprName)));
- };
- auto MatchCall = [=](CallContext CC) {
- std::string CharHandlerFuncName = "::" + CC.Name.str();
- // Try to match with 'wchar_t' based function calls.
- std::string WcharHandlerFuncName =
- "::" + (CC.Name.startswith("mem") ? "w" + CC.Name.str()
- : "wcs" + CC.Name.substr(3).str());
- return allOf(callee(functionDecl(
- hasAnyName(CharHandlerFuncName, WcharHandlerFuncName))),
- MatchGivenLength(CC));
- };
- auto Match = [=](CallContext CC) {
- if (CC.DestinationPos && CC.SourcePos)
- return allOf(MatchCall(CC), MatchDestination(CC), MatchSource(CC));
- if (CC.DestinationPos && !CC.SourcePos)
- return allOf(MatchCall(CC), MatchDestination(CC),
- hasArgument(*CC.DestinationPos, anything()));
- if (!CC.DestinationPos && CC.SourcePos)
- return allOf(MatchCall(CC), MatchSource(CC),
- hasArgument(*CC.SourcePos, anything()));
- llvm_unreachable("Unhandled match");
- };
- // void *memcpy(void *dest, const void *src, size_t count)
- auto Memcpy = Match({"memcpy", 0, 1, 2, false});
- // errno_t memcpy_s(void *dest, size_t ds, const void *src, size_t count)
- auto MemcpyS = Match({"memcpy_s", 0, 2, 3, false});
- // void *memchr(const void *src, int c, size_t count)
- auto Memchr = Match({"memchr", std::nullopt, 0, 2, false});
- // void *memmove(void *dest, const void *src, size_t count)
- auto Memmove = Match({"memmove", 0, 1, 2, false});
- // errno_t memmove_s(void *dest, size_t ds, const void *src, size_t count)
- auto MemmoveS = Match({"memmove_s", 0, 2, 3, false});
- // int strncmp(const char *str1, const char *str2, size_t count);
- auto StrncmpRHS = Match({"strncmp", std::nullopt, 1, 2, true});
- auto StrncmpLHS = Match({"strncmp", std::nullopt, 0, 2, true});
- // size_t strxfrm(char *dest, const char *src, size_t count);
- auto Strxfrm = Match({"strxfrm", 0, 1, 2, false});
- // errno_t strerror_s(char *buffer, size_t bufferSize, int errnum);
- auto StrerrorS = Match({"strerror_s", 0, std::nullopt, 1, false});
- auto AnyOfMatchers = anyOf(Memcpy, MemcpyS, Memmove, MemmoveS, StrncmpRHS,
- StrncmpLHS, Strxfrm, StrerrorS);
- Finder->addMatcher(callExpr(AnyOfMatchers).bind(FunctionExprName), this);
- // Need to remove the CastExpr from 'memchr()' as 'strchr()' returns 'char *'.
- Finder->addMatcher(
- callExpr(Memchr,
- unless(hasAncestor(castExpr(unless(implicitCastExpr())))))
- .bind(FunctionExprName),
- this);
- Finder->addMatcher(
- castExpr(allOf(unless(implicitCastExpr()),
- has(callExpr(Memchr).bind(FunctionExprName))))
- .bind(CastExprName),
- this);
- }
- void NotNullTerminatedResultCheck::check(
- const MatchFinder::MatchResult &Result) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- if (FunctionExpr->getBeginLoc().isMacroID())
- return;
- if (WantToUseSafeFunctions && PP->isMacroDefined("__STDC_LIB_EXT1__")) {
- std::optional<bool> AreSafeFunctionsWanted;
- Preprocessor::macro_iterator It = PP->macro_begin();
- while (It != PP->macro_end() && !AreSafeFunctionsWanted) {
- if (It->first->getName() == "__STDC_WANT_LIB_EXT1__") {
- const auto *MI = PP->getMacroInfo(It->first);
- // PP->getMacroInfo() returns nullptr if macro has no definition.
- if (MI) {
- const auto &T = MI->tokens().back();
- if (T.isLiteral() && T.getLiteralData()) {
- StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength());
- llvm::APInt IntValue;
- ValueStr.getAsInteger(10, IntValue);
- AreSafeFunctionsWanted = IntValue.getZExtValue();
- }
- }
- }
- ++It;
- }
- if (AreSafeFunctionsWanted)
- UseSafeFunctions = *AreSafeFunctionsWanted;
- }
- StringRef Name = FunctionExpr->getDirectCallee()->getName();
- if (Name.startswith("mem") || Name.startswith("wmem"))
- memoryHandlerFunctionFix(Name, Result);
- else if (Name == "strerror_s")
- strerror_sFix(Result);
- else if (Name.endswith("ncmp"))
- ncmpFix(Name, Result);
- else if (Name.endswith("xfrm"))
- xfrmFix(Name, Result);
- }
- void NotNullTerminatedResultCheck::memoryHandlerFunctionFix(
- StringRef Name, const MatchFinder::MatchResult &Result) {
- if (isCorrectGivenLength(Result))
- return;
- if (Name.endswith("chr")) {
- memchrFix(Name, Result);
- return;
- }
- if ((Name.contains("cpy") || Name.contains("move")) &&
- (isDestAndSrcEquals(Result) || isFixedGivenLengthAndUnknownSrc(Result)))
- return;
- auto Diag =
- diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
- "the result from calling '%0' is not null-terminated")
- << Name;
- if (Name.endswith("cpy")) {
- memcpyFix(Name, Result, Diag);
- } else if (Name.endswith("cpy_s")) {
- memcpy_sFix(Name, Result, Diag);
- } else if (Name.endswith("move")) {
- memmoveFix(Name, Result, Diag);
- } else if (Name.endswith("move_s")) {
- isDestCapacityFix(Result, Diag);
- lengthArgHandle(LengthHandleKind::Increase, Result, Diag);
- }
- }
- void NotNullTerminatedResultCheck::memcpyFix(
- StringRef Name, const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- bool IsOverflows = isDestCapacityFix(Result, Diag);
- bool IsDestFixed = isDestExprFix(Result, Diag);
- bool IsCopy =
- isGivenLengthEqualToSrcLength(Result) || isDestBasedOnGivenLength(Result);
- bool IsSafe = UseSafeFunctions && IsOverflows && isKnownDest(Result) &&
- !isDestBasedOnGivenLength(Result);
- bool IsDestLengthNotRequired =
- IsSafe && getLangOpts().CPlusPlus &&
- Result.Nodes.getNodeAs<ArrayType>(DestArrayTyName) && !IsDestFixed;
- renameMemcpy(Name, IsCopy, IsSafe, Result, Diag);
- if (IsSafe && !IsDestLengthNotRequired)
- insertDestCapacityArg(IsOverflows, Name, Result, Diag);
- if (IsCopy)
- removeArg(2, Result, Diag);
- if (!IsCopy && !IsSafe)
- insertNullTerminatorExpr(Name, Result, Diag);
- }
- void NotNullTerminatedResultCheck::memcpy_sFix(
- StringRef Name, const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- bool IsOverflows = isDestCapacityFix(Result, Diag);
- bool IsDestFixed = isDestExprFix(Result, Diag);
- bool RemoveDestLength = getLangOpts().CPlusPlus &&
- Result.Nodes.getNodeAs<ArrayType>(DestArrayTyName) &&
- !IsDestFixed;
- bool IsCopy = isGivenLengthEqualToSrcLength(Result);
- bool IsSafe = IsOverflows;
- renameMemcpy(Name, IsCopy, IsSafe, Result, Diag);
- if (!IsSafe || (IsSafe && RemoveDestLength))
- removeArg(1, Result, Diag);
- else if (IsOverflows && isKnownDest(Result))
- lengthArgPosHandle(1, LengthHandleKind::Increase, Result, Diag);
- if (IsCopy)
- removeArg(3, Result, Diag);
- if (!IsCopy && !IsSafe)
- insertNullTerminatorExpr(Name, Result, Diag);
- }
- void NotNullTerminatedResultCheck::memchrFix(
- StringRef Name, const MatchFinder::MatchResult &Result) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- if (const auto *GivenCL = dyn_cast<CharacterLiteral>(FunctionExpr->getArg(1)))
- if (GivenCL->getValue() != 0)
- return;
- auto Diag = diag(FunctionExpr->getArg(2)->IgnoreParenCasts()->getBeginLoc(),
- "the length is too short to include the null terminator");
- if (const auto *CastExpr = Result.Nodes.getNodeAs<Expr>(CastExprName)) {
- const auto CastRemoveFix = FixItHint::CreateRemoval(
- SourceRange(CastExpr->getBeginLoc(),
- FunctionExpr->getBeginLoc().getLocWithOffset(-1)));
- Diag << CastRemoveFix;
- }
- StringRef NewFuncName = (Name[0] != 'w') ? "strchr" : "wcschr";
- renameFunc(NewFuncName, Result, Diag);
- removeArg(2, Result, Diag);
- }
- void NotNullTerminatedResultCheck::memmoveFix(
- StringRef Name, const MatchFinder::MatchResult &Result,
- DiagnosticBuilder &Diag) {
- bool IsOverflows = isDestCapacityFix(Result, Diag);
- if (UseSafeFunctions && isKnownDest(Result)) {
- renameFunc((Name[0] != 'w') ? "memmove_s" : "wmemmove_s", Result, Diag);
- insertDestCapacityArg(IsOverflows, Name, Result, Diag);
- }
- lengthArgHandle(LengthHandleKind::Increase, Result, Diag);
- }
- void NotNullTerminatedResultCheck::strerror_sFix(
- const MatchFinder::MatchResult &Result) {
- auto Diag =
- diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
- "the result from calling 'strerror_s' is not null-terminated and "
- "missing the last character of the error message");
- isDestCapacityFix(Result, Diag);
- lengthArgHandle(LengthHandleKind::Increase, Result, Diag);
- }
- void NotNullTerminatedResultCheck::ncmpFix(
- StringRef Name, const MatchFinder::MatchResult &Result) {
- const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
- const Expr *FirstArgExpr = FunctionExpr->getArg(0)->IgnoreImpCasts();
- const Expr *SecondArgExpr = FunctionExpr->getArg(1)->IgnoreImpCasts();
- bool IsLengthTooLong = false;
- if (const CallExpr *StrlenExpr = getStrlenExpr(Result)) {
- const Expr *LengthExprArg = StrlenExpr->getArg(0);
- StringRef FirstExprStr = exprToStr(FirstArgExpr, Result).trim();
- StringRef SecondExprStr = exprToStr(SecondArgExpr, Result).trim();
- StringRef LengthArgStr = exprToStr(LengthExprArg, Result).trim();
- IsLengthTooLong =
- LengthArgStr == FirstExprStr || LengthArgStr == SecondExprStr;
- } else {
- int SrcLength =
- getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);
- int GivenLength = getGivenLength(Result);
- if (SrcLength != 0 && GivenLength != 0)
- IsLengthTooLong = GivenLength > SrcLength;
- }
- if (!IsLengthTooLong && !isStringDataAndLength(Result))
- return;
- auto Diag = diag(FunctionExpr->getArg(2)->IgnoreParenCasts()->getBeginLoc(),
- "comparison length is too long and might lead to a "
- "buffer overflow");
- lengthArgHandle(LengthHandleKind::Decrease, Result, Diag);
- }
- void NotNullTerminatedResultCheck::xfrmFix(
- StringRef Name, const MatchFinder::MatchResult &Result) {
- if (!isDestCapacityOverflows(Result))
- return;
- auto Diag =
- diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
- "the result from calling '%0' is not null-terminated")
- << Name;
- isDestCapacityFix(Result, Diag);
- lengthArgHandle(LengthHandleKind::Increase, Result, Diag);
- }
- } // namespace clang::tidy::bugprone
|