//===--- 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) {
  ExceptionSpecAnalyzer::State State;

  // Check if the function has already been analyzed and reuse that result.
  const auto CacheEntry = FunctionCache.find(FuncDecl);
  if (CacheEntry == FunctionCache.end()) {
    State = analyzeImpl(FuncDecl);

    // Cache the result of the analysis.
    FunctionCache.try_emplace(FuncDecl, State);
  } else
    State = CacheEntry->getSecond();

  return State;
}

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();
    bool Result;
    return (NoexceptExpr && !NoexceptExpr->isValueDependent() &&
            NoexceptExpr->EvaluateAsBooleanCondition(
                Result, FuncDecl->getASTContext(), true) &&
            Result)
               ? State::NotThrowing
               : State::Throwing;
  }
  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
