brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.0 KiB · 92ff1c8 Raw
171 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "ThrowByValueCatchByReferenceCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::misc {16 17ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(18    StringRef Name, ClangTidyContext *Context)19    : ClangTidyCheck(Name, Context),20      CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),21      WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),22      // Cannot access `ASTContext` from here so set it to an extremal value.23      MaxSizeOptions(24          Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),25      MaxSize(MaxSizeOptions) {}26 27void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {28  Finder->addMatcher(cxxThrowExpr().bind("throw"), this);29  Finder->addMatcher(cxxCatchStmt().bind("catch"), this);30}31 32void ThrowByValueCatchByReferenceCheck::storeOptions(33    ClangTidyOptions::OptionMap &Opts) {34  Options.store(Opts, "CheckThrowTemporaries", true);35  Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);36  Options.store(Opts, "MaxSize", MaxSizeOptions);37}38 39void 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 46bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(47    const DeclRefExpr *DeclRefExpr) {48  return isa<ParmVarDecl>(DeclRefExpr->getDecl());49}50 51bool ThrowByValueCatchByReferenceCheck::isCatchVariable(52    const DeclRefExpr *DeclRefExpr) {53  auto *ValueDecl = DeclRefExpr->getDecl();54  if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl))55    return VarDecl->isExceptionVariable();56  return false;57}58 59bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(60    const DeclRefExpr *DeclRefExpr) {61  return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);62}63 64void 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 string 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->getBeginLoc(), "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 value87  // which is ok.88  // There are addition checks that emit diagnosis messages if the thrown value89  // is not an RValue. See:90  // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries91  // This behavior can be influenced by an option.92 93  // If we encounter a CXXThrowExpr, we move through all casts until you either94  // encounter a DeclRefExpr or a CXXConstructExpr.95  // If it's a DeclRefExpr, we emit a message if the referenced variable is not96  // a catch variable or function parameter.97  // When encountering a CopyOrMoveConstructor: emit message if after casts,98  // the expression is a LValue99  if (CheckAnonymousTemporaries) {100    bool Emit = false;101    auto *CurrentSubExpr = SubExpr->IgnoreImpCasts();102    const auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);103    const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(CurrentSubExpr);104    // If we have a DeclRefExpr, we flag for emitting a diagnosis message in105    // case the referenced variable is neither a function parameter nor a106    // variable declared in the catch statement.107    if (VariableReference)108      Emit = !isFunctionOrCatchVar(VariableReference);109    else if (ConstructorCall &&110             ConstructorCall->getConstructor()->isCopyOrMoveConstructor()) {111      // If we have a copy / move construction, we emit a diagnosis message if112      // the object that we copy construct from is neither a function parameter113      // nor a variable declared in a catch statement114      auto ArgIter =115          ConstructorCall116              ->arg_begin(); // there's only one for copy constructors117      auto *CurrentSubExpr = (*ArgIter)->IgnoreImpCasts();118      if (CurrentSubExpr->isLValue()) {119        if (auto *Tmp = dyn_cast<DeclRefExpr>(CurrentSubExpr))120          Emit = !isFunctionOrCatchVar(Tmp);121        else if (isa<CallExpr>(CurrentSubExpr))122          Emit = true;123      }124    }125    if (Emit)126      diag(SubExpr->getBeginLoc(),127           "throw expression should throw anonymous temporary values instead");128  }129}130 131void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(132    const CXXCatchStmt *CatchStmt, ASTContext &Context) {133  if (!CatchStmt)134    return;135  auto CaughtType = CatchStmt->getCaughtType();136  if (CaughtType.isNull())137    return;138  auto *VarDecl = CatchStmt->getExceptionDecl();139  if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {140    const char *DiagMsgCatchReference =141        "catch handler catches a pointer value; "142        "should throw a non-pointer value and "143        "catch by reference instead";144    // We do not diagnose when catching pointer to strings since we also allow145    // throwing string literals.146    if (!PT->getPointeeType()->isAnyCharacterType())147      diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);148  } else if (!CaughtType->isReferenceType()) {149    const char *DiagMsgCatchReference = "catch handler catches by value; "150                                        "should catch by reference instead";151    // If it's not a pointer and not a reference then it must be caught "by152    // value". In this case we should emit a diagnosis message unless the type153    // is trivial.154    if (!CaughtType.isTrivialType(Context)) {155      diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);156    } else if (WarnOnLargeObject) {157      // If the type is trivial, then catching it by reference is not dangerous.158      // However, catching large objects by value decreases the performance.159 160      // We can now access `ASTContext` so if `MaxSize` is an extremal value161      // then set it to the size of `size_t`.162      if (MaxSize == std::numeric_limits<uint64_t>::max())163        MaxSize = Context.getTypeSize(Context.getSizeType());164      if (Context.getTypeSize(CaughtType) > MaxSize)165        diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);166    }167  }168}169 170} // namespace clang::tidy::misc171