CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
286 lines
8.9 KiB
C++
286 lines
8.9 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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"
|
|
#include "clang/AST/Type.h"
|
|
|
|
namespace clang::tidy::utils {
|
|
|
|
ExceptionSpecAnalyzer::State
|
|
ExceptionSpecAnalyzer::analyze(const FunctionDecl *FuncDecl) {
|
|
// Check if function exist in cache or add temporary value to cache to protect
|
|
// against endless recursion.
|
|
const auto [CacheEntry, NotFound] =
|
|
FunctionCache.try_emplace(FuncDecl, State::NotThrowing);
|
|
if (NotFound) {
|
|
const ExceptionSpecAnalyzer::State State = analyzeImpl(FuncDecl);
|
|
// Update result with calculated value
|
|
FunctionCache[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;
|
|
|
|
return analyzeRecord(RecType->getAsCXXRecordDecl(), 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()) {
|
|
const State Result = analyzeBase(BaseSpec, Kind);
|
|
if (Result == State::Throwing || Result == State::Unknown)
|
|
return Result;
|
|
}
|
|
|
|
for (const auto &BaseSpec : RecordDecl->vbases()) {
|
|
const 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()) {
|
|
const 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
|