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 25 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { 26 // This is a C++ only check thus we register the matchers only for C++ 27 if (!getLangOpts().CPlusPlus) 28 return; 29 30 Finder->addMatcher(cxxThrowExpr().bind("throw"), this); 31 Finder->addMatcher(cxxCatchStmt().bind("catch"), this); 32 } 33 34 void ThrowByValueCatchByReferenceCheck::storeOptions( 35 ClangTidyOptions::OptionMap &Opts) { 36 Options.store(Opts, "CheckThrowTemporaries", true); 37 } 38 39 void ThrowByValueCatchByReferenceCheck::check( 40 const MatchFinder::MatchResult &Result) { 41 diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw")); 42 diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"), 43 *Result.Context); 44 } 45 46 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( 47 const DeclRefExpr *declRefExpr) { 48 return isa<ParmVarDecl>(declRefExpr->getDecl()); 49 } 50 51 bool ThrowByValueCatchByReferenceCheck::isCatchVariable( 52 const DeclRefExpr *declRefExpr) { 53 auto *valueDecl = declRefExpr->getDecl(); 54 if (auto *varDecl = dyn_cast<VarDecl>(valueDecl)) 55 return varDecl->isExceptionVariable(); 56 return false; 57 } 58 59 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar( 60 const DeclRefExpr *declRefExpr) { 61 return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr); 62 } 63 64 void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations( 65 const CXXThrowExpr *throwExpr) { 66 if (!throwExpr) 67 return; 68 auto *subExpr = throwExpr->getSubExpr(); 69 if (!subExpr) 70 return; 71 auto qualType = subExpr->getType(); 72 if (qualType->isPointerType()) { 73 // The code is throwing a pointer. 74 // In case it is strng literal, it is safe and we return. 75 auto *inner = subExpr->IgnoreParenImpCasts(); 76 if (isa<StringLiteral>(inner)) 77 return; 78 // If it's a variable from a catch statement, we return as well. 79 auto *declRef = dyn_cast<DeclRefExpr>(inner); 80 if (declRef && isCatchVariable(declRef)) { 81 return; 82 } 83 diag(subExpr->getBeginLoc(), "throw expression throws a pointer; it should " 84 "throw a non-pointer value instead"); 85 } 86 // If the throw statement does not throw by pointer then it throws by value 87 // which is ok. 88 // There are addition checks that emit diagnosis messages if the thrown value 89 // is not an RValue. See: 90 // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries 91 // This behavior can be influenced by an option. 92 93 // If we encounter a CXXThrowExpr, we move through all casts until you either 94 // encounter a DeclRefExpr or a CXXConstructExpr. 95 // If it's a DeclRefExpr, we emit a message if the referenced variable is not 96 // a catch variable or function parameter. 97 // When encountering a CopyOrMoveConstructor: emit message if after casts, 98 // the expression is a LValue 99 if (CheckAnonymousTemporaries) { 100 bool emit = false; 101 auto *currentSubExpr = subExpr->IgnoreImpCasts(); 102 const auto *variableReference = dyn_cast<DeclRefExpr>(currentSubExpr); 103 const auto *constructorCall = dyn_cast<CXXConstructExpr>(currentSubExpr); 104 // If we have a DeclRefExpr, we flag for emitting a diagnosis message in 105 // case the referenced variable is neither a function parameter nor a 106 // variable declared in the catch statement. 107 if (variableReference) 108 emit = !isFunctionOrCatchVar(variableReference); 109 else if (constructorCall && 110 constructorCall->getConstructor()->isCopyOrMoveConstructor()) { 111 // If we have a copy / move construction, we emit a diagnosis message if 112 // the object that we copy construct from is neither a function parameter 113 // nor a variable declared in a catch statement 114 auto argIter = 115 constructorCall 116 ->arg_begin(); // there's only one for copy constructors 117 auto *currentSubExpr = (*argIter)->IgnoreImpCasts(); 118 if (currentSubExpr->isLValue()) { 119 if (auto *tmp = dyn_cast<DeclRefExpr>(currentSubExpr)) 120 emit = !isFunctionOrCatchVar(tmp); 121 else if (isa<CallExpr>(currentSubExpr)) 122 emit = true; 123 } 124 } 125 if (emit) 126 diag(subExpr->getBeginLoc(), 127 "throw expression should throw anonymous temporary values instead"); 128 } 129 } 130 131 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( 132 const CXXCatchStmt *catchStmt, ASTContext &context) { 133 if (!catchStmt) 134 return; 135 auto caughtType = catchStmt->getCaughtType(); 136 if (caughtType.isNull()) 137 return; 138 auto *varDecl = catchStmt->getExceptionDecl(); 139 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) { 140 const char *diagMsgCatchReference = "catch handler catches a pointer value; " 141 "should throw a non-pointer value and " 142 "catch by reference instead"; 143 // We do not diagnose when catching pointer to strings since we also allow 144 // throwing string literals. 145 if (!PT->getPointeeType()->isAnyCharacterType()) 146 diag(varDecl->getBeginLoc(), diagMsgCatchReference); 147 } else if (!caughtType->isReferenceType()) { 148 const char *diagMsgCatchReference = "catch handler catches by value; " 149 "should catch by reference instead"; 150 // If it's not a pointer and not a reference then it must be caught "by 151 // value". In this case we should emit a diagnosis message unless the type 152 // is trivial. 153 if (!caughtType.isTrivialType(context)) 154 diag(varDecl->getBeginLoc(), diagMsgCatchReference); 155 } 156 } 157 158 } // namespace misc 159 } // namespace tidy 160 } // namespace clang 161