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 DeclRefExpr *variableReference = 104 dyn_cast<DeclRefExpr>(currentSubExpr); 105 const CXXConstructExpr *constructorCall = 106 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->getLocStart(), 130 "throw expression should throw anonymous temporary values instead"); 131 } 132 } 133 134 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( 135 const CXXCatchStmt *catchStmt, ASTContext &context) { 136 const char *diagMsgCatchReference = "catch handler catches a pointer value; " 137 "should throw a non-pointer value and " 138 "catch by reference instead"; 139 if (!catchStmt) 140 return; 141 auto caughtType = catchStmt->getCaughtType(); 142 if (caughtType.isNull()) 143 return; 144 auto *varDecl = catchStmt->getExceptionDecl(); 145 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) { 146 // We do not diagnose when catching pointer to strings since we also allow 147 // throwing string literals. 148 if (!PT->getPointeeType()->isAnyCharacterType()) 149 diag(varDecl->getLocStart(), diagMsgCatchReference); 150 } else if (!caughtType->isReferenceType()) { 151 // If it's not a pointer and not a reference then it must be thrown "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