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