ThrowByValueCatchByReferenceCheck.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. #include "ThrowByValueCatchByReferenceCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/AST/OperationKinds.h"
  11. #include "clang/ASTMatchers/ASTMatchFinder.h"
  12. using namespace clang::ast_matchers;
  13. namespace clang::tidy::misc {
  14. ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
  15. StringRef Name, ClangTidyContext *Context)
  16. : ClangTidyCheck(Name, Context),
  17. CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),
  18. WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),
  19. // Cannot access `ASTContext` from here so set it to an extremal value.
  20. MaxSizeOptions(
  21. Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),
  22. MaxSize(MaxSizeOptions) {}
  23. void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
  24. Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
  25. Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
  26. }
  27. void ThrowByValueCatchByReferenceCheck::storeOptions(
  28. ClangTidyOptions::OptionMap &Opts) {
  29. Options.store(Opts, "CheckThrowTemporaries", true);
  30. Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);
  31. Options.store(Opts, "MaxSize", MaxSizeOptions);
  32. }
  33. void ThrowByValueCatchByReferenceCheck::check(
  34. const MatchFinder::MatchResult &Result) {
  35. diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
  36. diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
  37. *Result.Context);
  38. }
  39. bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
  40. const DeclRefExpr *DeclRefExpr) {
  41. return isa<ParmVarDecl>(DeclRefExpr->getDecl());
  42. }
  43. bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
  44. const DeclRefExpr *DeclRefExpr) {
  45. auto *ValueDecl = DeclRefExpr->getDecl();
  46. if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl))
  47. return VarDecl->isExceptionVariable();
  48. return false;
  49. }
  50. bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
  51. const DeclRefExpr *DeclRefExpr) {
  52. return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);
  53. }
  54. void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
  55. const CXXThrowExpr *ThrowExpr) {
  56. if (!ThrowExpr)
  57. return;
  58. auto *SubExpr = ThrowExpr->getSubExpr();
  59. if (!SubExpr)
  60. return;
  61. auto QualType = SubExpr->getType();
  62. if (QualType->isPointerType()) {
  63. // The code is throwing a pointer.
  64. // In case it is string literal, it is safe and we return.
  65. auto *Inner = SubExpr->IgnoreParenImpCasts();
  66. if (isa<StringLiteral>(Inner))
  67. return;
  68. // If it's a variable from a catch statement, we return as well.
  69. auto *DeclRef = dyn_cast<DeclRefExpr>(Inner);
  70. if (DeclRef && isCatchVariable(DeclRef)) {
  71. return;
  72. }
  73. diag(SubExpr->getBeginLoc(), "throw expression throws a pointer; it should "
  74. "throw a non-pointer value instead");
  75. }
  76. // If the throw statement does not throw by pointer then it throws by value
  77. // which is ok.
  78. // There are addition checks that emit diagnosis messages if the thrown value
  79. // is not an RValue. See:
  80. // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries
  81. // This behavior can be influenced by an option.
  82. // If we encounter a CXXThrowExpr, we move through all casts until you either
  83. // encounter a DeclRefExpr or a CXXConstructExpr.
  84. // If it's a DeclRefExpr, we emit a message if the referenced variable is not
  85. // a catch variable or function parameter.
  86. // When encountering a CopyOrMoveConstructor: emit message if after casts,
  87. // the expression is a LValue
  88. if (CheckAnonymousTemporaries) {
  89. bool Emit = false;
  90. auto *CurrentSubExpr = SubExpr->IgnoreImpCasts();
  91. const auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);
  92. const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(CurrentSubExpr);
  93. // If we have a DeclRefExpr, we flag for emitting a diagnosis message in
  94. // case the referenced variable is neither a function parameter nor a
  95. // variable declared in the catch statement.
  96. if (VariableReference)
  97. Emit = !isFunctionOrCatchVar(VariableReference);
  98. else if (ConstructorCall &&
  99. ConstructorCall->getConstructor()->isCopyOrMoveConstructor()) {
  100. // If we have a copy / move construction, we emit a diagnosis message if
  101. // the object that we copy construct from is neither a function parameter
  102. // nor a variable declared in a catch statement
  103. auto ArgIter =
  104. ConstructorCall
  105. ->arg_begin(); // there's only one for copy constructors
  106. auto *CurrentSubExpr = (*ArgIter)->IgnoreImpCasts();
  107. if (CurrentSubExpr->isLValue()) {
  108. if (auto *Tmp = dyn_cast<DeclRefExpr>(CurrentSubExpr))
  109. Emit = !isFunctionOrCatchVar(Tmp);
  110. else if (isa<CallExpr>(CurrentSubExpr))
  111. Emit = true;
  112. }
  113. }
  114. if (Emit)
  115. diag(SubExpr->getBeginLoc(),
  116. "throw expression should throw anonymous temporary values instead");
  117. }
  118. }
  119. void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
  120. const CXXCatchStmt *CatchStmt, ASTContext &Context) {
  121. if (!CatchStmt)
  122. return;
  123. auto CaughtType = CatchStmt->getCaughtType();
  124. if (CaughtType.isNull())
  125. return;
  126. auto *VarDecl = CatchStmt->getExceptionDecl();
  127. if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {
  128. const char *DiagMsgCatchReference =
  129. "catch handler catches a pointer value; "
  130. "should throw a non-pointer value and "
  131. "catch by reference instead";
  132. // We do not diagnose when catching pointer to strings since we also allow
  133. // throwing string literals.
  134. if (!PT->getPointeeType()->isAnyCharacterType())
  135. diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
  136. } else if (!CaughtType->isReferenceType()) {
  137. const char *DiagMsgCatchReference = "catch handler catches by value; "
  138. "should catch by reference instead";
  139. // If it's not a pointer and not a reference then it must be caught "by
  140. // value". In this case we should emit a diagnosis message unless the type
  141. // is trivial.
  142. if (!CaughtType.isTrivialType(Context)) {
  143. diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
  144. } else if (WarnOnLargeObject) {
  145. // If the type is trivial, then catching it by reference is not dangerous.
  146. // However, catching large objects by value decreases the performance.
  147. // We can now access `ASTContext` so if `MaxSize` is an extremal value
  148. // then set it to the size of `size_t`.
  149. if (MaxSize == std::numeric_limits<uint64_t>::max())
  150. MaxSize = Context.getTypeSize(Context.getSizeType());
  151. if (Context.getTypeSize(CaughtType) > MaxSize)
  152. diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
  153. }
  154. }
  155. }
  156. } // namespace clang::tidy::misc