|  | //===--- ExceptionSpecAnalyzer.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 "ExceptionSpecAnalyzer.h" | 
|  |  | 
|  | #include "clang/AST/Expr.h" | 
|  |  | 
|  | namespace clang::tidy::utils { | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyze(const FunctionDecl *FuncDecl) { | 
|  | // Check if the function has already been analyzed and reuse that result. | 
|  | const auto CacheEntry = FunctionCache.find(FuncDecl); | 
|  | if (CacheEntry == FunctionCache.end()) { | 
|  | ExceptionSpecAnalyzer::State State = analyzeImpl(FuncDecl); | 
|  |  | 
|  | // Cache the result of the analysis. | 
|  | FunctionCache.try_emplace(FuncDecl, State); | 
|  | return State; | 
|  | } | 
|  |  | 
|  | return CacheEntry->getSecond(); | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeUnresolvedOrDefaulted( | 
|  | const CXXMethodDecl *MethodDecl, const FunctionProtoType *FuncProto) { | 
|  | if (!FuncProto || !MethodDecl) | 
|  | return State::Unknown; | 
|  |  | 
|  | const DefaultableMemberKind Kind = getDefaultableMemberKind(MethodDecl); | 
|  |  | 
|  | if (Kind == DefaultableMemberKind::None) | 
|  | return State::Unknown; | 
|  |  | 
|  | return analyzeRecord(MethodDecl->getParent(), Kind, SkipMethods::Yes); | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeFieldDecl(const FieldDecl *FDecl, | 
|  | DefaultableMemberKind Kind) { | 
|  | if (!FDecl) | 
|  | return State::Unknown; | 
|  |  | 
|  | if (const CXXRecordDecl *RecDecl = | 
|  | FDecl->getType()->getUnqualifiedDesugaredType()->getAsCXXRecordDecl()) | 
|  | return analyzeRecord(RecDecl, Kind); | 
|  |  | 
|  | // Trivial types do not throw | 
|  | if (FDecl->getType().isTrivialType(FDecl->getASTContext())) | 
|  | return State::NotThrowing; | 
|  |  | 
|  | return State::Unknown; | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeBase(const CXXBaseSpecifier &Base, | 
|  | DefaultableMemberKind Kind) { | 
|  | const auto *RecType = Base.getType()->getAs<RecordType>(); | 
|  | if (!RecType) | 
|  | return State::Unknown; | 
|  |  | 
|  | const auto *BaseClass = cast<CXXRecordDecl>(RecType->getDecl()); | 
|  |  | 
|  | return analyzeRecord(BaseClass, Kind); | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeRecord(const CXXRecordDecl *RecordDecl, | 
|  | DefaultableMemberKind Kind, | 
|  | SkipMethods SkipMethods) { | 
|  | if (!RecordDecl) | 
|  | return State::Unknown; | 
|  |  | 
|  | // Trivial implies noexcept | 
|  | if (hasTrivialMemberKind(RecordDecl, Kind)) | 
|  | return State::NotThrowing; | 
|  |  | 
|  | if (SkipMethods == SkipMethods::No) | 
|  | for (const auto *MethodDecl : RecordDecl->methods()) | 
|  | if (getDefaultableMemberKind(MethodDecl) == Kind) | 
|  | return analyze(MethodDecl); | 
|  |  | 
|  | for (const auto &BaseSpec : RecordDecl->bases()) { | 
|  | State Result = analyzeBase(BaseSpec, Kind); | 
|  | if (Result == State::Throwing || Result == State::Unknown) | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | for (const auto &BaseSpec : RecordDecl->vbases()) { | 
|  | State Result = analyzeBase(BaseSpec, Kind); | 
|  | if (Result == State::Throwing || Result == State::Unknown) | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | for (const auto *FDecl : RecordDecl->fields()) | 
|  | if (!FDecl->isInvalidDecl() && !FDecl->isUnnamedBitField()) { | 
|  | State Result = analyzeFieldDecl(FDecl, Kind); | 
|  | if (Result == State::Throwing || Result == State::Unknown) | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | return State::NotThrowing; | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeImpl(const FunctionDecl *FuncDecl) { | 
|  | const auto *FuncProto = FuncDecl->getType()->getAs<FunctionProtoType>(); | 
|  | if (!FuncProto) | 
|  | return State::Unknown; | 
|  |  | 
|  | const ExceptionSpecificationType EST = FuncProto->getExceptionSpecType(); | 
|  |  | 
|  | if (EST == EST_Unevaluated || (EST == EST_None && FuncDecl->isDefaulted())) | 
|  | return analyzeUnresolvedOrDefaulted(cast<CXXMethodDecl>(FuncDecl), | 
|  | FuncProto); | 
|  |  | 
|  | return analyzeFunctionEST(FuncDecl, FuncProto); | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::State | 
|  | ExceptionSpecAnalyzer::analyzeFunctionEST(const FunctionDecl *FuncDecl, | 
|  | const FunctionProtoType *FuncProto) { | 
|  | if (!FuncDecl || !FuncProto) | 
|  | return State::Unknown; | 
|  |  | 
|  | if (isUnresolvedExceptionSpec(FuncProto->getExceptionSpecType())) | 
|  | return State::Unknown; | 
|  |  | 
|  | // A non defaulted destructor without the noexcept specifier is still noexcept | 
|  | if (isa<CXXDestructorDecl>(FuncDecl) && | 
|  | FuncDecl->getExceptionSpecType() == EST_None) | 
|  | return State::NotThrowing; | 
|  |  | 
|  | switch (FuncProto->canThrow()) { | 
|  | case CT_Cannot: | 
|  | return State::NotThrowing; | 
|  | case CT_Dependent: { | 
|  | const Expr *NoexceptExpr = FuncProto->getNoexceptExpr(); | 
|  | if (!NoexceptExpr) | 
|  | return State::NotThrowing; | 
|  |  | 
|  | // We can't resolve value dependence so just return unknown | 
|  | if (NoexceptExpr->isValueDependent()) | 
|  | return State::Unknown; | 
|  |  | 
|  | // Try to evaluate the expression to a boolean value | 
|  | bool Result = false; | 
|  | if (NoexceptExpr->EvaluateAsBooleanCondition( | 
|  | Result, FuncDecl->getASTContext(), true)) | 
|  | return Result ? State::NotThrowing : State::Throwing; | 
|  |  | 
|  | // The noexcept expression is not value dependent but we can't evaluate it | 
|  | // as a boolean condition so we have no idea if its throwing or not | 
|  | return State::Unknown; | 
|  | } | 
|  | default: | 
|  | return State::Throwing; | 
|  | }; | 
|  | } | 
|  |  | 
|  | bool ExceptionSpecAnalyzer::hasTrivialMemberKind(const CXXRecordDecl *RecDecl, | 
|  | DefaultableMemberKind Kind) { | 
|  | if (!RecDecl) | 
|  | return false; | 
|  |  | 
|  | switch (Kind) { | 
|  | case DefaultableMemberKind::DefaultConstructor: | 
|  | return RecDecl->hasTrivialDefaultConstructor(); | 
|  | case DefaultableMemberKind::CopyConstructor: | 
|  | return RecDecl->hasTrivialCopyConstructor(); | 
|  | case DefaultableMemberKind::MoveConstructor: | 
|  | return RecDecl->hasTrivialMoveConstructor(); | 
|  | case DefaultableMemberKind::CopyAssignment: | 
|  | return RecDecl->hasTrivialCopyAssignment(); | 
|  | case DefaultableMemberKind::MoveAssignment: | 
|  | return RecDecl->hasTrivialMoveAssignment(); | 
|  | case DefaultableMemberKind::Destructor: | 
|  | return RecDecl->hasTrivialDestructor(); | 
|  |  | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ExceptionSpecAnalyzer::isConstructor(DefaultableMemberKind Kind) { | 
|  | switch (Kind) { | 
|  | case DefaultableMemberKind::DefaultConstructor: | 
|  | case DefaultableMemberKind::CopyConstructor: | 
|  | case DefaultableMemberKind::MoveConstructor: | 
|  | return true; | 
|  |  | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ExceptionSpecAnalyzer::isSpecialMember(DefaultableMemberKind Kind) { | 
|  | switch (Kind) { | 
|  | case DefaultableMemberKind::DefaultConstructor: | 
|  | case DefaultableMemberKind::CopyConstructor: | 
|  | case DefaultableMemberKind::MoveConstructor: | 
|  | case DefaultableMemberKind::CopyAssignment: | 
|  | case DefaultableMemberKind::MoveAssignment: | 
|  | case DefaultableMemberKind::Destructor: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ExceptionSpecAnalyzer::isComparison(DefaultableMemberKind Kind) { | 
|  | switch (Kind) { | 
|  | case DefaultableMemberKind::CompareEqual: | 
|  | case DefaultableMemberKind::CompareNotEqual: | 
|  | case DefaultableMemberKind::CompareRelational: | 
|  | case DefaultableMemberKind::CompareThreeWay: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | ExceptionSpecAnalyzer::DefaultableMemberKind | 
|  | ExceptionSpecAnalyzer::getDefaultableMemberKind(const FunctionDecl *FuncDecl) { | 
|  | if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl)) { | 
|  | if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(FuncDecl)) { | 
|  | if (Ctor->isDefaultConstructor()) | 
|  | return DefaultableMemberKind::DefaultConstructor; | 
|  |  | 
|  | if (Ctor->isCopyConstructor()) | 
|  | return DefaultableMemberKind::CopyConstructor; | 
|  |  | 
|  | if (Ctor->isMoveConstructor()) | 
|  | return DefaultableMemberKind::MoveConstructor; | 
|  | } | 
|  |  | 
|  | if (MethodDecl->isCopyAssignmentOperator()) | 
|  | return DefaultableMemberKind::CopyAssignment; | 
|  |  | 
|  | if (MethodDecl->isMoveAssignmentOperator()) | 
|  | return DefaultableMemberKind::MoveAssignment; | 
|  |  | 
|  | if (isa<CXXDestructorDecl>(FuncDecl)) | 
|  | return DefaultableMemberKind::Destructor; | 
|  | } | 
|  |  | 
|  | const LangOptions &LangOpts = FuncDecl->getLangOpts(); | 
|  |  | 
|  | switch (FuncDecl->getDeclName().getCXXOverloadedOperator()) { | 
|  | case OO_EqualEqual: | 
|  | return DefaultableMemberKind::CompareEqual; | 
|  |  | 
|  | case OO_ExclaimEqual: | 
|  | return DefaultableMemberKind::CompareNotEqual; | 
|  |  | 
|  | case OO_Spaceship: | 
|  | // No point allowing this if <=> doesn't exist in the current language mode. | 
|  | if (!LangOpts.CPlusPlus20) | 
|  | break; | 
|  | return DefaultableMemberKind::CompareThreeWay; | 
|  |  | 
|  | case OO_Less: | 
|  | case OO_LessEqual: | 
|  | case OO_Greater: | 
|  | case OO_GreaterEqual: | 
|  | // No point allowing this if <=> doesn't exist in the current language mode. | 
|  | if (!LangOpts.CPlusPlus20) | 
|  | break; | 
|  | return DefaultableMemberKind::CompareRelational; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | // Not a defaultable member kind | 
|  | return DefaultableMemberKind::None; | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::utils |