blob: 4a9426ee7e8bbbe35f46cfbc1a9670833aab123d [file] [log] [blame]
//===--- 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