// 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/v8.h"

#include "src/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/heap/spaces.h"
#include "src/isolate.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"

namespace v8 {
namespace internal {
namespace test_unwinder {

static void* unlimited_stack_base = std::numeric_limits<void*>::max();

TEST(Unwind_BadState_Fail) {
  UnwindState unwind_state;  // Fields are intialized to nullptr.
  RegisterState register_state;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 unlimited_stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_NULL(register_state.fp);
  CHECK_NULL(register_state.sp);
  CHECK_NULL(register_state.pc);
}

TEST(Unwind_BuiltinPCInMiddle_Success) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  uintptr_t stack[3];
  void* stack_base = stack + arraysize(stack);
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
  stack[1] = 202;  // Return address into C++ code.
  stack[2] = 303;  // The SP points here in the caller's frame.

  register_state.sp = stack;
  register_state.fp = stack;

  // Put the current PC inside of a valid builtin.
  Code builtin = i_isolate->builtins()->builtin(Builtins::kStringEqual);
  const uintptr_t offset = 40;
  CHECK_LT(offset, builtin->InstructionSize());
  register_state.pc =
      reinterpret_cast<void*>(builtin->InstructionStart() + offset);

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);
  CHECK(unwound);
  CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.sp);
  CHECK_EQ(reinterpret_cast<void*>(202), register_state.pc);
}

// The unwinder should be able to unwind even if we haven't properly set up the
// current frame, as long as there is another JS frame underneath us (i.e. as
// long as the PC isn't in JSEntry). This test puts the PC at the start
// of a JS builtin and creates a fake JSEntry frame before it on the stack. The
// unwinder should be able to unwind to the C++ frame before the JSEntry frame.
TEST(Unwind_BuiltinPCAtStart_Success) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  uintptr_t stack[6];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  // Return address into JS code. It doesn't matter that this is not actually in
  // JSEntry, because we only check that for the top frame.
  stack[1] = reinterpret_cast<uintptr_t>(code + 10);
  stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP (rbp).
  stack[3] = 303;  // Return address into C++ code.
  stack[4] = 404;
  stack[5] = 505;

  register_state.sp = stack;
  register_state.fp = stack + 2;  // FP to the JSEntry frame.

  // Put the current PC at the start of a valid builtin, so that we are setting
  // up the frame.
  Code builtin = i_isolate->builtins()->builtin(Builtins::kStringEqual);
  register_state.pc = reinterpret_cast<void*>(builtin->InstructionStart());

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);

  CHECK(unwound);
  CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 4), register_state.sp);
  CHECK_EQ(reinterpret_cast<void*>(303), register_state.pc);
}

const char* foo_source = R"(
  function foo(a, b) {
    let x = a * b;
    let y = x ^ b;
    let z = y / a;
    return x + y - z;
  }
  foo(1, 2);
  foo(1, 2);
  %OptimizeFunctionOnNextCall(foo);
  foo(1, 2);
)";

// Check that we can unwind when the pc is within an optimized code object on
// the V8 heap.
TEST(Unwind_CodeObjectPCInMiddle_Success) {
  FLAG_allow_natives_syntax = true;
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  HandleScope scope(i_isolate);

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  uintptr_t stack[3];
  void* stack_base = stack + arraysize(stack);
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
  stack[1] = 202;  // Return address into C++ code.
  stack[2] = 303;  // The SP points here in the caller's frame.

  register_state.sp = stack;
  register_state.fp = stack;

  // Create an on-heap code object. Make sure we run the function so that it is
  // compiled and not just marked for lazy compilation.
  CompileRun(foo_source);
  v8::Local<v8::Function> local_foo = v8::Local<v8::Function>::Cast(
      env.local()->Global()->Get(env.local(), v8_str("foo")).ToLocalChecked());
  Handle<JSFunction> foo =
      Handle<JSFunction>::cast(v8::Utils::OpenHandle(*local_foo));

  // Put the current PC inside of the created code object.
  AbstractCode abstract_code = foo->abstract_code();
  // We don't produce optimized code when run with --no-opt.
  if (!abstract_code->IsCode() && FLAG_opt == false) return;
  CHECK(abstract_code->IsCode());

  Code code = abstract_code->GetCode();
  // We don't want the offset too early or it could be the `push rbp`
  // instruction (which is not at the start of generated code, because the lazy
  // deopt check happens before frame setup).
  const uintptr_t offset = code->InstructionSize() - 20;
  CHECK_LT(offset, code->InstructionSize());
  Address pc = code->InstructionStart() + offset;
  register_state.pc = reinterpret_cast<void*>(pc);

  // Check that the created code is within the code range that we get from the
  // API.
  Address start = reinterpret_cast<Address>(unwind_state.code_range.start);
  CHECK(pc >= start && pc < start + unwind_state.code_range.length_in_bytes);

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);
  CHECK(unwound);
  CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.sp);
  CHECK_EQ(reinterpret_cast<void*>(202), register_state.pc);
}

// If the PC is within JSEntry but we haven't set up the frame yet, then we
// cannot unwind.
TEST(Unwind_JSEntryBeforeFrame_Fail) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  // Pretend that it takes 5 instructions to set up the frame in JSEntry.
  unwind_state.js_entry_stub.code.start = code + 10;
  unwind_state.js_entry_stub.code.length_in_bytes = 10 * sizeof(uintptr_t);

  uintptr_t stack[10];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = 121;
  stack[3] = 131;
  stack[4] = 141;
  stack[5] = 151;
  stack[6] = 100;  // Return address into C++ code.
  stack[7] = 303;  // The SP points here in the caller's frame.
  stack[8] = 404;
  stack[9] = 505;

  register_state.sp = stack + 5;
  register_state.fp = stack + 9;

  // Put the current PC inside of JSEntry, before the frame is set up.
  register_state.pc = code + 12;
  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.sp);
  CHECK_EQ(code + 12, register_state.pc);

  // Change the PC to a few instructions later, after the frame is set up.
  register_state.pc = code + 16;
  unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                            stack_base);
  // TODO(petermarshall): More precisely check position within JSEntry rather
  // than just assuming the frame is unreadable.
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.sp);
  CHECK_EQ(code + 16, register_state.pc);
}

TEST(Unwind_OneJSFrame_Success) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  // Use a fake code range so that we can initialize it to 0s.
  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  // Our fake stack has two frames - one C++ frame and one JS frame (on top).
  // The stack grows from high addresses to low addresses.
  uintptr_t stack[10];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = 121;
  stack[3] = 131;
  stack[4] = 141;
  stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
  stack[6] = 100;  // Return address into C++ code.
  stack[7] = 303;  // The SP points here in the caller's frame.
  stack[8] = 404;
  stack[9] = 505;

  register_state.sp = stack;
  register_state.fp = stack + 5;

  // Put the current PC inside of the code range so it looks valid.
  register_state.pc = code + 30;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);

  CHECK(unwound);
  CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 7), register_state.sp);
  CHECK_EQ(reinterpret_cast<void*>(100), register_state.pc);
}

// Creates a fake stack with two JS frames on top of a C++ frame and checks that
// the unwinder correctly unwinds past the JS frames and returns the C++ frame's
// details.
TEST(Unwind_TwoJSFrames_Success) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  // Use a fake code range so that we can initialize it to 0s.
  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  // Our fake stack has three frames - one C++ frame and two JS frames (on top).
  // The stack grows from high addresses to low addresses.
  uintptr_t stack[10];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP (rbp).
  // The fake return address is in the JS code range.
  stack[3] = reinterpret_cast<uintptr_t>(code + 10);
  stack[4] = 141;
  stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
  stack[6] = 100;  // Return address into C++ code.
  stack[7] = 303;  // The SP points here in the caller's frame.
  stack[8] = 404;
  stack[9] = 505;

  register_state.sp = stack;
  register_state.fp = stack + 2;

  // Put the current PC inside of the code range so it looks valid.
  register_state.pc = code + 30;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);

  CHECK(unwound);
  CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
  CHECK_EQ(reinterpret_cast<void*>(stack + 7), register_state.sp);
  CHECK_EQ(reinterpret_cast<void*>(100), register_state.pc);
}

// If the PC is in JSEntry then the frame might not be set up correctly, meaning
// we can't unwind the stack properly.
TEST(Unwind_JSEntry_Fail) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  Code js_entry = i_isolate->heap()->builtin(Builtins::kJSEntry);
  byte* start = reinterpret_cast<byte*>(js_entry->InstructionStart());
  register_state.pc = start + 10;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 unlimited_stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_NULL(register_state.fp);
  CHECK_NULL(register_state.sp);
  CHECK_EQ(start + 10, register_state.pc);
}

TEST(Unwind_StackBounds_Basic) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  const size_t code_length = 10;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  uintptr_t stack[3];
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
  stack[1] = 202;  // Return address into C++ code.
  stack[2] = 303;  // The SP points here in the caller's frame.

  register_state.sp = stack;
  register_state.fp = stack;
  register_state.pc = code;

  void* wrong_stack_base = reinterpret_cast<void*>(
      reinterpret_cast<uintptr_t>(stack) - sizeof(uintptr_t));
  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 wrong_stack_base);
  CHECK(!unwound);

  // Correct the stack base and unwinding should succeed.
  void* correct_stack_base = stack + arraysize(stack);
  unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                            correct_stack_base);
  CHECK(unwound);
}

TEST(Unwind_StackBounds_WithUnwinding) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  RegisterState register_state;

  // Use a fake code range so that we can initialize it to 0s.
  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  unwind_state.code_range.start = code;
  unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);

  // Our fake stack has two frames - one C++ frame and one JS frame (on top).
  // The stack grows from high addresses to low addresses.
  uintptr_t stack[11];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = 121;
  stack[3] = 131;
  stack[4] = 141;
  stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
  stack[6] = reinterpret_cast<uintptr_t>(code + 20);  // JS code.
  stack[7] = 303;  // The SP points here in the caller's frame.
  stack[8] = 404;
  stack[9] = reinterpret_cast<uintptr_t>(stack) +
             (12 * sizeof(uintptr_t));                 // saved FP (OOB).
  stack[10] = reinterpret_cast<uintptr_t>(code + 20);  // JS code.

  register_state.sp = stack;
  register_state.fp = stack + 5;

  // Put the current PC inside of the code range so it looks valid.
  register_state.pc = code + 30;

  // Unwind will fail because stack[9] FP points outside of the stack.
  bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                                 stack_base);
  CHECK(!unwound);

  // Change the return address so that it is not in range.
  stack[10] = 202;
  unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                            stack_base);
  CHECK(!unwound);
}

TEST(PCIsInV8_BadState_Fail) {
  UnwindState unwind_state;
  void* pc = nullptr;

  CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
}

TEST(PCIsInV8_ValidStateNullPC_Fail) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();
  void* pc = nullptr;

  CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
}

void TestRangeBoundaries(const UnwindState& unwind_state, byte* range_start,
                         size_t range_length) {
  void* pc = range_start - 1;
  CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = range_start;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = range_start + 1;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = range_start + range_length - 1;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = range_start + range_length;
  CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = range_start + range_length + 1;
  CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
}

TEST(PCIsInV8_InCodeOrEmbeddedRange) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  UnwindState unwind_state = isolate->GetUnwindState();

  byte* code_range_start = const_cast<byte*>(
      reinterpret_cast<const byte*>(unwind_state.code_range.start));
  size_t code_range_length = unwind_state.code_range.length_in_bytes;
  TestRangeBoundaries(unwind_state, code_range_start, code_range_length);

  byte* embedded_range_start = const_cast<byte*>(
      reinterpret_cast<const byte*>(unwind_state.embedded_code_range.start));
  size_t embedded_range_length =
      unwind_state.embedded_code_range.length_in_bytes;
  TestRangeBoundaries(unwind_state, embedded_range_start,
                      embedded_range_length);
}

// PCIsInV8 doesn't check if the PC is in JSEntrydirectly. It's assumed that the
// CodeRange or EmbeddedCodeRange contain JSEntry.
TEST(PCIsInV8_InJSEntryRange) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  UnwindState unwind_state = isolate->GetUnwindState();

  Code js_entry = i_isolate->heap()->builtin(Builtins::kJSEntry);
  byte* start = reinterpret_cast<byte*>(js_entry->InstructionStart());
  size_t length = js_entry->InstructionSize();

  void* pc = start;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = start + 1;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
  pc = start + length - 1;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
}

// Large code objects can be allocated in large object space. Check that this is
// inside the CodeRange.
TEST(PCIsInV8_LargeCodeObject) {
  FLAG_allow_natives_syntax = true;
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  HandleScope scope(i_isolate);

  UnwindState unwind_state = isolate->GetUnwindState();

  // Create a big function that ends up in CODE_LO_SPACE.
  const int instruction_size = Page::kPageSize + 1;
  STATIC_ASSERT(instruction_size > kMaxRegularHeapObjectSize);
  std::unique_ptr<byte[]> instructions(new byte[instruction_size]);

  CodeDesc desc;
  desc.buffer = instructions.get();
  desc.buffer_size = instruction_size;
  desc.instr_size = instruction_size;
  desc.reloc_size = 0;
  desc.constant_pool_size = 0;
  desc.unwinding_info = nullptr;
  desc.unwinding_info_size = 0;
  desc.origin = nullptr;
  Handle<Object> self_ref;
  Handle<Code> foo_code =
      i_isolate->factory()->NewCode(desc, Code::WASM_FUNCTION, self_ref);

  CHECK(i_isolate->heap()->InSpace(*foo_code, CODE_LO_SPACE));
  byte* start = reinterpret_cast<byte*>(foo_code->InstructionStart());

  void* pc = start;
  CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
}

}  // namespace test_unwinder
}  // namespace internal
}  // namespace v8