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/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/AST/OperationKinds.h" 14 15 using namespace clang::ast_matchers; 16 17 namespace clang { 18 namespace tidy { 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->getLocStart(), "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 DeclRefExpr *variableReference = 103 dyn_cast<DeclRefExpr>(currentSubExpr); 104 const CXXConstructExpr *constructorCall = 105 dyn_cast<CXXConstructExpr>(currentSubExpr); 106 // If we have a DeclRefExpr, we flag for emitting a diagnosis message in 107 // case the referenced variable is neither a function parameter nor a 108 // variable declared in the catch statement. 109 if (variableReference) 110 emit = !isFunctionOrCatchVar(variableReference); 111 else if (constructorCall && 112 constructorCall->getConstructor()->isCopyOrMoveConstructor()) { 113 // If we have a copy / move construction, we emit a diagnosis message if 114 // the object that we copy construct from is neither a function parameter 115 // nor a variable declared in a catch statement 116 auto argIter = 117 constructorCall 118 ->arg_begin(); // there's only one for copy constructors 119 auto *currentSubExpr = (*argIter)->IgnoreImpCasts(); 120 if (currentSubExpr->isLValue()) { 121 if (auto *tmp = dyn_cast<DeclRefExpr>(currentSubExpr)) 122 emit = !isFunctionOrCatchVar(tmp); 123 else if (isa<CallExpr>(currentSubExpr)) 124 emit = true; 125 } 126 } 127 if (emit) 128 diag(subExpr->getLocStart(), 129 "throw expression should throw anonymous temporary values instead"); 130 } 131 } 132 133 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( 134 const CXXCatchStmt *catchStmt, ASTContext &context) { 135 const char *diagMsgCatchReference = "catch handler catches a pointer value; " 136 "should throw a non-pointer value and " 137 "catch by reference instead"; 138 if (!catchStmt) 139 return; 140 auto caughtType = catchStmt->getCaughtType(); 141 if (caughtType.isNull()) 142 return; 143 auto *varDecl = catchStmt->getExceptionDecl(); 144 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) { 145 // We do not diagnose when catching pointer to strings since we also allow 146 // throwing string literals. 147 if (!PT->getPointeeType()->isAnyCharacterType()) 148 diag(varDecl->getLocStart(), diagMsgCatchReference); 149 } else if (!caughtType->isReferenceType()) { 150 // If it's not a pointer and not a reference then it must be thrown "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->getLocStart(), diagMsgCatchReference); 155 } 156 } 157 158 } // namespace tidy 159 } // namespace clang 160