//===--- 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::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("throw")); diagnoseCatchLocations(Result.Nodes.getNodeAs("catch"), *Result.Context); } bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( const DeclRefExpr *DeclRefExpr) { return isa(DeclRefExpr->getDecl()); } bool ThrowByValueCatchByReferenceCheck::isCatchVariable( const DeclRefExpr *DeclRefExpr) { auto *ValueDecl = DeclRefExpr->getDecl(); if (auto *VarDecl = dyn_cast(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(Inner)) return; // If it's a variable from a catch statement, we return as well. auto *DeclRef = dyn_cast(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(CurrentSubExpr); const auto *ConstructorCall = dyn_cast(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(CurrentSubExpr)) Emit = !isFunctionOrCatchVar(Tmp); else if (isa(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()) { 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::max()) MaxSize = Context.getTypeSize(Context.getSizeType()); if (Context.getTypeSize(CaughtType) > MaxSize) diag(VarDecl->getBeginLoc(), DiagMsgCatchReference); } } } } // namespace clang::tidy::misc