| //===--- 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 |