text: // Copyright 2018 the V8 project authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "include/v8config.h"
  
  #if V8_OS_LINUX
  #include <signal.h>
  #include <ucontext.h>
  #elif V8_OS_MACOSX
  #include <signal.h>
  #include <sys/ucontext.h>
  #elif V8_OS_WIN
  #include <windows.h>
  #endif
  
  #include "testing/gtest/include/gtest/gtest.h"
  
  #if V8_OS_POSIX
  #include "include/v8-wasm-trap-handler-posix.h"
  #elif V8_OS_WIN
  #include "include/v8-wasm-trap-handler-win.h"
  #endif
  #include "src/base/page-allocator.h"
  #include "src/codegen/assembler-inl.h"
  #include "src/codegen/macro-assembler-inl.h"
  #include "src/execution/simulator.h"
  #include "src/objects/backing-store.h"
  #include "src/trap-handler/trap-handler.h"
  #include "src/utils/allocation.h"
  #include "src/utils/vector.h"
  #include "src/wasm/wasm-engine.h"
  
  #include "test/common/assembler-tester.h"
  #include "test/unittests/test-utils.h"
  
  namespace v8 {
  namespace internal {
  namespace wasm {
  
  namespace {
  constexpr Register scratch = r10;
  bool g_test_handler_executed = false;
  #if V8_OS_LINUX || V8_OS_MACOSX
  struct sigaction g_old_segv_action;
  struct sigaction g_old_fpe_action;
  struct sigaction g_old_bus_action;  // We get SIGBUS on Mac sometimes.
  #elif V8_OS_WIN
  void* g_registered_handler = nullptr;
  #endif
  
  // The recovery address allows us to recover from an intentional crash.
  Address g_recovery_address;
  // Flag to indicate if the test handler should call the trap handler as a first
  // chance handler.
  bool g_use_as_first_chance_handler = false;
  }  // namespace
  
  #define __ masm.
  
  enum TrapHandlerStyle : int {
    // The test uses the default trap handler of V8.
    kDefault = 0,
    // The test installs the trap handler callback in its own test handler.
    kCallback = 1
  };
  
  std::string PrintTrapHandlerTestParam(
      ::testing::TestParamInfo<TrapHandlerStyle> info) {
    switch (info.param) {
      case kDefault:
        return "DefaultTrapHandler";
      case kCallback:
        return "Callback";
    }
    UNREACHABLE();
  }
  
  class TrapHandlerTest : public TestWithIsolate,
                          public ::testing::WithParamInterface<TrapHandlerStyle> {
   protected:
    void SetUp() override {
      backing_store_ = BackingStore::AllocateWasmMemory(i_isolate(), 1, 1,
                                                        SharedFlag::kNotShared);
      CHECK(backing_store_);
      CHECK(backing_store_->has_guard_regions());
      // The allocated backing store ends with a guard page.
      crash_address_ = reinterpret_cast<Address>(backing_store_->buffer_start()) +
                       backing_store_->byte_length() + 32;
      // Allocate a buffer for the generated code.
      buffer_ = AllocateAssemblerBuffer(AssemblerBase::kMinimalBufferSize,
                                        GetRandomMmapAddr());
  
      InitRecoveryCode();
  
  #if V8_OS_LINUX || V8_OS_MACOSX
      // Set up a signal handler to recover from the expected crash.
      struct sigaction action;
      action.sa_sigaction = SignalHandler;
      sigemptyset(&action.sa_mask);
      action.sa_flags = SA_SIGINFO;
      // SIGSEGV happens for wasm oob memory accesses on Linux.
      CHECK_EQ(0, sigaction(SIGSEGV, &action, &g_old_segv_action));
      // SIGBUS happens for wasm oob memory accesses on macOS.
      CHECK_EQ(0, sigaction(SIGBUS, &action, &g_old_bus_action));
      // SIGFPE to simulate crashes which are not handled by the trap handler.
      CHECK_EQ(0, sigaction(SIGFPE, &action, &g_old_fpe_action));
  #elif V8_OS_WIN
      g_registered_handler =
          AddVectoredExceptionHandler(/*first=*/0, TestHandler);
  #endif
    }
  
    void TearDown() override {
      // We should always have left wasm code.
      CHECK(!GetThreadInWasmFlag());
      buffer_.reset();
      recovery_buffer_.reset();
      backing_store_.reset();
  
      // Clean up the trap handler
      trap_handler::RemoveTrapHandler();
      if (!g_test_handler_executed) {
  #if V8_OS_LINUX || V8_OS_MACOSX
        // The test handler cleans up the signal handler setup in the test. If the
        // test handler was not called, we have to do the cleanup ourselves.
        CHECK_EQ(0, sigaction(SIGSEGV, &g_old_segv_action, nullptr));
        CHECK_EQ(0, sigaction(SIGFPE, &g_old_fpe_action, nullptr));
        CHECK_EQ(0, sigaction(SIGBUS, &g_old_bus_action, nullptr));
  #elif V8_OS_WIN
        RemoveVectoredExceptionHandler(g_registered_handler);
        g_registered_handler = nullptr;
  #endif
      }
    }
  
    void InitRecoveryCode() {
      // Create a code snippet where we can jump to to recover from a signal or
      // exception. The code snippet only consists of a return statement.
      recovery_buffer_ = AllocateAssemblerBuffer(
          AssemblerBase::kMinimalBufferSize, GetRandomMmapAddr());
  
      MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                          recovery_buffer_->CreateView());
      int recovery_offset = __ pc_offset();
      __ Pop(scratch);
      __ Ret();
      CodeDesc desc;
      masm.GetCode(nullptr, &desc);
      recovery_buffer_->MakeExecutable();
      g_recovery_address =
          reinterpret_cast<Address>(desc.buffer + recovery_offset);
    }
  
  #if V8_OS_LINUX || V8_OS_MACOSX
    static void SignalHandler(int signal, siginfo_t* info, void* context) {
      if (g_use_as_first_chance_handler) {
        if (v8::TryHandleWebAssemblyTrapPosix(signal, info, context)) {
          return;
        }
      }
  
      // Reset the signal handler, to avoid that this signal handler is called
      // repeatedly.
      sigaction(SIGSEGV, &g_old_segv_action, nullptr);
      sigaction(SIGFPE, &g_old_fpe_action, nullptr);
      sigaction(SIGBUS, &g_old_bus_action, nullptr);
  
      g_test_handler_executed = true;
      // Set the $rip to the recovery code.
      ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
  #if V8_OS_LINUX
      uc->uc_mcontext.gregs[REG_RIP] = g_recovery_address;
  #else  // V8_OS_MACOSX
      uc->uc_mcontext->__ss.__rip = g_recovery_address;
  #endif
    }
  #endif
  
  #if V8_OS_WIN
    static LONG WINAPI TestHandler(EXCEPTION_POINTERS* exception) {
      if (g_use_as_first_chance_handler) {
        if (v8::TryHandleWebAssemblyTrapWindows(exception)) {
          return EXCEPTION_CONTINUE_EXECUTION;
        }
      }
      RemoveVectoredExceptionHandler(g_registered_handler);
      g_registered_handler = nullptr;
      g_test_handler_executed = true;
      exception->ContextRecord->Rip = g_recovery_address;
      return EXCEPTION_CONTINUE_EXECUTION;
    }
  #endif
  
   public:
    void SetupTrapHandler(TrapHandlerStyle style) {
      bool use_default_handler = style == kDefault;
      g_use_as_first_chance_handler = !use_default_handler;
      CHECK(v8::V8::EnableWebAssemblyTrapHandler(use_default_handler));
    }
  
    void GenerateSetThreadInWasmFlagCode(MacroAssembler* masm) {
      masm->Move(scratch,
                 i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
                 RelocInfo::NONE);
      masm->movl(MemOperand(scratch, 0), Immediate(1));
    }
  
    void GenerateResetThreadInWasmFlagCode(MacroAssembler* masm) {
      masm->Move(scratch,
                 i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
                 RelocInfo::NONE);
      masm->movl(MemOperand(scratch, 0), Immediate(0));
    }
  
    bool GetThreadInWasmFlag() {
      return *reinterpret_cast<int*>(
          trap_handler::GetThreadInWasmThreadLocalAddress());
    }
  
    // Execute the code in buffer.
    void ExecuteBuffer() {
      buffer_->MakeExecutable();
      GeneratedCode<void>::FromAddress(
          i_isolate(), reinterpret_cast<Address>(buffer_->start()))
          .Call();
      CHECK(!g_test_handler_executed);
    }
  
    // Execute the code in buffer. We expect a crash which we recover from in the
    // test handler.
    void ExecuteExpectCrash(TestingAssemblerBuffer* buffer,
                            bool check_wasm_flag = true) {
      CHECK(!g_test_handler_executed);
      buffer->MakeExecutable();
      GeneratedCode<void>::FromAddress(i_isolate(),
                                       reinterpret_cast<Address>(buffer->start()))
          .Call();
      CHECK(g_test_handler_executed);
      g_test_handler_executed = false;
      if (check_wasm_flag) CHECK(!GetThreadInWasmFlag());
    }
  
    bool test_handler_executed() { return g_test_handler_executed; }
  
    // The backing store used for testing the trap handler.
    std::unique_ptr<BackingStore> backing_store_;
  
    // Address within the guard region of the wasm memory. Accessing this memory
    // address causes a signal or exception.
    Address crash_address_;
  
    // Buffer for generated code.
    std::unique_ptr<TestingAssemblerBuffer> buffer_;
    // Buffer for the code for the landing pad of the test handler.
    std::unique_ptr<TestingAssemblerBuffer> recovery_buffer_;
  };
  
  TEST_P(TrapHandlerTest, TestTrapHandlerRecovery) {
    // Test that the wasm trap handler can recover a memory access violation in
    // wasm code (we fake the wasm code and the access violation).
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    GenerateSetThreadInWasmFlagCode(&masm);
    __ Move(scratch, crash_address_, RelocInfo::NONE);
    int crash_offset = __ pc_offset();
    __ testl(MemOperand(scratch, 0), Immediate(1));
    int recovery_offset = __ pc_offset();
    GenerateResetThreadInWasmFlagCode(&masm);
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    SetupTrapHandler(GetParam());
    trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
                                                                 recovery_offset};
    trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
                                      desc.instr_size, 1, &protected_instruction);
  
    ExecuteBuffer();
  }
  
  TEST_P(TrapHandlerTest, TestReleaseHandlerData) {
    // Test that after we release handler data in the trap handler, it cannot
    // recover from the specific memory access violation anymore.
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    GenerateSetThreadInWasmFlagCode(&masm);
    __ Move(scratch, crash_address_, RelocInfo::NONE);
    int crash_offset = __ pc_offset();
    __ testl(MemOperand(scratch, 0), Immediate(1));
    int recovery_offset = __ pc_offset();
    GenerateResetThreadInWasmFlagCode(&masm);
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
                                                                 recovery_offset};
    int handler_id = trap_handler::RegisterHandlerData(
        reinterpret_cast<Address>(desc.buffer), desc.instr_size, 1,
        &protected_instruction);
  
    SetupTrapHandler(GetParam());
  
    ExecuteBuffer();
  
    // Deregister from the trap handler. The trap handler should not do the
    // recovery now.
    trap_handler::ReleaseHandlerData(handler_id);
  
    ExecuteExpectCrash(buffer_.get());
  }
  
  TEST_P(TrapHandlerTest, TestNoThreadInWasmFlag) {
    // That that if the thread_in_wasm flag is not set, the trap handler does not
    // get active.
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    __ Move(scratch, crash_address_, RelocInfo::NONE);
    int crash_offset = __ pc_offset();
    __ testl(MemOperand(scratch, 0), Immediate(1));
    int recovery_offset = __ pc_offset();
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
                                                                 recovery_offset};
    trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
                                      desc.instr_size, 1, &protected_instruction);
  
    SetupTrapHandler(GetParam());
  
    ExecuteExpectCrash(buffer_.get());
  }
  
  TEST_P(TrapHandlerTest, TestCrashInWasmNoProtectedInstruction) {
    // Test that if the crash in wasm happened at an instruction which is not
    // protected, then the trap handler does not handle it.
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    GenerateSetThreadInWasmFlagCode(&masm);
    int no_crash_offset = __ pc_offset();
    __ Move(scratch, crash_address_, RelocInfo::NONE);
    __ testl(MemOperand(scratch, 0), Immediate(1));
    // Offset where the crash is not happening.
    int recovery_offset = __ pc_offset();
    GenerateResetThreadInWasmFlagCode(&masm);
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    trap_handler::ProtectedInstructionData protected_instruction{no_crash_offset,
                                                                 recovery_offset};
    trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
                                      desc.instr_size, 1, &protected_instruction);
  
    SetupTrapHandler(GetParam());
  
    ExecuteExpectCrash(buffer_.get());
  }
  
  TEST_P(TrapHandlerTest, TestCrashInWasmWrongCrashType) {
    // Test that if the crash reason is not a memory access violation, then the
    // wasm trap handler does not handle it.
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    GenerateSetThreadInWasmFlagCode(&masm);
    __ xorq(scratch, scratch);
    int crash_offset = __ pc_offset();
    __ divq(scratch);
    // Offset where the crash is not happening.
    int recovery_offset = __ pc_offset();
    GenerateResetThreadInWasmFlagCode(&masm);
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
                                                                 recovery_offset};
    trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
                                      desc.instr_size, 1, &protected_instruction);
  
    SetupTrapHandler(GetParam());
  
  #if V8_OS_POSIX
    // On Posix, the V8 default trap handler does not register for SIGFPE,
    // therefore the thread-in-wasm flag is never reset in this test. We
    // therefore do not check the value of this flag.
    bool check_wasm_flag = GetParam() != kDefault;
  #elif V8_OS_WIN
    // On Windows, the trap handler returns immediately if not an exception of
    // interest.
    bool check_wasm_flag = false;
  #else
    bool check_wasm_flag = true;
  #endif
    ExecuteExpectCrash(buffer_.get(), check_wasm_flag);
    if (!check_wasm_flag) {
      // Reset the thread-in-wasm flag because it was probably not reset in the
      // trap handler.
      *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
    }
  }
  
  class CodeRunner : public v8::base::Thread {
   public:
    CodeRunner(TrapHandlerTest* test, TestingAssemblerBuffer* buffer)
        : Thread(Options("CodeRunner")), test_(test), buffer_(buffer) {}
  
    void Run() override { test_->ExecuteExpectCrash(buffer_); }
  
   private:
    TrapHandlerTest* test_;
    TestingAssemblerBuffer* buffer_;
  };
  
  TEST_P(TrapHandlerTest, TestCrashInOtherThread) {
    // Test setup:
    // The current thread enters wasm land (sets the thread_in_wasm flag)
    // A second thread crashes at a protected instruction without having the flag
    // set.
    MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
                        buffer_->CreateView());
    __ Push(scratch);
    __ Move(scratch, crash_address_, RelocInfo::NONE);
    int crash_offset = __ pc_offset();
    __ testl(MemOperand(scratch, 0), Immediate(1));
    int recovery_offset = __ pc_offset();
    __ Pop(scratch);
    __ Ret();
    CodeDesc desc;
    masm.GetCode(nullptr, &desc);
  
    trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
                                                                 recovery_offset};
    trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
                                      desc.instr_size, 1, &protected_instruction);
  
    SetupTrapHandler(GetParam());
  
    CodeRunner runner(this, buffer_.get());
    CHECK(!GetThreadInWasmFlag());
    // Set the thread-in-wasm flag manually in this thread.
    *trap_handler::GetThreadInWasmThreadLocalAddress() = 1;
    CHECK(runner.Start());
    runner.Join();
    CHECK(GetThreadInWasmFlag());
    // Reset the thread-in-wasm flag.
    *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
  }
  
  INSTANTIATE_TEST_SUITE_P(Traps, TrapHandlerTest,
                           ::testing::Values(kDefault, kCallback),
                           PrintTrapHandlerTestParam);
  
  #undef __
  }  // namespace wasm
  }  // namespace internal
  }  // namespace v8
  