//===-- CrashReason.cpp -----------------------------------------*- 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 "CrashReason.h"

#include "llvm/Support/raw_ostream.h"

#include <sstream>

namespace {

void AppendFaultAddr(std::string &str, lldb::addr_t addr) {
  std::stringstream ss;
  ss << " (fault address: 0x" << std::hex << addr << ")";
  str += ss.str();
}

#if defined(si_lower) && defined(si_upper)
void AppendBounds(std::string &str, lldb::addr_t lower_bound,
                  lldb::addr_t upper_bound, lldb::addr_t addr) {
  llvm::raw_string_ostream stream(str);
  if ((unsigned long)addr < lower_bound)
    stream << ": lower bound violation ";
  else
    stream << ": upper bound violation ";
  stream << "(fault address: 0x";
  stream.write_hex(addr);
  stream << ", lower bound: 0x";
  stream.write_hex(lower_bound);
  stream << ", upper bound: 0x";
  stream.write_hex(upper_bound);
  stream << ")";
  stream.flush();
}
#endif

CrashReason GetCrashReasonForSIGSEGV(const siginfo_t &info) {
  assert(info.si_signo == SIGSEGV);

  switch (info.si_code) {
#ifdef SI_KERNEL
  case SI_KERNEL:
    // Some platforms will occasionally send nonstandard spurious SI_KERNEL
    // codes. One way to get this is via unaligned SIMD loads.
    return CrashReason::eInvalidAddress; // for lack of anything better
#endif
  case SEGV_MAPERR:
    return CrashReason::eInvalidAddress;
  case SEGV_ACCERR:
    return CrashReason::ePrivilegedAddress;
#ifndef SEGV_BNDERR
#define SEGV_BNDERR 3
#endif
  case SEGV_BNDERR:
    return CrashReason::eBoundViolation;
  }

  return CrashReason::eInvalidCrashReason;
}

CrashReason GetCrashReasonForSIGILL(const siginfo_t &info) {
  assert(info.si_signo == SIGILL);

  switch (info.si_code) {
  case ILL_ILLOPC:
    return CrashReason::eIllegalOpcode;
  case ILL_ILLOPN:
    return CrashReason::eIllegalOperand;
  case ILL_ILLADR:
    return CrashReason::eIllegalAddressingMode;
  case ILL_ILLTRP:
    return CrashReason::eIllegalTrap;
  case ILL_PRVOPC:
    return CrashReason::ePrivilegedOpcode;
  case ILL_PRVREG:
    return CrashReason::ePrivilegedRegister;
  case ILL_COPROC:
    return CrashReason::eCoprocessorError;
  case ILL_BADSTK:
    return CrashReason::eInternalStackError;
  }

  return CrashReason::eInvalidCrashReason;
}

CrashReason GetCrashReasonForSIGFPE(const siginfo_t &info) {
  assert(info.si_signo == SIGFPE);

  switch (info.si_code) {
  case FPE_INTDIV:
    return CrashReason::eIntegerDivideByZero;
  case FPE_INTOVF:
    return CrashReason::eIntegerOverflow;
  case FPE_FLTDIV:
    return CrashReason::eFloatDivideByZero;
  case FPE_FLTOVF:
    return CrashReason::eFloatOverflow;
  case FPE_FLTUND:
    return CrashReason::eFloatUnderflow;
  case FPE_FLTRES:
    return CrashReason::eFloatInexactResult;
  case FPE_FLTINV:
    return CrashReason::eFloatInvalidOperation;
  case FPE_FLTSUB:
    return CrashReason::eFloatSubscriptRange;
  }

  return CrashReason::eInvalidCrashReason;
}

CrashReason GetCrashReasonForSIGBUS(const siginfo_t &info) {
  assert(info.si_signo == SIGBUS);

  switch (info.si_code) {
  case BUS_ADRALN:
    return CrashReason::eIllegalAlignment;
  case BUS_ADRERR:
    return CrashReason::eIllegalAddress;
  case BUS_OBJERR:
    return CrashReason::eHardwareError;
  }

  return CrashReason::eInvalidCrashReason;
}
}

std::string GetCrashReasonString(CrashReason reason, const siginfo_t &info) {
  std::string str;

// make sure that siginfo_t has the bound fields available.
#if defined(si_lower) && defined(si_upper)
  if (reason == CrashReason::eBoundViolation) {
    str = "signal SIGSEGV";
    AppendBounds(str, reinterpret_cast<lldb::addr_t>(info.si_lower),
                 reinterpret_cast<lldb::addr_t>(info.si_upper),
                 reinterpret_cast<lldb::addr_t>(info.si_addr));
    return str;
  }
#endif

  return GetCrashReasonString(reason,
                              reinterpret_cast<lldb::addr_t>(info.si_addr));
}

std::string GetCrashReasonString(CrashReason reason, lldb::addr_t fault_addr) {
  std::string str;

  switch (reason) {
  default:
    str = "unknown crash reason";
    break;

  case CrashReason::eInvalidAddress:
    str = "signal SIGSEGV: invalid address";
    AppendFaultAddr(str, fault_addr);
    break;
  case CrashReason::ePrivilegedAddress:
    str = "signal SIGSEGV: address access protected";
    AppendFaultAddr(str, fault_addr);
    break;
  case CrashReason::eBoundViolation:
    str = "signal SIGSEGV: bound violation";
    break;
  case CrashReason::eIllegalOpcode:
    str = "signal SIGILL: illegal instruction";
    break;
  case CrashReason::eIllegalOperand:
    str = "signal SIGILL: illegal instruction operand";
    break;
  case CrashReason::eIllegalAddressingMode:
    str = "signal SIGILL: illegal addressing mode";
    break;
  case CrashReason::eIllegalTrap:
    str = "signal SIGILL: illegal trap";
    break;
  case CrashReason::ePrivilegedOpcode:
    str = "signal SIGILL: privileged instruction";
    break;
  case CrashReason::ePrivilegedRegister:
    str = "signal SIGILL: privileged register";
    break;
  case CrashReason::eCoprocessorError:
    str = "signal SIGILL: coprocessor error";
    break;
  case CrashReason::eInternalStackError:
    str = "signal SIGILL: internal stack error";
    break;
  case CrashReason::eIllegalAlignment:
    str = "signal SIGBUS: illegal alignment";
    break;
  case CrashReason::eIllegalAddress:
    str = "signal SIGBUS: illegal address";
    break;
  case CrashReason::eHardwareError:
    str = "signal SIGBUS: hardware error";
    break;
  case CrashReason::eIntegerDivideByZero:
    str = "signal SIGFPE: integer divide by zero";
    break;
  case CrashReason::eIntegerOverflow:
    str = "signal SIGFPE: integer overflow";
    break;
  case CrashReason::eFloatDivideByZero:
    str = "signal SIGFPE: floating point divide by zero";
    break;
  case CrashReason::eFloatOverflow:
    str = "signal SIGFPE: floating point overflow";
    break;
  case CrashReason::eFloatUnderflow:
    str = "signal SIGFPE: floating point underflow";
    break;
  case CrashReason::eFloatInexactResult:
    str = "signal SIGFPE: inexact floating point result";
    break;
  case CrashReason::eFloatInvalidOperation:
    str = "signal SIGFPE: invalid floating point operation";
    break;
  case CrashReason::eFloatSubscriptRange:
    str = "signal SIGFPE: invalid floating point subscript range";
    break;
  }

  return str;
}

const char *CrashReasonAsString(CrashReason reason) {
  const char *str = nullptr;

  switch (reason) {
  case CrashReason::eInvalidCrashReason:
    str = "eInvalidCrashReason";
    break;

  // SIGSEGV crash reasons.
  case CrashReason::eInvalidAddress:
    str = "eInvalidAddress";
    break;
  case CrashReason::ePrivilegedAddress:
    str = "ePrivilegedAddress";
    break;
  case CrashReason::eBoundViolation:
    str = "eBoundViolation";
    break;

  // SIGILL crash reasons.
  case CrashReason::eIllegalOpcode:
    str = "eIllegalOpcode";
    break;
  case CrashReason::eIllegalOperand:
    str = "eIllegalOperand";
    break;
  case CrashReason::eIllegalAddressingMode:
    str = "eIllegalAddressingMode";
    break;
  case CrashReason::eIllegalTrap:
    str = "eIllegalTrap";
    break;
  case CrashReason::ePrivilegedOpcode:
    str = "ePrivilegedOpcode";
    break;
  case CrashReason::ePrivilegedRegister:
    str = "ePrivilegedRegister";
    break;
  case CrashReason::eCoprocessorError:
    str = "eCoprocessorError";
    break;
  case CrashReason::eInternalStackError:
    str = "eInternalStackError";
    break;

  // SIGBUS crash reasons:
  case CrashReason::eIllegalAlignment:
    str = "eIllegalAlignment";
    break;
  case CrashReason::eIllegalAddress:
    str = "eIllegalAddress";
    break;
  case CrashReason::eHardwareError:
    str = "eHardwareError";
    break;

  // SIGFPE crash reasons:
  case CrashReason::eIntegerDivideByZero:
    str = "eIntegerDivideByZero";
    break;
  case CrashReason::eIntegerOverflow:
    str = "eIntegerOverflow";
    break;
  case CrashReason::eFloatDivideByZero:
    str = "eFloatDivideByZero";
    break;
  case CrashReason::eFloatOverflow:
    str = "eFloatOverflow";
    break;
  case CrashReason::eFloatUnderflow:
    str = "eFloatUnderflow";
    break;
  case CrashReason::eFloatInexactResult:
    str = "eFloatInexactResult";
    break;
  case CrashReason::eFloatInvalidOperation:
    str = "eFloatInvalidOperation";
    break;
  case CrashReason::eFloatSubscriptRange:
    str = "eFloatSubscriptRange";
    break;
  }
  return str;
}

CrashReason GetCrashReason(const siginfo_t &info) {
  switch (info.si_signo) {
  case SIGSEGV:
    return GetCrashReasonForSIGSEGV(info);
  case SIGBUS:
    return GetCrashReasonForSIGBUS(info);
  case SIGFPE:
    return GetCrashReasonForSIGFPE(info);
  case SIGILL:
    return GetCrashReasonForSIGILL(info);
  }

  assert(false && "unexpected signal");
  return CrashReason::eInvalidCrashReason;
}
