// Copyright 2015 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "util/mach/exception_types.h"

#include <kern/exc_resource.h>
#include <signal.h>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>

#include <iterator>

#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/mac/mac_util.h"
#include "util/mach/mach_extensions.h"

namespace crashpad {
namespace test {
namespace {

TEST(ExceptionTypes, ExcCrashRecoverOriginalException) {
  static constexpr struct {
    mach_exception_code_t code_0;
    exception_type_t exception;
    mach_exception_code_t original_code_0;
    int signal;
  } kTestData[] = {
    {0xb100001, EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, SIGSEGV},
    {0xb100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGSEGV},
    {0xa100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGBUS},
    {0xa100005, EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE, SIGBUS},
    {0x9100032, EXC_BAD_ACCESS, KERN_CODESIGN_ERROR, SIGKILL},
    {0x0700080, EXC_SYSCALL, 128, 0},
    {0x0706000, EXC_SYSCALL, 0x6000, 0},
    {0x3000000, 0, 0, SIGQUIT},
    {0x4000000, 0, 0, SIGILL},
    {0x5000000, 0, 0, SIGTRAP},
    {0x6000000, 0, 0, SIGABRT},
    {0x7000000, 0, 0, SIGEMT},
    {0x8000000, 0, 0, SIGFPE},
    {0xa000000, 0, 0, SIGBUS},
    {0xb000000, 0, 0, SIGSEGV},
    {0xc000000, 0, 0, SIGSYS},
    {0, 0, 0, 0},
#if defined(ARCH_CPU_X86_FAMILY)
    {0xa10000d, EXC_BAD_ACCESS, EXC_I386_GPFLT, SIGBUS},
    {0x4200001, EXC_BAD_INSTRUCTION, EXC_I386_INVOP, SIGILL},
    {0x420000b, EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT, SIGILL},
    {0x420000c, EXC_BAD_INSTRUCTION, EXC_I386_STKFLT, SIGILL},
    {0x8300001, EXC_ARITHMETIC, EXC_I386_DIV, SIGFPE},
    {0x8300002, EXC_ARITHMETIC, EXC_I386_INTO, SIGFPE},
    {0x8300005, EXC_ARITHMETIC, EXC_I386_EXTERR, SIGFPE},
    {0x8300008, EXC_ARITHMETIC, EXC_I386_SSEEXTERR, SIGFPE},
    {0x5500007, EXC_SOFTWARE, EXC_I386_BOUND, SIGTRAP},
    {0x5600001, EXC_BREAKPOINT, EXC_I386_SGL, SIGTRAP},
    {0x5600002, EXC_BREAKPOINT, EXC_I386_BPT, SIGTRAP},
#endif
    // TODO(macos_arm64): Add arm64 test data.
  };

  for (size_t index = 0; index < std::size(kTestData); ++index) {
    const auto& test_data = kTestData[index];
    SCOPED_TRACE(base::StringPrintf(
        "index %zu, code_0 0x%llx", index, test_data.code_0));

    mach_exception_code_t original_code_0;
    int signal;
    exception_type_t exception = ExcCrashRecoverOriginalException(
        test_data.code_0, &original_code_0, &signal);

    EXPECT_EQ(exception, test_data.exception);
    EXPECT_EQ(original_code_0, test_data.original_code_0);
    EXPECT_EQ(signal, test_data.signal);
  }

  // Now make sure that ExcCrashRecoverOriginalException() properly ignores
  // optional arguments.
  static_assert(std::size(kTestData) >= 1, "must have something to test");
  const auto& test_data = kTestData[0];
  EXPECT_EQ(
      ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr),
      test_data.exception);

  mach_exception_code_t original_code_0;
  EXPECT_EQ(ExcCrashRecoverOriginalException(
                test_data.code_0, &original_code_0, nullptr),
            test_data.exception);
  EXPECT_EQ(original_code_0, test_data.original_code_0);

  int signal;
  EXPECT_EQ(
      ExcCrashRecoverOriginalException(test_data.code_0, nullptr, &signal),
      test_data.exception);
  EXPECT_EQ(signal, test_data.signal);
}

TEST(ExceptionTypes, ExcCrashCouldContainException) {
  // This seems wrong, but it happens when EXC_CRASH carries an exception not
  // originally caused by a hardware fault, such as SIGABRT.
  EXPECT_TRUE(ExcCrashCouldContainException(0));

  EXPECT_TRUE(ExcCrashCouldContainException(EXC_BAD_ACCESS));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_BAD_INSTRUCTION));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_ARITHMETIC));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_EMULATION));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_SOFTWARE));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_BREAKPOINT));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_SYSCALL));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_MACH_SYSCALL));
  EXPECT_TRUE(ExcCrashCouldContainException(EXC_RPC_ALERT));
  EXPECT_FALSE(ExcCrashCouldContainException(EXC_CRASH));
  EXPECT_FALSE(ExcCrashCouldContainException(EXC_RESOURCE));
  EXPECT_FALSE(ExcCrashCouldContainException(EXC_GUARD));
  EXPECT_FALSE(ExcCrashCouldContainException(EXC_CORPSE_NOTIFY));
  EXPECT_FALSE(ExcCrashCouldContainException(kMachExceptionSimulated));
}

// This macro is adapted from those in the #ifdef KERNEL section of
// <kern/exc_resource.h>: 10.12.2 xnu-3789.31.2/osfmk/kern/exc_resource.h.
#define EXC_RESOURCE_ENCODE_TYPE_FLAVOR(type, flavor)   \
  (static_cast<mach_exception_code_t>(                  \
      (((static_cast<uint64_t>(type) & 0x7ull) << 61) | \
       (static_cast<uint64_t>(flavor) & 0x7ull) << 58)))

TEST(ExceptionTypes, ExceptionCodeForMetrics) {
  static constexpr struct {
    exception_type_t exception;
    mach_exception_code_t code_0;
    int32_t metrics_code;
  } kTestData[] = {
#define ENCODE_EXC(type, code_0) \
  { (type), (code_0), ((type) << 16) | (code_0) }
    ENCODE_EXC(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS),
    ENCODE_EXC(EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE),
    ENCODE_EXC(EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE),
    ENCODE_EXC(EXC_BAD_ACCESS, KERN_CODESIGN_ERROR),
    ENCODE_EXC(EXC_SYSCALL, 128),
    ENCODE_EXC(EXC_SYSCALL, 0x6000),
#if defined(ARCH_CPU_X86_FAMILY)
    ENCODE_EXC(EXC_BAD_ACCESS, EXC_I386_GPFLT),
    ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_INVOP),
    ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT),
    ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_STKFLT),
    ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_DIV),
    ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_INTO),
    ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_EXTERR),
    ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_SSEEXTERR),
    ENCODE_EXC(EXC_SOFTWARE, EXC_I386_BOUND),
    ENCODE_EXC(EXC_BREAKPOINT, EXC_I386_SGL),
    ENCODE_EXC(EXC_BREAKPOINT, EXC_I386_BPT),
#endif
    // TODO(macos_arm64): Add arm64 test data.
#undef ENCODE_EXC

#define ENCODE_EXC_CRASH(type, code_0)                        \
  {                                                           \
    EXC_CRASH, (((type) & 0xf) << 20) | ((code_0) & 0xfffff), \
        ((type) << 16) | (code_0)                             \
  }
    ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS),
    ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE),
    ENCODE_EXC_CRASH(EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE),
    ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_CODESIGN_ERROR),
    ENCODE_EXC_CRASH(EXC_SYSCALL, 128),
    ENCODE_EXC_CRASH(EXC_SYSCALL, 0x6000),
#if defined(ARCH_CPU_X86_FAMILY)
    ENCODE_EXC_CRASH(EXC_BAD_ACCESS, EXC_I386_GPFLT),
    ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_INVOP),
    ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT),
    ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_STKFLT),
    ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_DIV),
    ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_INTO),
    ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_EXTERR),
    ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_SSEEXTERR),
    ENCODE_EXC_CRASH(EXC_SOFTWARE, EXC_I386_BOUND),
    ENCODE_EXC_CRASH(EXC_BREAKPOINT, EXC_I386_SGL),
    ENCODE_EXC_CRASH(EXC_BREAKPOINT, EXC_I386_BPT),
#endif
    // TODO(macos_arm64): Add arm64 test data.
#undef ENCODE_EXC_CRASH

#define ENCODE_EXC_CRASH_SIGNAL(signal) \
  { EXC_CRASH, (((signal) & 0xff) << 24), (EXC_CRASH << 16) | (signal) }
    ENCODE_EXC_CRASH_SIGNAL(SIGQUIT),
    ENCODE_EXC_CRASH_SIGNAL(SIGILL),
    ENCODE_EXC_CRASH_SIGNAL(SIGTRAP),
    ENCODE_EXC_CRASH_SIGNAL(SIGABRT),
    ENCODE_EXC_CRASH_SIGNAL(SIGEMT),
    ENCODE_EXC_CRASH_SIGNAL(SIGFPE),
    ENCODE_EXC_CRASH_SIGNAL(SIGBUS),
    ENCODE_EXC_CRASH_SIGNAL(SIGSEGV),
    ENCODE_EXC_CRASH_SIGNAL(SIGSYS),
#undef ENCODE_EXC_CRASH_SIGNAL

#define ENCODE_EXC_RESOURCE(type, flavor)                            \
  {                                                                  \
    EXC_RESOURCE, EXC_RESOURCE_ENCODE_TYPE_FLAVOR((type), (flavor)), \
        (EXC_RESOURCE << 16) | ((type) << 8) | (flavor)              \
  }
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR),
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR_FATAL),
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_WAKEUPS, FLAVOR_WAKEUPS_MONITOR),
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_MEMORY, FLAVOR_HIGH_WATERMARK),
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_IO, FLAVOR_IO_PHYSICAL_WRITES),
    ENCODE_EXC_RESOURCE(RESOURCE_TYPE_IO, FLAVOR_IO_LOGICAL_WRITES),
#undef ENCODE_EXC_RESOURCE

#define ENCODE_EXC_GUARD(type, flavor)                                         \
  {                                                                            \
    EXC_GUARD,                                                                 \
        static_cast<mach_exception_code_t>(static_cast<uint64_t>((type) & 0x7) \
                                           << 61) |                            \
            (static_cast<uint64_t>((1 << (flavor)) & 0x1ffffffff) << 32),      \
        (EXC_GUARD << 16) | ((type) << 8) | (flavor)                           \
  }
    ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 0),  // kGUARD_EXC_DESTROY
    ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 1),  // kGUARD_EXC_MOD_REFS
    ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 2),  // kGUARD_EXC_SET_CONTEXT
    ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 3),  // kGUARD_EXC_UNGUARDED
    ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 4),  // kGUARD_EXC_INCORRECT_GUARD

    // 2 is GUARD_TYPE_FD from 10.12.2 xnu-3789.31.2/bsd/sys/guarded.h.
    ENCODE_EXC_GUARD(2, 0),  // kGUARD_EXC_CLOSE
    ENCODE_EXC_GUARD(2, 1),  // kGUARD_EXC_DUP
    ENCODE_EXC_GUARD(2, 2),  // kGUARD_EXC_NOCLOEXEC
    ENCODE_EXC_GUARD(2, 3),  // kGUARD_EXC_SOCKET_IPC
    ENCODE_EXC_GUARD(2, 4),  // kGUARD_EXC_FILEPORT
    ENCODE_EXC_GUARD(2, 5),  // kGUARD_EXC_MISMATCH
    ENCODE_EXC_GUARD(2, 6),  // kGUARD_EXC_WRITE
#undef ENCODE_EXC_GUARD

    // Test that overflow saturates.
    {0x00010000, 0x00001000, static_cast<int32_t>(0xffff1000)},
    {0x00001000, 0x00010000, 0x1000ffff},
    {0x00010000, 0x00010000, static_cast<int32_t>(0xffffffff)},
  };

  for (size_t index = 0; index < std::size(kTestData); ++index) {
    const auto& test_data = kTestData[index];
    SCOPED_TRACE(base::StringPrintf("index %zu, exception 0x%x, code_0 0x%llx",
                                    index,
                                    test_data.exception,
                                    test_data.code_0));

    int32_t metrics_code =
        ExceptionCodeForMetrics(test_data.exception, test_data.code_0);

    EXPECT_EQ(metrics_code, test_data.metrics_code);
  }
}

TEST(ExceptionTypes, IsExceptionNonfatalResource) {
  const pid_t pid = getpid();

  mach_exception_code_t code =
      EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR);
  EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));

  if (MacOSVersionNumber() >= 10'10'00) {
    // FLAVOR_CPU_MONITOR_FATAL was introduced in OS X 10.10.
    code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_CPU,
                                           FLAVOR_CPU_MONITOR_FATAL);
    EXPECT_FALSE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));
  }

  // This assumes that WAKEMON_MAKE_FATAL is not set for this process. The
  // default is for WAKEMON_MAKE_FATAL to not be set, there’s no public API to
  // enable it, and nothing in this process should have enabled it.
  code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_WAKEUPS,
                                         FLAVOR_WAKEUPS_MONITOR);
  EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));

  code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_MEMORY,
                                         FLAVOR_HIGH_WATERMARK);
  EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));

  // Non-EXC_RESOURCE exceptions should never be considered nonfatal resource
  // exceptions, because they aren’t resource exceptions at all.
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0xb100001, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x0b00000, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x6000000, pid));
  EXPECT_FALSE(
      IsExceptionNonfatalResource(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BAD_INSTRUCTION, 1, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_ARITHMETIC, 1, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BREAKPOINT, 2, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(0, 0, pid));
  EXPECT_FALSE(IsExceptionNonfatalResource(kMachExceptionSimulated, 0, pid));
}

}  // namespace
}  // namespace test
}  // namespace crashpad
