// Copyright 2019 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 "src/api/api-inl.h"
#include "test/cctest/wasm/wasm-atomics-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"

namespace v8 {
namespace internal {
namespace wasm {
namespace test_run_wasm_exceptions {

WASM_COMPILED_EXEC_TEST(TryCatchThrow) {
  TestSignatures sigs;
  EXPERIMENTAL_FLAG_SCOPE(eh);
  WasmRunner<uint32_t, uint32_t> r(execution_tier);
  uint32_t except = r.builder().AddException(sigs.v_v());
  constexpr uint32_t kResult0 = 23;
  constexpr uint32_t kResult1 = 42;

  // Build the main test function.
  BUILD(r, WASM_TRY_CATCH_T(kWasmI32,
                            WASM_STMTS(WASM_I32V(kResult1),
                                       WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
                                               WASM_THROW(except))),
                            WASM_STMTS(WASM_I32V(kResult0)), except));

  // Need to call through JS to allow for creation of stack traces.
  r.CheckCallViaJS(kResult0, 0);
  r.CheckCallViaJS(kResult1, 1);
}

WASM_COMPILED_EXEC_TEST(TryCatchCallDirect) {
  TestSignatures sigs;
  EXPERIMENTAL_FLAG_SCOPE(eh);
  WasmRunner<uint32_t, uint32_t> r(execution_tier);
  uint32_t except = r.builder().AddException(sigs.v_v());
  constexpr uint32_t kResult0 = 23;
  constexpr uint32_t kResult1 = 42;

  // Build a throwing helper function.
  WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
  BUILD(throw_func, WASM_THROW(except));

  // Build the main test function.
  BUILD(r, WASM_TRY_CATCH_T(
               kWasmI32,
               WASM_STMTS(WASM_I32V(kResult1),
                          WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
                                  WASM_STMTS(WASM_CALL_FUNCTION(
                                                 throw_func.function_index(),
                                                 WASM_I32V(7), WASM_I32V(9)),
                                             WASM_DROP))),
               WASM_STMTS(WASM_I32V(kResult0)), except));

  // Need to call through JS to allow for creation of stack traces.
  r.CheckCallViaJS(kResult0, 0);
  r.CheckCallViaJS(kResult1, 1);
}

WASM_COMPILED_EXEC_TEST(TryCatchCallIndirect) {
  TestSignatures sigs;
  EXPERIMENTAL_FLAG_SCOPE(eh);
  WasmRunner<uint32_t, uint32_t> r(execution_tier);
  uint32_t except = r.builder().AddException(sigs.v_v());
  constexpr uint32_t kResult0 = 23;
  constexpr uint32_t kResult1 = 42;

  // Build a throwing helper function.
  WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
  BUILD(throw_func, WASM_THROW(except));
  byte sig_index = r.builder().AddSignature(sigs.i_ii());
  throw_func.SetSigIndex(0);

  // Add an indirect function table.
  uint16_t indirect_function_table[] = {
      static_cast<uint16_t>(throw_func.function_index())};
  r.builder().AddIndirectFunctionTable(indirect_function_table,
                                       arraysize(indirect_function_table));

  // Build the main test function.
  BUILD(r,
        WASM_TRY_CATCH_T(
            kWasmI32,
            WASM_STMTS(WASM_I32V(kResult1),
                       WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
                               WASM_STMTS(WASM_CALL_INDIRECT(
                                              sig_index, WASM_I32V(7),
                                              WASM_I32V(9), WASM_LOCAL_GET(0)),
                                          WASM_DROP))),
            WASM_STMTS(WASM_I32V(kResult0)), except));

  // Need to call through JS to allow for creation of stack traces.
  r.CheckCallViaJS(kResult0, 0);
  r.CheckCallViaJS(kResult1, 1);
}

WASM_COMPILED_EXEC_TEST(TryCatchCallExternal) {
  TestSignatures sigs;
  EXPERIMENTAL_FLAG_SCOPE(eh);
  HandleScope scope(CcTest::InitIsolateOnce());
  const char* source = "(function() { throw 'ball'; })";
  Handle<JSFunction> js_function =
      Handle<JSFunction>::cast(v8::Utils::OpenHandle(
          *v8::Local<v8::Function>::Cast(CompileRun(source))));
  ManuallyImportedJSFunction import = {sigs.i_ii(), js_function};
  WasmRunner<uint32_t, uint32_t> r(execution_tier, &import);
  constexpr uint32_t kResult0 = 23;
  constexpr uint32_t kResult1 = 42;
  constexpr uint32_t kJSFunc = 0;

  // Build the main test function.
  BUILD(r, WASM_TRY_CATCH_ALL_T(
               kWasmI32,
               WASM_STMTS(
                   WASM_I32V(kResult1),
                   WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
                           WASM_STMTS(WASM_CALL_FUNCTION(kJSFunc, WASM_I32V(7),
                                                         WASM_I32V(9)),
                                      WASM_DROP))),
               WASM_STMTS(WASM_I32V(kResult0))));

  // Need to call through JS to allow for creation of stack traces.
  r.CheckCallViaJS(kResult0, 0);
  r.CheckCallViaJS(kResult1, 1);
}

namespace {

void TestTrapNotCaught(byte* code, size_t code_size,
                       TestExecutionTier execution_tier) {
  TestSignatures sigs;
  EXPERIMENTAL_FLAG_SCOPE(eh);
  WasmRunner<uint32_t> r(execution_tier, nullptr, "main",
                         kRuntimeExceptionSupport);
  r.builder().AddMemory(kWasmPageSize);
  constexpr uint32_t kResultSuccess = 23;
  constexpr uint32_t kResultCaught = 47;

  // Build a trapping helper function.
  WasmFunctionCompiler& trap_func = r.NewFunction(sigs.i_ii());
  trap_func.Build(code, code + code_size);

  // Build the main test function.
  BUILD(r, WASM_TRY_CATCH_ALL_T(
               kWasmI32,
               WASM_STMTS(WASM_I32V(kResultSuccess),
                          WASM_CALL_FUNCTION(trap_func.function_index(),
                                             WASM_I32V(7), WASM_I32V(9)),
                          WASM_DROP),
               WASM_STMTS(WASM_I32V(kResultCaught))));

  // Need to call through JS to allow for creation of stack traces.
  r.CheckCallViaJSTraps();
}

}  // namespace

WASM_COMPILED_EXEC_TEST(TryCatchTrapUnreachable) {
  byte code[] = {WASM_UNREACHABLE};
  TestTrapNotCaught(code, arraysize(code), execution_tier);
}

WASM_COMPILED_EXEC_TEST(TryCatchTrapMemOutOfBounds) {
  byte code[] = {WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(-1))};
  TestTrapNotCaught(code, arraysize(code), execution_tier);
}

WASM_COMPILED_EXEC_TEST(TryCatchTrapDivByZero) {
  byte code[] = {WASM_I32_DIVS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
  TestTrapNotCaught(code, arraysize(code), execution_tier);
}

WASM_COMPILED_EXEC_TEST(TryCatchTrapRemByZero) {
  byte code[] = {WASM_I32_REMS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
  TestTrapNotCaught(code, arraysize(code), execution_tier);
}

}  // namespace test_run_wasm_exceptions
}  // namespace wasm
}  // namespace internal
}  // namespace v8
