//===--- 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(); if (!RecType) return State::Unknown; const auto *BaseClass = cast(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(); if (!FuncProto) return State::Unknown; const ExceptionSpecificationType EST = FuncProto->getExceptionSpecType(); if (EST == EST_Unevaluated || (EST == EST_None && FuncDecl->isDefaulted())) return analyzeUnresolvedOrDefaulted(cast(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(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(FuncDecl)) { if (const auto *Ctor = dyn_cast(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(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