176 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "ThrowByValueCatchByReferenceCheck.h"
 | 
						|
#include "clang/AST/ASTContext.h"
 | 
						|
#include "clang/AST/OperationKinds.h"
 | 
						|
#include "clang/ASTMatchers/ASTMatchFinder.h"
 | 
						|
 | 
						|
using namespace clang::ast_matchers;
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace tidy {
 | 
						|
namespace misc {
 | 
						|
 | 
						|
ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
 | 
						|
    StringRef Name, ClangTidyContext *Context)
 | 
						|
    : ClangTidyCheck(Name, Context),
 | 
						|
      CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),
 | 
						|
      WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),
 | 
						|
      // Cannot access `ASTContext` from here so set it to an extremal value.
 | 
						|
      MaxSizeOptions(
 | 
						|
          Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),
 | 
						|
      MaxSize(MaxSizeOptions) {}
 | 
						|
 | 
						|
void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
 | 
						|
  Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
 | 
						|
  Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
 | 
						|
}
 | 
						|
 | 
						|
void ThrowByValueCatchByReferenceCheck::storeOptions(
 | 
						|
    ClangTidyOptions::OptionMap &Opts) {
 | 
						|
  Options.store(Opts, "CheckThrowTemporaries", true);
 | 
						|
  Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);
 | 
						|
  Options.store(Opts, "MaxSize", MaxSizeOptions);
 | 
						|
}
 | 
						|
 | 
						|
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<clang::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 string 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->getBeginLoc(), "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 auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);
 | 
						|
    const auto *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->getBeginLoc(),
 | 
						|
           "throw expression should throw anonymous temporary values instead");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
 | 
						|
    const CXXCatchStmt *CatchStmt, ASTContext &Context) {
 | 
						|
  if (!CatchStmt)
 | 
						|
    return;
 | 
						|
  auto CaughtType = CatchStmt->getCaughtType();
 | 
						|
  if (CaughtType.isNull())
 | 
						|
    return;
 | 
						|
  auto *VarDecl = CatchStmt->getExceptionDecl();
 | 
						|
  if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {
 | 
						|
    const char *DiagMsgCatchReference =
 | 
						|
        "catch handler catches a pointer value; "
 | 
						|
        "should throw a non-pointer value and "
 | 
						|
        "catch by reference instead";
 | 
						|
    // We do not diagnose when catching pointer to strings since we also allow
 | 
						|
    // throwing string literals.
 | 
						|
    if (!PT->getPointeeType()->isAnyCharacterType())
 | 
						|
      diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
 | 
						|
  } else if (!CaughtType->isReferenceType()) {
 | 
						|
    const char *DiagMsgCatchReference = "catch handler catches by value; "
 | 
						|
                                        "should catch by reference instead";
 | 
						|
    // If it's not a pointer and not a reference then it must be caught "by
 | 
						|
    // value". In this case we should emit a diagnosis message unless the type
 | 
						|
    // is trivial.
 | 
						|
    if (!CaughtType.isTrivialType(Context)) {
 | 
						|
      diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
 | 
						|
    } else if (WarnOnLargeObject) {
 | 
						|
      // If the type is trivial, then catching it by reference is not dangerous.
 | 
						|
      // However, catching large objects by value decreases the performance.
 | 
						|
 | 
						|
      // We can now access `ASTContext` so if `MaxSize` is an extremal value
 | 
						|
      // then set it to the size of `size_t`.
 | 
						|
      if (MaxSize == std::numeric_limits<uint64_t>::max())
 | 
						|
        MaxSize = Context.getTypeSize(Context.getSizeType());
 | 
						|
      if (Context.getTypeSize(CaughtType) > MaxSize)
 | 
						|
        diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
} // namespace misc
 | 
						|
} // namespace tidy
 | 
						|
} // namespace clang
 |