1 //===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ThrowByValueCatchByReferenceCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/OperationKinds.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 14 using namespace clang::ast_matchers; 15 16 namespace clang { 17 namespace tidy { 18 namespace misc { 19 20 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck( 21 StringRef Name, ClangTidyContext *Context) 22 : ClangTidyCheck(Name, Context), 23 CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)), 24 WarnOnLargeObject(Options.get("WarnOnLargeObject", false)), 25 // Cannot access `ASTContext` from here so set it to an extremal value. 26 MaxSizeOptions( 27 Options.get("MaxSize", std::numeric_limits<uint64_t>::max())), 28 MaxSize(MaxSizeOptions) {} 29 30 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { 31 Finder->addMatcher(cxxThrowExpr().bind("throw"), this); 32 Finder->addMatcher(cxxCatchStmt().bind("catch"), this); 33 } 34 35 void ThrowByValueCatchByReferenceCheck::storeOptions( 36 ClangTidyOptions::OptionMap &Opts) { 37 Options.store(Opts, "CheckThrowTemporaries", true); 38 Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject); 39 Options.store(Opts, "MaxSize", MaxSizeOptions); 40 } 41 42 void ThrowByValueCatchByReferenceCheck::check( 43 const MatchFinder::MatchResult &Result) { 44 diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw")); 45 diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"), 46 *Result.Context); 47 } 48 49 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( 50 const DeclRefExpr *DeclRefExpr) { 51 return isa<ParmVarDecl>(DeclRefExpr->getDecl()); 52 } 53 54 bool ThrowByValueCatchByReferenceCheck::isCatchVariable( 55 const DeclRefExpr *DeclRefExpr) { 56 auto *ValueDecl = DeclRefExpr->getDecl(); 57 if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl)) 58 return VarDecl->isExceptionVariable(); 59 return false; 60 } 61 62 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar( 63 const DeclRefExpr *DeclRefExpr) { 64 return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr); 65 } 66 67 void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations( 68 const CXXThrowExpr *ThrowExpr) { 69 if (!ThrowExpr) 70 return; 71 auto *SubExpr = ThrowExpr->getSubExpr(); 72 if (!SubExpr) 73 return; 74 auto QualType = SubExpr->getType(); 75 if (QualType->isPointerType()) { 76 // The code is throwing a pointer. 77 // In case it is string literal, it is safe and we return. 78 auto *Inner = SubExpr->IgnoreParenImpCasts(); 79 if (isa<StringLiteral>(Inner)) 80 return; 81 // If it's a variable from a catch statement, we return as well. 82 auto *DeclRef = dyn_cast<DeclRefExpr>(Inner); 83 if (DeclRef && isCatchVariable(DeclRef)) { 84 return; 85 } 86 diag(SubExpr->getBeginLoc(), "throw expression throws a pointer; it should " 87 "throw a non-pointer value instead"); 88 } 89 // If the throw statement does not throw by pointer then it throws by value 90 // which is ok. 91 // There are addition checks that emit diagnosis messages if the thrown value 92 // is not an RValue. See: 93 // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries 94 // This behavior can be influenced by an option. 95 96 // If we encounter a CXXThrowExpr, we move through all casts until you either 97 // encounter a DeclRefExpr or a CXXConstructExpr. 98 // If it's a DeclRefExpr, we emit a message if the referenced variable is not 99 // a catch variable or function parameter. 100 // When encountering a CopyOrMoveConstructor: emit message if after casts, 101 // the expression is a LValue 102 if (CheckAnonymousTemporaries) { 103 bool Emit = false; 104 auto *CurrentSubExpr = SubExpr->IgnoreImpCasts(); 105 const auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr); 106 const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(CurrentSubExpr); 107 // If we have a DeclRefExpr, we flag for emitting a diagnosis message in 108 // case the referenced variable is neither a function parameter nor a 109 // variable declared in the catch statement. 110 if (VariableReference) 111 Emit = !isFunctionOrCatchVar(VariableReference); 112 else if (ConstructorCall && 113 ConstructorCall->getConstructor()->isCopyOrMoveConstructor()) { 114 // If we have a copy / move construction, we emit a diagnosis message if 115 // the object that we copy construct from is neither a function parameter 116 // nor a variable declared in a catch statement 117 auto ArgIter = 118 ConstructorCall 119 ->arg_begin(); // there's only one for copy constructors 120 auto *CurrentSubExpr = (*ArgIter)->IgnoreImpCasts(); 121 if (CurrentSubExpr->isLValue()) { 122 if (auto *Tmp = dyn_cast<DeclRefExpr>(CurrentSubExpr)) 123 Emit = !isFunctionOrCatchVar(Tmp); 124 else if (isa<CallExpr>(CurrentSubExpr)) 125 Emit = true; 126 } 127 } 128 if (Emit) 129 diag(SubExpr->getBeginLoc(), 130 "throw expression should throw anonymous temporary values instead"); 131 } 132 } 133 134 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( 135 const CXXCatchStmt *CatchStmt, ASTContext &Context) { 136 if (!CatchStmt) 137 return; 138 auto CaughtType = CatchStmt->getCaughtType(); 139 if (CaughtType.isNull()) 140 return; 141 auto *VarDecl = CatchStmt->getExceptionDecl(); 142 if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) { 143 const char *DiagMsgCatchReference = 144 "catch handler catches a pointer value; " 145 "should throw a non-pointer value and " 146 "catch by reference instead"; 147 // We do not diagnose when catching pointer to strings since we also allow 148 // throwing string literals. 149 if (!PT->getPointeeType()->isAnyCharacterType()) 150 diag(VarDecl->getBeginLoc(), DiagMsgCatchReference); 151 } else if (!CaughtType->isReferenceType()) { 152 const char *DiagMsgCatchReference = "catch handler catches by value; " 153 "should catch by reference instead"; 154 // If it's not a pointer and not a reference then it must be caught "by 155 // value". In this case we should emit a diagnosis message unless the type 156 // is trivial. 157 if (!CaughtType.isTrivialType(Context)) { 158 diag(VarDecl->getBeginLoc(), DiagMsgCatchReference); 159 } else if (WarnOnLargeObject) { 160 // If the type is trivial, then catching it by reference is not dangerous. 161 // However, catching large objects by value decreases the performance. 162 163 // We can now access `ASTContext` so if `MaxSize` is an extremal value 164 // then set it to the size of `size_t`. 165 if (MaxSize == std::numeric_limits<uint64_t>::max()) 166 MaxSize = Context.getTypeSize(Context.getSizeType()); 167 if (Context.getTypeSize(CaughtType) > MaxSize) 168 diag(VarDecl->getBeginLoc(), DiagMsgCatchReference); 169 } 170 } 171 } 172 173 } // namespace misc 174 } // namespace tidy 175 } // namespace clang 176