forked from OSchip/llvm-project
176 lines
7.1 KiB
C++
176 lines
7.1 KiB
C++
//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===//
|
|
//
|
|
// 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 "ThrowByValueCatchByReferenceCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/OperationKinds.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace misc {
|
|
|
|
ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
|
|
StringRef Name, ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),
|
|
WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),
|
|
// Cannot access `ASTContext` from here so set it to an extremal value.
|
|
MaxSizeOptions(
|
|
Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),
|
|
MaxSize(MaxSizeOptions) {}
|
|
|
|
void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
|
|
Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
|
|
Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
|
|
}
|
|
|
|
void ThrowByValueCatchByReferenceCheck::storeOptions(
|
|
ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "CheckThrowTemporaries", true);
|
|
Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);
|
|
Options.store(Opts, "MaxSize", MaxSizeOptions);
|
|
}
|
|
|
|
void ThrowByValueCatchByReferenceCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
|
|
diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
|
|
*Result.Context);
|
|
}
|
|
|
|
bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
|
|
const DeclRefExpr *DeclRefExpr) {
|
|
return isa<ParmVarDecl>(DeclRefExpr->getDecl());
|
|
}
|
|
|
|
bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
|
|
const DeclRefExpr *DeclRefExpr) {
|
|
auto *ValueDecl = DeclRefExpr->getDecl();
|
|
if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl))
|
|
return VarDecl->isExceptionVariable();
|
|
return false;
|
|
}
|
|
|
|
bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
|
|
const DeclRefExpr *DeclRefExpr) {
|
|
return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);
|
|
}
|
|
|
|
void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
|
|
const CXXThrowExpr *ThrowExpr) {
|
|
if (!ThrowExpr)
|
|
return;
|
|
auto *SubExpr = ThrowExpr->getSubExpr();
|
|
if (!SubExpr)
|
|
return;
|
|
auto QualType = SubExpr->getType();
|
|
if (QualType->isPointerType()) {
|
|
// The code is throwing a pointer.
|
|
// In case it is string literal, it is safe and we return.
|
|
auto *Inner = SubExpr->IgnoreParenImpCasts();
|
|
if (isa<StringLiteral>(Inner))
|
|
return;
|
|
// If it's a variable from a catch statement, we return as well.
|
|
auto *DeclRef = dyn_cast<DeclRefExpr>(Inner);
|
|
if (DeclRef && isCatchVariable(DeclRef)) {
|
|
return;
|
|
}
|
|
diag(SubExpr->getBeginLoc(), "throw expression throws a pointer; it should "
|
|
"throw a non-pointer value instead");
|
|
}
|
|
// If the throw statement does not throw by pointer then it throws by value
|
|
// which is ok.
|
|
// There are addition checks that emit diagnosis messages if the thrown value
|
|
// is not an RValue. See:
|
|
// https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries
|
|
// This behavior can be influenced by an option.
|
|
|
|
// If we encounter a CXXThrowExpr, we move through all casts until you either
|
|
// encounter a DeclRefExpr or a CXXConstructExpr.
|
|
// If it's a DeclRefExpr, we emit a message if the referenced variable is not
|
|
// a catch variable or function parameter.
|
|
// When encountering a CopyOrMoveConstructor: emit message if after casts,
|
|
// the expression is a LValue
|
|
if (CheckAnonymousTemporaries) {
|
|
bool Emit = false;
|
|
auto *CurrentSubExpr = SubExpr->IgnoreImpCasts();
|
|
const auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);
|
|
const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(CurrentSubExpr);
|
|
// If we have a DeclRefExpr, we flag for emitting a diagnosis message in
|
|
// case the referenced variable is neither a function parameter nor a
|
|
// variable declared in the catch statement.
|
|
if (VariableReference)
|
|
Emit = !isFunctionOrCatchVar(VariableReference);
|
|
else if (ConstructorCall &&
|
|
ConstructorCall->getConstructor()->isCopyOrMoveConstructor()) {
|
|
// If we have a copy / move construction, we emit a diagnosis message if
|
|
// the object that we copy construct from is neither a function parameter
|
|
// nor a variable declared in a catch statement
|
|
auto ArgIter =
|
|
ConstructorCall
|
|
->arg_begin(); // there's only one for copy constructors
|
|
auto *CurrentSubExpr = (*ArgIter)->IgnoreImpCasts();
|
|
if (CurrentSubExpr->isLValue()) {
|
|
if (auto *Tmp = dyn_cast<DeclRefExpr>(CurrentSubExpr))
|
|
Emit = !isFunctionOrCatchVar(Tmp);
|
|
else if (isa<CallExpr>(CurrentSubExpr))
|
|
Emit = true;
|
|
}
|
|
}
|
|
if (Emit)
|
|
diag(SubExpr->getBeginLoc(),
|
|
"throw expression should throw anonymous temporary values instead");
|
|
}
|
|
}
|
|
|
|
void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
|
|
const CXXCatchStmt *CatchStmt, ASTContext &Context) {
|
|
if (!CatchStmt)
|
|
return;
|
|
auto CaughtType = CatchStmt->getCaughtType();
|
|
if (CaughtType.isNull())
|
|
return;
|
|
auto *VarDecl = CatchStmt->getExceptionDecl();
|
|
if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {
|
|
const char *DiagMsgCatchReference =
|
|
"catch handler catches a pointer value; "
|
|
"should throw a non-pointer value and "
|
|
"catch by reference instead";
|
|
// We do not diagnose when catching pointer to strings since we also allow
|
|
// throwing string literals.
|
|
if (!PT->getPointeeType()->isAnyCharacterType())
|
|
diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
|
|
} else if (!CaughtType->isReferenceType()) {
|
|
const char *DiagMsgCatchReference = "catch handler catches by value; "
|
|
"should catch by reference instead";
|
|
// If it's not a pointer and not a reference then it must be caught "by
|
|
// value". In this case we should emit a diagnosis message unless the type
|
|
// is trivial.
|
|
if (!CaughtType.isTrivialType(Context)) {
|
|
diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
|
|
} else if (WarnOnLargeObject) {
|
|
// If the type is trivial, then catching it by reference is not dangerous.
|
|
// However, catching large objects by value decreases the performance.
|
|
|
|
// We can now access `ASTContext` so if `MaxSize` is an extremal value
|
|
// then set it to the size of `size_t`.
|
|
if (MaxSize == std::numeric_limits<uint64_t>::max())
|
|
MaxSize = Context.getTypeSize(Context.getSizeType());
|
|
if (Context.getTypeSize(CaughtType) > MaxSize)
|
|
diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace misc
|
|
} // namespace tidy
|
|
} // namespace clang
|