| //===--- 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" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace 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(), 0); |
| } |
| |
| // 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(), 0); |
| |
| 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, Optional<unsigned> DestinationPos, |
| Optional<unsigned> SourcePos, unsigned LengthPos, |
| bool WithIncrease) |
| : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos), |
| LengthPos(LengthPos), WithIncrease(WithIncrease){}; |
| |
| StringRef Name; |
| Optional<unsigned> DestinationPos; |
| 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", None, 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", None, 1, 2, true}); |
| auto StrncmpLHS = Match({"strncmp", None, 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, None, 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__")) { |
| Optional<bool> AreSafeFunctionsWanted; |
| |
| Preprocessor::macro_iterator It = PP->macro_begin(); |
| while (It != PP->macro_end() && !AreSafeFunctionsWanted.hasValue()) { |
| 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.hasValue()) |
| UseSafeFunctions = AreSafeFunctionsWanted.getValue(); |
| } |
| |
| 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 bugprone |
| } // namespace tidy |
| } // namespace clang |