diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp new file mode 100644 index 00000000000..3fbbe8ea860 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp @@ -0,0 +1,159 @@ +//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrowByValueCatchByReferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/AST/OperationKinds.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {} + +void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) { + // This is a C++ only check thus we register the matchers only for C++ + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxThrowExpr().bind("throw"), this); + Finder->addMatcher(cxxCatchStmt().bind("catch"), this); +} + +void ThrowByValueCatchByReferenceCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckThrowTemporaries", true); +} + +void ThrowByValueCatchByReferenceCheck::check( + const MatchFinder::MatchResult &Result) { + diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw")); + diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"), + *Result.Context); +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionParameter( + const DeclRefExpr *declRefExpr) { + return isa<ParmVarDecl>(declRefExpr->getDecl()); +} + +bool ThrowByValueCatchByReferenceCheck::isCatchVariable( + const DeclRefExpr *declRefExpr) { + auto *valueDecl = declRefExpr->getDecl(); + if (auto *varDecl = dyn_cast<VarDecl>(valueDecl)) + return varDecl->isExceptionVariable(); + return false; +} + +bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar( + const DeclRefExpr *declRefExpr) { + return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr); +} + +void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations( + const CXXThrowExpr *throwExpr) { + if (!throwExpr) + return; + auto *subExpr = throwExpr->getSubExpr(); + if (!subExpr) + return; + auto qualType = subExpr->getType(); + if (qualType->isPointerType()) { + // The code is throwing a pointer. + // In case it is strng literal, it is safe and we return. + auto *inner = subExpr->IgnoreParenImpCasts(); + if (isa<StringLiteral>(inner)) + return; + // If it's a variable from a catch statement, we return as well. + auto *declRef = dyn_cast<DeclRefExpr>(inner); + if (declRef && isCatchVariable(declRef)) { + return; + } + diag(subExpr->getLocStart(), "throw expression throws a pointer; it should " + "throw a non-pointer value instead"); + } + // If the throw statement does not throw by pointer then it throws by value + // which is ok. + // There are addition checks that emit diagnosis messages if the thrown value + // is not an RValue. See: + // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries + // This behavior can be influenced by an option. + + // If we encounter a CXXThrowExpr, we move through all casts until you either + // encounter a DeclRefExpr or a CXXConstructExpr. + // If it's a DeclRefExpr, we emit a message if the referenced variable is not + // a catch variable or function parameter. + // When encountering a CopyOrMoveConstructor: emit message if after casts, + // the expression is a LValue + if (CheckAnonymousTemporaries) { + bool emit = false; + auto *currentSubExpr = subExpr->IgnoreImpCasts(); + const DeclRefExpr *variableReference = + dyn_cast<DeclRefExpr>(currentSubExpr); + const CXXConstructExpr *constructorCall = + dyn_cast<CXXConstructExpr>(currentSubExpr); + // If we have a DeclRefExpr, we flag for emitting a diagnosis message in + // case the referenced variable is neither a function parameter nor a + // variable declared in the catch statement. + if (variableReference) + emit = !isFunctionOrCatchVar(variableReference); + else if (constructorCall && + constructorCall->getConstructor()->isCopyOrMoveConstructor()) { + // If we have a copy / move construction, we emit a diagnosis message if + // the object that we copy construct from is neither a function parameter + // nor a variable declared in a catch statement + auto argIter = + constructorCall + ->arg_begin(); // there's only one for copy constructors + auto *currentSubExpr = (*argIter)->IgnoreImpCasts(); + if (currentSubExpr->isLValue()) { + if (auto *tmp = dyn_cast<DeclRefExpr>(currentSubExpr)) + emit = !isFunctionOrCatchVar(tmp); + else if (isa<CallExpr>(currentSubExpr)) + emit = true; + } + } + if (emit) + diag(subExpr->getLocStart(), + "throw expression should throw anonymous temporary values instead"); + } +} + +void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations( + const CXXCatchStmt *catchStmt, ASTContext &context) { + const char *diagMsgCatchReference = "catch handler catches a pointer value; " + "should throw a non-pointer value and " + "catch by reference instead"; + if (!catchStmt) + return; + auto caughtType = catchStmt->getCaughtType(); + if (caughtType.isNull()) + return; + auto *varDecl = catchStmt->getExceptionDecl(); + if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) { + // We do not diagnose when catching pointer to strings since we also allow + // throwing string literals. + if (!PT->getPointeeType()->isAnyCharacterType()) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } else if (!caughtType->isReferenceType()) { + // If it's not a pointer and not a reference then it must be thrown "by + // value". In this case we should emit a diagnosis message unless the type + // is trivial. + if (!caughtType.isTrivialType(context)) + diag(varDecl->getLocStart(), diagMsgCatchReference); + } +} + +} // namespace tidy +} // namespace clang |