123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- //===--- 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::tidy::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 clang::tidy::misc
|