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