[clang-tools-extra] r374707 - [clang-tidy] New checker for not null-terminated result caused by strlen(), size() or equal length
Csaba Dabis via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 13 01:28:28 PDT 2019
Author: charusso
Date: Sun Oct 13 01:28:27 2019
New Revision: 374707
URL: http://llvm.org/viewvc/llvm-project?rev=374707&view=rev
Log:
[clang-tidy] New checker for not null-terminated result caused by strlen(), size() or equal length
Summary:
New checker called bugprone-not-null-terminated-result. This checker finds
function calls where it is possible to cause a not null-terminated result.
Usually the proper length of a string is `strlen(src) + 1` or equal length
of this expression, because the null terminator needs an extra space.
Without the null terminator it can result in undefined behaviour when the
string is read.
The following and their respective `wchar_t` based functions are checked:
`memcpy`, `memcpy_s`, `memchr`, `memmove`, `memmove_s`, `strerror_s`,
`strncmp`, `strxfrm`
The following is a real-world example where the programmer forgot to
increase the passed third argument, which is `size_t length`.
That is why the length of the allocated memory is not enough to hold the
null terminator.
```
static char *stringCpy(const std::string &str) {
char *result = reinterpret_cast<char *>(malloc(str.size()));
memcpy(result, str.data(), str.size());
return result;
}
```
In addition to issuing warnings, fix-it rewrites all the necessary code.
It also tries to adjust the capacity of the destination array:
```
static char *stringCpy(const std::string &str) {
char *result = reinterpret_cast<char *>(malloc(str.size() + 1));
strcpy(result, str.data());
return result;
}
```
Note: It cannot guarantee to rewrite every of the path-sensitive memory
allocations.
Reviewed By: JonasToth, aaron.ballman, whisperity, alexfh
Tags: #clang-tools-extra, #clang
Differential Revision: https://reviews.llvm.org/D45050
Added:
clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp
clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.h
clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-not-null-terminated-result.rst
clang-tools-extra/trunk/test/clang-tidy/Inputs/
clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/
clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-c.h
clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-cxx.h
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-in-initialization-strlen.c
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-before-safe.c
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-cxx.cpp
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-other.c
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe.c
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-strlen.c
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wcslen.cpp
clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wmemcpy-safe-cxx.cpp
Modified:
clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp
clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt
clang-tools-extra/trunk/docs/ReleaseNotes.rst
clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst
Modified: clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp?rev=374707&r1=374706&r2=374707&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp (original)
+++ clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp Sun Oct 13 01:28:27 2019
@@ -32,6 +32,7 @@
#include "MisplacedWideningCastCheck.h"
#include "MoveForwardingReferenceCheck.h"
#include "MultipleStatementMacroCheck.h"
+#include "NotNullTerminatedResultCheck.h"
#include "ParentVirtualCallCheck.h"
#include "PosixReturnCheck.h"
#include "SizeofContainerCheck.h"
@@ -109,6 +110,8 @@ public:
"bugprone-multiple-statement-macro");
CheckFactories.registerCheck<cppcoreguidelines::NarrowingConversionsCheck>(
"bugprone-narrowing-conversions");
+ CheckFactories.registerCheck<NotNullTerminatedResultCheck>(
+ "bugprone-not-null-terminated-result");
CheckFactories.registerCheck<ParentVirtualCallCheck>(
"bugprone-parent-virtual-call");
CheckFactories.registerCheck<PosixReturnCheck>(
Modified: clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt?rev=374707&r1=374706&r2=374707&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt Sun Oct 13 01:28:27 2019
@@ -24,6 +24,7 @@ add_clang_library(clangTidyBugproneModul
MisplacedWideningCastCheck.cpp
MoveForwardingReferenceCheck.cpp
MultipleStatementMacroCheck.cpp
+ NotNullTerminatedResultCheck.cpp
ParentVirtualCallCheck.cpp
PosixReturnCheck.cpp
SizeofContainerCheck.cpp
Added: clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp (added)
+++ clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp Sun Oct 13 01:28:27 2019
@@ -0,0 +1,1006 @@
+//===--- 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"
+
+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 int 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 (dyn_cast<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 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", 1)) {}
+
+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(DestVarDeclName))))),
+ hasDescendant(declRefExpr(equalsBoundNode(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 Memcpy_s = 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 Memmove_s = 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 Strerror_s = Match({"strerror_s", 0, None, 1, false});
+
+ auto AnyOfMatchers = anyOf(Memcpy, Memcpy_s, Memmove, Memmove_s, StrncmpRHS,
+ StrncmpLHS, Strxfrm, Strerror_s);
+
+ 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);
+ const auto &T = MI->tokens().back();
+ 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
Added: clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.h?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.h (added)
+++ clang-tools-extra/trunk/clang-tidy/bugprone/NotNullTerminatedResultCheck.h Sun Oct 13 01:28:27 2019
@@ -0,0 +1,67 @@
+//===--- NotNullTerminatedResultCheck.h - 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOT_NULL_TERMINATED_RESULT_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOT_NULL_TERMINATED_RESULT_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// Finds function calls where it is possible to cause a not null-terminated
+/// result. Usually the proper length of a string is 'strlen(src) + 1' or
+/// equal length of this expression, because the null terminator needs an extra
+/// space. Without the null terminator it can result in undefined behaviour
+/// when the string is read.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-not-null-terminated-result.html
+class NotNullTerminatedResultCheck : public ClangTidyCheck {
+public:
+ NotNullTerminatedResultCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+
+private:
+ // If non-zero it is specifying if the target environment is considered to
+ // implement '_s' suffixed memory and string handler functions which are safer
+ // than older version (e.g. 'memcpy_s()'). The default value is '1'.
+ const int WantToUseSafeFunctions;
+
+ bool UseSafeFunctions = false;
+
+ void memoryHandlerFunctionFix(
+ StringRef Name, const ast_matchers::MatchFinder::MatchResult &Result);
+ void memcpyFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ DiagnosticBuilder &Diag);
+ void memcpy_sFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ DiagnosticBuilder &Diag);
+ void memchrFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+ void memmoveFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ DiagnosticBuilder &Diag);
+ void strerror_sFix(const ast_matchers::MatchFinder::MatchResult &Result);
+ void ncmpFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+ void xfrmFix(StringRef Name,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOT_NULL_TERMINATED_RESULT_H
Modified: clang-tools-extra/trunk/docs/ReleaseNotes.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/ReleaseNotes.rst?rev=374707&r1=374706&r2=374707&view=diff
==============================================================================
--- clang-tools-extra/trunk/docs/ReleaseNotes.rst (original)
+++ clang-tools-extra/trunk/docs/ReleaseNotes.rst Sun Oct 13 01:28:27 2019
@@ -79,6 +79,15 @@ Improvements to clang-tidy
Finds obvious infinite loops (loops where the condition variable is not
changed at all).
+- New :doc:`bugprone-not-null-terminated-result
+ <clang-tidy/checks/bugprone-not-null-terminated-result>` check
+
+ Finds function calls where it is possible to cause a not null-terminated
+ result. Usually the proper length of a string is ``strlen(str) + 1`` or equal
+ length of this expression, because the null terminator needs an extra space.
+ Without the null terminator it can result in undefined behaviour when the
+ string is read.
+
- New :doc:`cppcoreguidelines-init-variables
<clang-tidy/checks/cppcoreguidelines-init-variables>` check.
Added: clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-not-null-terminated-result.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-not-null-terminated-result.rst?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-not-null-terminated-result.rst (added)
+++ clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-not-null-terminated-result.rst Sun Oct 13 01:28:27 2019
@@ -0,0 +1,127 @@
+.. title:: clang-tidy - bugprone-not-null-terminated-result
+
+bugprone-not-null-terminated-result
+===================================
+
+Finds function calls where it is possible to cause a not null-terminated result.
+Usually the proper length of a string is ``strlen(src) + 1`` or equal length of
+this expression, because the null terminator needs an extra space. Without the
+null terminator it can result in undefined behaviour when the string is read.
+
+The following and their respective ``wchar_t`` based functions are checked:
+
+``memcpy``, ``memcpy_s``, ``memchr``, ``memmove``, ``memmove_s``,
+``strerror_s``, ``strncmp``, ``strxfrm``
+
+The following is a real-world example where the programmer forgot to increase
+the passed third argument, which is ``size_t length``. That is why the length
+of the allocated memory is not enough to hold the null terminator.
+
+ .. code-block:: c
+
+ static char *stringCpy(const std::string &str) {
+ char *result = reinterpret_cast<char *>(malloc(str.size()));
+ memcpy(result, str.data(), str.size());
+ return result;
+ }
+
+In addition to issuing warnings, fix-it rewrites all the necessary code. It also
+tries to adjust the capacity of the destination array:
+
+ .. code-block:: c
+
+ static char *stringCpy(const std::string &str) {
+ char *result = reinterpret_cast<char *>(malloc(str.size() + 1));
+ strcpy(result, str.data());
+ return result;
+ }
+
+Note: It cannot guarantee to rewrite every of the path-sensitive memory
+ allocations.
+
+.. _MemcpyTransformation:
+
+Transformation rules of 'memcpy()'
+----------------------------------
+
+It is possible to rewrite the ``memcpy()`` and ``memcpy_s()`` calls as the
+following four functions: ``strcpy()``, ``strncpy()``, ``strcpy_s()``,
+``strncpy_s()``, where the latter two are the safer versions of the former two.
+It rewrites the ``wchar_t`` based memory handler functions respectively.
+
+Rewrite based on the destination array
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- If copy to the destination array cannot overflow [1] the new function should
+ be the older copy function (ending with ``cpy``), because it is more
+ efficient than the safe version.
+
+- If copy to the destination array can overflow [1] and
+ ``AreSafeFunctionsAvailable`` is set to ``Yes``, ``y`` or non-zero and it is
+ possible to obtain the capacity of the destination array then the new function
+ could be the safe version (ending with ``cpy_s``).
+
+- If the new function is could be safe version and C++ files are analysed and
+ the destination array is plain ``char``/``wchar_t`` without ``un/signed`` then
+ the length of the destination array can be omitted.
+
+- If the new function is could be safe version and the destination array is
+ ``un/signed`` it needs to be casted to plain ``char *``/``wchar_t *``.
+
+[1] It is possible to overflow:
+ - If the capacity of the destination array is unknown.
+ - If the given length is equal to the destination array's capacity.
+
+Rewrite based on the length of the source string
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- If the given length is ``strlen(source)`` or equal length of this expression
+ then the new function should be the older copy function (ending with ``cpy``),
+ as it is more efficient than the safe version (ending with ``cpy_s``).
+
+- Otherwise we assume that the programmer wanted to copy 'N' characters, so the
+ new function is ``ncpy``-like which copies 'N' characters.
+
+Transformations with 'strlen()' or equal length of this expression
+------------------------------------------------------------------
+
+It transforms the ``wchar_t`` based memory and string handler functions
+respectively (where only ``strerror_s`` does not have ``wchar_t`` based alias).
+
+Memory handler functions
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``memcpy``: Visit the
+ :ref:`Transformation rules of 'memcpy()'<MemcpyTransformation>` section.
+
+- ``memchr``:
+ - Usually there is a C-style cast and it is needed to be removed, because the
+ new function ``strchr``'s return type is correct.
+ - The given length is going to be removed.
+
+- ``memmove``:
+ - If safe functions are available the new function is ``memmove_s``, which has
+ a new second argument which is the length of the destination array, it is
+ adjusted, and the length of the source string is incremented by one.
+ - If safe functions are not available the given length is incremented by one.
+
+- ``memmove_s``: given length is incremented by one.
+
+String handler functions
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``strerror_s``: given length is incremented by one.
+
+- ``strncmp``: If the third argument is the first or the second argument's
+ ``length + 1`` it has to be truncated without the ``+ 1`` operation.
+
+- ``strxfrm``: given length is incremented by one.
+
+Options
+-------
+
+.. option:: WantToUseSafeFunctions
+
+ An integer non-zero value specifying if the target environment is considered
+ to implement '_s' suffixed memory and string handler functions which are
+ safer than older versions (e.g. 'memcpy_s()'). The default value is ``1``.
Modified: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst?rev=374707&r1=374706&r2=374707&view=diff
==============================================================================
--- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst (original)
+++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Sun Oct 13 01:28:27 2019
@@ -60,6 +60,7 @@ Clang-Tidy Checks
bugprone-misplaced-widening-cast
bugprone-move-forwarding-reference
bugprone-multiple-statement-macro
+ bugprone-not-null-terminated-result
bugprone-parent-virtual-call
bugprone-posix-return
bugprone-sizeof-container
Added: clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-c.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-c.h?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-c.h (added)
+++ clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-c.h Sun Oct 13 01:28:27 2019
@@ -0,0 +1,39 @@
+//===- not-null-terminated-result-c.h - Helper header -------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This header helps to maintain every function call checked by the
+// NotNullTerminatedResult checker.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma clang system_header
+
+typedef unsigned int size_t;
+typedef int errno_t;
+
+size_t strlen(const char *str);
+void *malloc(size_t size);
+char *strerror(int errnum);
+errno_t strerror_s(char *buffer, size_t bufferSize, int errnum);
+
+char *strcpy(char *dest, const char *src);
+errno_t strcpy_s(char *dest, size_t destSize, const char *src);
+char *strncpy(char *dest, const char *src, size_t count);
+errno_t strncpy_s(char *dest, size_t destSize, const char *src, size_t count);
+
+void *memcpy(void *dest, const void *src, size_t count);
+errno_t memcpy_s(void *dest, size_t destSize, const void *src, size_t count);
+
+char *strchr(char *str, int c);
+int strncmp(const char *str1, const char *str2, size_t count);
+size_t strxfrm(char *dest, const char *src, size_t count);
+
+void *memchr(const void *buffer, int c, size_t count);
+void *memmove(void *dest, const void *src, size_t count);
+errno_t memmove_s(void *dest, size_t destSize, const void *src, size_t count);
+void *memset(void *dest, int c, size_t count);
Added: clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-cxx.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-cxx.h?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-cxx.h (added)
+++ clang-tools-extra/trunk/test/clang-tidy/Inputs/bugprone-not-null-terminated-result/not-null-terminated-result-cxx.h Sun Oct 13 01:28:27 2019
@@ -0,0 +1,65 @@
+//===- not-null-terminated-result-cxx.h - Helper header ---------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This header helps to maintain every function call checked by the
+// NotNullTerminatedResult checker.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma clang system_header
+
+#include "not-null-terminated-result-c.h"
+
+namespace std {
+template <typename T>
+struct basic_string {
+ basic_string();
+ const T *data() const;
+ unsigned long size() const;
+ unsigned long length() const;
+};
+typedef basic_string<char> string;
+} // namespace std
+
+size_t wcslen(const wchar_t *str);
+
+template <size_t size>
+char *strcpy(char (&dest)[size], const char *src);
+template <size_t size>
+wchar_t *wcscpy(wchar_t (&dest)[size], const wchar_t *src);
+wchar_t *wcscpy(wchar_t *dest, const wchar_t *src);
+
+template <size_t size>
+errno_t strcpy_s(char (&dest)[size], const char *src);
+template <size_t size>
+errno_t wcscpy_s(wchar_t (&dest)[size], const wchar_t *src);
+errno_t wcscpy_s(wchar_t *dest, size_t destSize, const wchar_t *src);
+
+template <size_t size>
+char *strncpy(char (&dest)[size], const char *src, size_t count);
+template <size_t size>
+wchar_t *wcsncpy(wchar_t (&dest)[size], const wchar_t *src, size_t count);
+wchar_t *wcsncpy(wchar_t *dest, const wchar_t *src, size_t count);
+
+template <size_t size>
+errno_t strncpy_s(char (&dest)[size], const char *src, size_t count);
+template <size_t size>
+errno_t wcsncpy_s(wchar_t (&dest)[size], const wchar_t *src, size_t length);
+errno_t wcsncpy_s(wchar_t *dest, size_t destSize, const wchar_t *src, size_t c);
+
+wchar_t *wmemcpy(wchar_t *dest, const wchar_t *src, size_t count);
+errno_t wmemcpy_s(wchar_t *dest, size_t destSize, const wchar_t *src, size_t c);
+
+wchar_t *wcschr(const wchar_t *str, int c);
+int wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t count);
+size_t wcsxfrm(wchar_t *dest, const wchar_t *src, size_t count);
+
+void *wmemchr(const void *buffer, int c, size_t count);
+void *wmemmove(void *dest, const void *src, size_t count);
+errno_t wmemmove_s(void *dest, size_t destSize, const void *src, size_t count);
+void *wmemset(void *dest, int c, size_t count);
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-in-initialization-strlen.c
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-in-initialization-strlen.c?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-in-initialization-strlen.c (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-in-initialization-strlen.c Sun Oct 13 01:28:27 2019
@@ -0,0 +1,84 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-c.h"
+
+void path_sensitive_unknown_length(char *position, const char *src) {
+ int length;
+ length = strlen(src);
+ position = (char *)memchr(src, '\0', length);
+}
+
+void bad_memchr(char *position, const char *src) {
+ int length = strlen(src);
+ position = (char *)memchr(src, '\0', length);
+ // CHECK-MESSAGES: :[[@LINE-1]]:40: warning: the length is too short to include the null terminator [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: position = strchr(src, '\0');
+}
+
+void good_memchr(char *pos, const char *src) {
+ pos = strchr(src, '\0');
+}
+
+void bad_strerror_s(int errno) {
+ char dest[13];
+ int length = strlen(strerror(errno));
+ strerror_s(dest, length, errno);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'strerror_s' is not null-terminated and missing the last character of the error message [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest[14];
+ // CHECK-FIXES-NEXT: int length = strlen(strerror(errno));
+ // CHECK-FIXES-NEXT: strerror_s(dest, length + 1, errno);
+}
+
+void good_strerror_s(int errno) {
+ char dst[14];
+ int length = strlen(strerror(errno));
+ strerror_s(dst, length + 1, errno);
+}
+
+int bad_strncmp_1(char *str1, const char *str2) {
+ int length = strlen(str1) + 1;
+ return strncmp(str1, str2, length);
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str1, str2, length - 1);
+}
+
+int good_strncmp_1(char *str1, const char *str2) {
+ int length = strlen(str1) + 1;
+ return strncmp(str1, str2, length - 1);
+}
+
+int bad_strncmp_2(char *str2) {
+ return strncmp(str2, "foobar", (strlen("foobar") + 1));
+ // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str2, "foobar", (strlen("foobar")));
+}
+
+int bad_strncmp_3(char *str3) {
+ return strncmp(str3, "foobar", 1 + strlen("foobar"));
+ // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str3, "foobar", strlen("foobar"));
+}
+
+int good_strncmp_2_3(char *str) {
+ return strncmp(str, "foobar", strlen("foobar"));
+}
+
+void bad_strxfrm(const char *long_source_name) {
+ char long_destination_name[13];
+ int very_long_length_definition_name = strlen(long_source_name);
+ strxfrm(long_destination_name, long_source_name,
+ very_long_length_definition_name);
+ // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: the result from calling 'strxfrm' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char long_destination_name[14];
+ // CHECK-FIXES-NEXT: int very_long_length_definition_name = strlen(long_source_name);
+ // CHECK-FIXES-NEXT: strxfrm(long_destination_name, long_source_name,
+ // CHECK-FIXES-NEXT: very_long_length_definition_name + 1);
+}
+
+void good_strxfrm(const char *long_source_name) {
+ char long_destination_name[14];
+ int very_long_length_definition_name = strlen(long_source_name);
+ strxfrm(long_destination_name, long_source_name,
+ very_long_length_definition_name + 1);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-before-safe.c
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-before-safe.c?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-before-safe.c (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-before-safe.c Sun Oct 13 01:28:27 2019
@@ -0,0 +1,71 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -config="{CheckOptions: \
+// RUN: [{key: bugprone-not-null-terminated-result.WantToUseSafeFunctions, \
+// RUN: value: 1}]}" \
+// RUN: -- -std=c11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-c.h"
+
+// The following is not defined therefore the safe functions are unavailable.
+// #define __STDC_LIB_EXT1__ 1
+
+#define __STDC_WANT_LIB_EXT1__ 1
+
+//===----------------------------------------------------------------------===//
+// memcpy() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dest00[13];
+ memcpy(dest00, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: unsigned char dest00[14];
+ // CHECK-FIXES-NEXT: strcpy((char *)dest00, src);
+}
+
+void good_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dst00[14];
+ strcpy((char *)dst00, src);
+}
+
+void bad_memcpy_known_dest(const char *src) {
+ char dest01[13];
+ memcpy(dest01, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy(dest01, src);
+}
+
+void good_memcpy_known_dest(const char *src) {
+ char dst01[13];
+ strcpy(dst01, src);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_full_source_length(const char *src) {
+ char dest20[13];
+ memcpy(dest20, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy(dest20, src);
+}
+
+void good_memcpy_full_source_length(const char *src) {
+ char dst20[13];
+ strcpy(dst20, src);
+}
+
+void bad_memcpy_partial_source_length(const char *src) {
+ char dest21[13];
+ memcpy(dest21, src, strlen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncpy(dest21, src, strlen(src) - 1);
+ // CHECK-FIXES-NEXT: dest21[strlen(src) - 1] = '\0';
+}
+
+void good_memcpy_partial_source_length(const char *src) {
+ char dst21[13];
+ strncpy(dst21, src, strlen(src) - 1);
+ dst21[strlen(src) - 1] = '\0';
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-cxx.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-cxx.cpp?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-cxx.cpp (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-cxx.cpp Sun Oct 13 01:28:27 2019
@@ -0,0 +1,124 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c++11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-cxx.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+//===----------------------------------------------------------------------===//
+// memcpy() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dest00[13];
+ memcpy(dest00, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: unsigned char dest00[14];
+ // CHECK-FIXES-NEXT: strcpy_s((char *)dest00, 14, src);
+}
+
+void good_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dst00[14];
+ strcpy_s((char *)dst00, 14, src);
+}
+
+void bad_memcpy_known_dest(const char *src) {
+ char dest01[13];
+ memcpy(dest01, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: dest01[14];
+ // CHECK-FIXES-NEXT: strcpy_s(dest01, src);
+}
+
+void good_memcpy_known_dest(const char *src) {
+ char dst01[14];
+ strcpy_s(dst01, src);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_full_source_length(std::string src) {
+ char *dest20 = reinterpret_cast<char *>(malloc(src.size()));
+ memcpy(dest20, src.data(), src.size());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: dest20 = reinterpret_cast<char *>(malloc(src.size() + 1));
+ // CHECK-FIXES-NEXT: strcpy(dest20, src.data());
+}
+
+void good_memcpy_full_source_length(std::string src) {
+ char dst20[14];
+ strcpy_s(dst20, src.data());
+}
+
+void bad_memcpy_partial_source_length(const char *src) {
+ char dest21[13];
+ memcpy(dest21, src, strlen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest21[14];
+ // CHECK-FIXES-NEXT: strncpy_s(dest21, src, strlen(src) - 1);
+}
+
+void good_memcpy_partial_source_length(const char *src) {
+ char dst21[14];
+ strncpy_s(dst21, src, strlen(src) - 1);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy_s() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_s_unknown_dest(char *dest40, const char *src) {
+ memcpy_s(dest40, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy_s(dest40, 13, src);
+}
+
+void good_memcpy_s_unknown_dest(char *dst40, const char *src) {
+ strcpy_s(dst40, 13, src);
+}
+
+void bad_memcpy_s_known_dest(const char *src) {
+ char dest41[13];
+ memcpy_s(dest41, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest41[14];
+ // CHECK-FIXES: strcpy_s(dest41, src);
+}
+
+void good_memcpy_s_known_dest(const char *src) {
+ char dst41[14];
+ strcpy_s(dst41, src);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy_s() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_s_full_source_length(const char *src) {
+ char dest60[13];
+ memcpy_s(dest60, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest60[14];
+ // CHECK-FIXES-NEXT: strcpy_s(dest60, src);
+}
+
+void good_memcpy_s_full_source_length(const char *src) {
+ char dst60[14];
+ strcpy_s(dst60, src);
+}
+
+void bad_memcpy_s_partial_source_length(const char *src) {
+ char dest61[13];
+ memcpy_s(dest61, 13, src, strlen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest61[14];
+ // CHECK-FIXES-NEXT: strncpy_s(dest61, src, strlen(src) - 1);
+}
+
+void good_memcpy_s_partial_source_length(const char *src) {
+ char dst61[14];
+ strncpy_s(dst61, src, strlen(src) - 1);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-other.c
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-other.c?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-other.c (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe-other.c Sun Oct 13 01:28:27 2019
@@ -0,0 +1,112 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-c.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+#define SRC_LENGTH 3
+#define SRC "foo"
+
+//===----------------------------------------------------------------------===//
+// False positive suppression.
+//===----------------------------------------------------------------------===//
+
+void good_memcpy_known_src() {
+ char dest[13];
+ char src[] = "foobar";
+ memcpy(dest, src, sizeof(src));
+}
+
+void good_memcpy_null_terminated(const char *src) {
+ char dest[13];
+ const int length = strlen(src);
+ memcpy(dest, src, length);
+ dest[length] = '\0';
+}
+
+void good_memcpy_proper_length(const char *src) {
+ char *dest = 0;
+ int length = strlen(src) + 1;
+ dest = (char *)malloc(length);
+ memcpy(dest, src, length);
+}
+
+void may_bad_memcpy_unknown_length(const char *src, int length) {
+ char dest[13];
+ memcpy(dest, src, length);
+}
+
+void may_bad_memcpy_const_length(const char *src) {
+ char dest[13];
+ memcpy(dest, src, 12);
+}
+
+//===----------------------------------------------------------------------===//
+// Special cases.
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_unknown_dest(char *dest01, const char *src) {
+ memcpy(dest01, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy(dest01, src);
+}
+
+void good_memcpy_unknown_dest(char *dst01, const char *src) {
+ strcpy(dst01, src);
+}
+
+void bad_memcpy_variable_array(int dest_length) {
+ char dest02[dest_length + 1];
+ memcpy(dest02, "foobarbazqux", strlen("foobarbazqux"));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy(dest02, "foobarbazqux");
+}
+
+void good_memcpy_variable_array(int dest_length) {
+ char dst02[dest_length + 1];
+ strcpy(dst02, "foobarbazqux");
+}
+
+void bad_memcpy_equal_src_length_and_length() {
+ char dest03[13];
+ const char *src = "foobarbazqux";
+ memcpy(dest03, src, 12);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy(dest03, src);
+}
+
+void good_memcpy_equal_src_length_and_length() {
+ char dst03[13];
+ const char *src = "foobarbazqux";
+ strcpy(dst03, src);
+}
+
+void bad_memcpy_dest_size_overflows(const char *src) {
+ const int length = strlen(src);
+ char *dest04 = (char *)malloc(length);
+ memcpy(dest04, src, length);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char *dest04 = (char *)malloc(length + 1);
+ // CHECK-FIXES-NEXT: strcpy(dest04, src);
+}
+
+void good_memcpy_dest_size_overflows(const char *src) {
+ const int length = strlen(src);
+ char *dst04 = (char *)malloc(length + 1);
+ strcpy(dst04, src);
+}
+
+void bad_memcpy_macro() {
+ char dest05[SRC_LENGTH];
+ memcpy(dest05, SRC, SRC_LENGTH);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest05[SRC_LENGTH + 1];
+ // CHECK-FIXES-NEXT: strcpy(dest05, SRC);
+}
+
+void good_memcpy_macro() {
+ char dst05[SRC_LENGTH + 1];
+ strcpy(dst05, SRC);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe.c
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe.c?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe.c (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-memcpy-safe.c Sun Oct 13 01:28:27 2019
@@ -0,0 +1,124 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-c.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+//===----------------------------------------------------------------------===//
+// memcpy() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dest00[13];
+ memcpy(dest00, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: unsigned char dest00[14];
+ // CHECK-FIXES-NEXT: strcpy_s((char *)dest00, 14, src);
+}
+
+void good_memcpy_not_just_char_dest(const char *src) {
+ unsigned char dst00[14];
+ strcpy_s((char *)dst00, 14, src);
+}
+
+void bad_memcpy_known_dest(const char *src) {
+ char dest01[13];
+ memcpy(dest01, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest01[14];
+ // CHECK-FIXES: strcpy_s(dest01, 14, src);
+}
+
+void good_memcpy_known_dest(const char *src) {
+ char dst01[14];
+ strcpy_s(dst01, 14, src);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_full_source_length(const char *src) {
+ char dest20[13];
+ memcpy(dest20, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest20[14];
+ // CHECK-FIXES-NEXT: strcpy_s(dest20, 14, src);
+}
+
+void good_memcpy_full_source_length(const char *src) {
+ char dst20[14];
+ strcpy_s(dst20, 14, src);
+}
+
+void bad_memcpy_partial_source_length(const char *src) {
+ char dest21[13];
+ memcpy(dest21, src, strlen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest21[14];
+ // CHECK-FIXES-NEXT: strncpy_s(dest21, 14, src, strlen(src) - 1);
+}
+
+void good__memcpy_partial_source_length(const char *src) {
+ char dst21[14];
+ strncpy_s(dst21, 14, src, strlen(src) - 1);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy_s() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_s_unknown_dest(char *dest40, const char *src) {
+ memcpy_s(dest40, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strcpy_s(dest40, 13, src);
+}
+
+void good_memcpy_s_unknown_dest(char *dst40, const char *src) {
+ strcpy_s(dst40, 13, src);
+}
+
+void bad_memcpy_s_known_dest(const char *src) {
+ char dest41[13];
+ memcpy_s(dest41, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest41[14];
+ // CHECK-FIXES-NEXT: strcpy_s(dest41, 14, src);
+}
+
+void good_memcpy_s_known_dest(const char *src) {
+ char dst41[14];
+ strcpy_s(dst41, 14, src);
+}
+
+//===----------------------------------------------------------------------===//
+// memcpy_s() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_memcpy_s_full_source_length(const char *src) {
+ char dest60[13];
+ memcpy_s(dest60, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest60[14];
+ // CHECK-FIXES-NEXT: strcpy_s(dest60, 14, src);
+}
+
+void good_memcpy_s_full_source_length(const char *src) {
+ char dst60[14];
+ strcpy_s(dst60, 14, src);
+}
+
+void bad_memcpy_s_partial_source_length(const char *src) {
+ char dest61[13];
+ memcpy_s(dest61, 13, src, strlen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest61[14];
+ // CHECK-FIXES-NEXT: strncpy_s(dest61, 14, src, strlen(src) - 1);
+}
+
+void good_memcpy_s_partial_source_length(const char *src) {
+ char dst61[14];
+ strncpy_s(dst61, 14, src, strlen(src) - 1);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-strlen.c
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-strlen.c?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-strlen.c (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-strlen.c Sun Oct 13 01:28:27 2019
@@ -0,0 +1,118 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-c.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+void bad_memchr_1(char *position, const char *src) {
+ position = (char *)memchr(src, '\0', strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:40: warning: the length is too short to include the null terminator [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: position = strchr(src, '\0');
+}
+
+void good_memchr_1(char *pos, const char *src) {
+ pos = strchr(src, '\0');
+}
+
+void bad_memchr_2(char *position) {
+ position = (char *)memchr("foobar", '\0', 6);
+ // CHECK-MESSAGES: :[[@LINE-1]]:45: warning: the length is too short to include the null terminator [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: position = strchr("foobar", '\0');
+}
+
+void good_memchr_2(char *pos) {
+ pos = strchr("foobar", '\0');
+}
+
+void bad_memmove(const char *src) {
+ char dest[13];
+ memmove(dest, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memmove' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest[14];
+ // CHECK-FIXES-NEXT: memmove_s(dest, 14, src, strlen(src) + 1);
+}
+
+void good_memmove(const char *src) {
+ char dst[14];
+ memmove_s(dst, 13, src, strlen(src) + 1);
+}
+
+void bad_memmove_s(char *dest, const char *src) {
+ memmove_s(dest, 13, src, strlen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'memmove_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: memmove_s(dest, 13, src, strlen(src) + 1);
+}
+
+void good_memmove_s_1(char *dest, const char *src) {
+ memmove_s(dest, 13, src, strlen(src) + 1);
+}
+
+void bad_strerror_s(int errno) {
+ char dest[13];
+ strerror_s(dest, strlen(strerror(errno)), errno);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'strerror_s' is not null-terminated and missing the last character of the error message [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char dest[14];
+ // CHECK-FIXES-NEXT: strerror_s(dest, strlen(strerror(errno)) + 1, errno);
+}
+
+void good_strerror_s(int errno) {
+ char dst[14];
+ strerror_s(dst, strlen(strerror(errno)) + 1, errno);
+}
+
+int bad_strncmp_1(char *str0, const char *str1) {
+ return strncmp(str0, str1, (strlen(str0) + 1));
+ // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str0, str1, (strlen(str0)));
+}
+
+int bad_strncmp_2(char *str2, const char *str3) {
+ return strncmp(str2, str3, 1 + strlen(str2));
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str2, str3, strlen(str2));
+}
+
+int good_strncmp_1_2(char *str4, const char *str5) {
+ return strncmp(str4, str5, strlen(str4));
+}
+
+int bad_strncmp_3(char *str6) {
+ return strncmp(str6, "string", 7);
+ // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: strncmp(str6, "string", 6);
+}
+
+int good_strncmp_3(char *str7) {
+ return strncmp(str7, "string", 6);
+}
+
+void bad_strxfrm_1(const char *long_source_name) {
+ char long_destination_array_name[13];
+ strxfrm(long_destination_array_name, long_source_name,
+ strlen(long_source_name));
+ // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: the result from calling 'strxfrm' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char long_destination_array_name[14];
+ // CHECK-FIXES-NEXT: strxfrm(long_destination_array_name, long_source_name,
+ // CHECK-FIXES-NEXT: strlen(long_source_name) + 1);
+}
+
+void good_strxfrm_1(const char *long_source_name) {
+ char long_destination_array_name[14];
+ strxfrm(long_destination_array_name, long_source_name,
+ strlen(long_source_name) + 1);
+}
+
+void bad_strxfrm_2() {
+ char long_destination_array_name1[16];
+ strxfrm(long_destination_array_name1, "long_source_name", 16);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'strxfrm' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: char long_destination_array_name1[17];
+ // CHECK-FIXES: strxfrm(long_destination_array_name1, "long_source_name", 17);
+}
+
+void good_strxfrm_2() {
+ char long_destination_array_name2[17];
+ strxfrm(long_destination_array_name2, "long_source_name", 17);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wcslen.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wcslen.cpp?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wcslen.cpp (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wcslen.cpp Sun Oct 13 01:28:27 2019
@@ -0,0 +1,106 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c++11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-cxx.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+void bad_wmemchr_1(wchar_t *position, const wchar_t *src) {
+ position = (wchar_t *)wmemchr(src, L'\0', wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:45: warning: the length is too short to include the null terminator [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: position = wcschr(src, L'\0');
+}
+
+void good_wmemchr_1(wchar_t *pos, const wchar_t *src) {
+ pos = wcschr(src, L'\0');
+}
+
+void bad_wmemchr_2(wchar_t *position) {
+ position = (wchar_t *)wmemchr(L"foobar", L'\0', 6);
+ // CHECK-MESSAGES: :[[@LINE-1]]:51: warning: the length is too short to include the null terminator [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: position = wcschr(L"foobar", L'\0');
+}
+
+void good_wmemchr_2(wchar_t *pos) {
+ pos = wcschr(L"foobar", L'\0');
+}
+
+
+void bad_wmemmove(const wchar_t *src) {
+ wchar_t dest[13];
+ wmemmove(dest, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemmove' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest[14];
+ // CHECK-FIXES-NEXT: wmemmove_s(dest, 14, src, wcslen(src) + 1);
+}
+
+void good_wmemmove(const wchar_t *src) {
+ wchar_t dst[14];
+ wmemmove_s(dst, 13, src, wcslen(src) + 1);
+}
+
+void bad_wmemmove_s(wchar_t *dest, const wchar_t *src) {
+ wmemmove_s(dest, 13, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemmove_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wmemmove_s(dest, 13, src, wcslen(src) + 1);
+}
+
+void good_wmemmove_s_1(wchar_t *dest, const wchar_t *src) {
+ wmemmove_s(dest, 13, src, wcslen(src) + 1);
+}
+
+int bad_wcsncmp_1(wchar_t *wcs0, const wchar_t *wcs1) {
+ return wcsncmp(wcs0, wcs1, (wcslen(wcs0) + 1));
+ // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wcsncmp(wcs0, wcs1, (wcslen(wcs0)));
+}
+
+int bad_wcsncmp_2(wchar_t *wcs2, const wchar_t *wcs3) {
+ return wcsncmp(wcs2, wcs3, 1 + wcslen(wcs2));
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wcsncmp(wcs2, wcs3, wcslen(wcs2));
+}
+
+int good_wcsncmp_1_2(wchar_t *wcs4, const wchar_t *wcs5) {
+ return wcsncmp(wcs4, wcs5, wcslen(wcs4));
+}
+
+int bad_wcsncmp_3(wchar_t *wcs6) {
+ return wcsncmp(wcs6, L"string", 7);
+ // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: comparison length is too long and might lead to a buffer overflow [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wcsncmp(wcs6, L"string", 6);
+}
+
+int good_wcsncmp_3(wchar_t *wcs7) {
+ return wcsncmp(wcs7, L"string", 6);
+}
+
+void bad_wcsxfrm_1(const wchar_t *long_source_name) {
+ wchar_t long_destination_array_name[13];
+ wcsxfrm(long_destination_array_name, long_source_name,
+ wcslen(long_source_name));
+ // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: the result from calling 'wcsxfrm' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t long_destination_array_name[14];
+ // CHECK-FIXES-NEXT: wcsxfrm(long_destination_array_name, long_source_name,
+ // CHECK-FIXES-NEXT: wcslen(long_source_name) + 1);
+}
+
+void good_wcsxfrm_1(const wchar_t *long_source_name) {
+ wchar_t long_destination_array_name[14];
+ wcsxfrm(long_destination_array_name, long_source_name,
+ wcslen(long_source_name) + 1);
+}
+
+void bad_wcsxfrm_2() {
+ wchar_t long_destination_array_name1[16];
+ wcsxfrm(long_destination_array_name1, L"long_source_name", 16);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wcsxfrm' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t long_destination_array_name1[17];
+ // CHECK-FIXES: wcsxfrm(long_destination_array_name1, L"long_source_name", 17);
+}
+
+void good_wcsxfrm_2() {
+ wchar_t long_destination_array_name2[17];
+ wcsxfrm(long_destination_array_name2, L"long_source_name", 17);
+}
Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wmemcpy-safe-cxx.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wmemcpy-safe-cxx.cpp?rev=374707&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wmemcpy-safe-cxx.cpp (added)
+++ clang-tools-extra/trunk/test/clang-tidy/bugprone-not-null-terminated-result-wmemcpy-safe-cxx.cpp Sun Oct 13 01:28:27 2019
@@ -0,0 +1,111 @@
+// RUN: %check_clang_tidy %s bugprone-not-null-terminated-result %t -- \
+// RUN: -- -std=c++11 -I %S/Inputs/bugprone-not-null-terminated-result
+
+#include "not-null-terminated-result-cxx.h"
+
+#define __STDC_LIB_EXT1__ 1
+#define __STDC_WANT_LIB_EXT1__ 1
+
+//===----------------------------------------------------------------------===//
+// wmemcpy() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_wmemcpy_known_dest(const wchar_t *src) {
+ wchar_t dest01[13];
+ wmemcpy(dest01, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest01[14];
+ // CHECK-FIXES-NEXT: wcscpy_s(dest01, src);
+}
+
+void good_wmemcpy_known_dest(const wchar_t *src) {
+ wchar_t dst01[14];
+ wcscpy_s(dst01, src);
+}
+
+//===----------------------------------------------------------------------===//
+// wmemcpy() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_wmemcpy_full_source_length(const wchar_t *src) {
+ wchar_t dest20[13];
+ wmemcpy(dest20, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest20[14];
+ // CHECK-FIXES-NEXT: wcscpy_s(dest20, src);
+}
+
+void good_wmemcpy_full_source_length(const wchar_t *src) {
+ wchar_t dst20[14];
+ wcscpy_s(dst20, src);
+}
+
+void bad_wmemcpy_partial_source_length(const wchar_t *src) {
+ wchar_t dest21[13];
+ wmemcpy(dest21, src, wcslen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest21[14];
+ // CHECK-FIXES-NEXT: wcsncpy_s(dest21, src, wcslen(src) - 1);
+}
+
+void good_wmemcpy_partial_source_length(const wchar_t *src) {
+ wchar_t dst21[14];
+ wcsncpy_s(dst21, src, wcslen(src) - 1);
+}
+
+//===----------------------------------------------------------------------===//
+// wmemcpy_s() - destination array tests
+//===----------------------------------------------------------------------===//
+
+void bad_wmemcpy_s_unknown_dest(wchar_t *dest40, const wchar_t *src) {
+ wmemcpy_s(dest40, 13, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wcscpy_s(dest40, 13, src);
+}
+
+void good_wmemcpy_s_unknown_dest(wchar_t *dst40, const wchar_t *src) {
+ wcscpy_s(dst40, 13, src);
+}
+
+void bad_wmemcpy_s_known_dest(const wchar_t *src) {
+ wchar_t dest41[13];
+ wmemcpy_s(dest41, 13, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest41[14];
+ // CHECK-FIXES-NEXT: wcscpy_s(dest41, src);
+}
+
+void good_wmemcpy_s_known_dest(const wchar_t *src) {
+ wchar_t dst41[13];
+ wcscpy_s(dst41, src);
+}
+
+//===----------------------------------------------------------------------===//
+// wmemcpy_s() - length tests
+//===----------------------------------------------------------------------===//
+
+void bad_wmemcpy_s_full_source_length(const wchar_t *src) {
+ wchar_t dest60[13];
+ wmemcpy_s(dest60, 13, src, wcslen(src));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest60[14];
+ // CHECK-FIXES-NEXT: wcscpy_s(dest60, src);
+}
+
+void good_wmemcpy_s_full_source_length(const wchar_t *src) {
+ wchar_t dst60[13];
+ wcscpy_s(dst60, src);
+}
+
+void bad_wmemcpy_s_partial_source_length(const wchar_t *src) {
+ wchar_t dest61[13];
+ wmemcpy_s(dest61, 13, src, wcslen(src) - 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the result from calling 'wmemcpy_s' is not null-terminated [bugprone-not-null-terminated-result]
+ // CHECK-FIXES: wchar_t dest61[14];
+ // CHECK-FIXES-NEXT: wcsncpy_s(dest61, src, wcslen(src) - 1);
+}
+
+void good_wmemcpy_s_partial_source_length(const wchar_t *src) {
+ wchar_t dst61[13];
+ wcsncpy_s(dst61, src, wcslen(src) - 1);
+}
More information about the cfe-commits
mailing list