// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "lib/stacktrace.h"
#include "vm/bootstrap_natives.h"
#include "vm/debugger.h"
#include "vm/exceptions.h"
#include "vm/native_entry.h"
#include "vm/object_store.h"
#include "vm/runtime_entry.h"
#include "vm/stack_frame.h"
#include "vm/stack_trace.h"

namespace dart {

DECLARE_FLAG(bool, show_invisible_frames);

static constexpr intptr_t kDefaultStackAllocation = 8;

static StackTracePtr CreateStackTraceObject(
    Zone* zone,
    const GrowableObjectArray& code_list,
    const GrowableArray<uword>& pc_offset_list) {
  const auto& code_array =
      Array::Handle(zone, Array::MakeFixedLength(code_list));
  const auto& pc_offset_array = TypedData::Handle(
      zone, TypedData::New(kUintPtrCid, pc_offset_list.length()));
  {
    NoSafepointScope no_safepoint;
    memmove(pc_offset_array.DataAddr(0), pc_offset_list.data(),
            pc_offset_list.length() * kWordSize);
  }
  return StackTrace::New(code_array, pc_offset_array);
}

// Gets current stack trace for `thread`.
static StackTracePtr CurrentStackTrace(Thread* thread,
                                       intptr_t skip_frames = 1) {
  Zone* zone = thread->zone();

  const auto& code_array = GrowableObjectArray::ZoneHandle(
      zone, GrowableObjectArray::New(kDefaultStackAllocation));
  GrowableArray<uword> pc_offset_array(kDefaultStackAllocation);

  // Collect the frames.
  StackTraceUtils::CollectFrames(thread, skip_frames,
                                 [&](const StackTraceUtils::Frame& frame) {
                                   if (!frame.bytecode.IsNull()) {
                                     code_array.Add(frame.bytecode);
                                   } else {
                                     code_array.Add(frame.code);
                                   }
                                   pc_offset_array.Add(frame.pc_offset);
                                 });

  return CreateStackTraceObject(zone, code_array, pc_offset_array);
}

StackTracePtr GetStackTraceForException() {
  Thread* thread = Thread::Current();
  return CurrentStackTrace(thread, 0);
}

DEFINE_NATIVE_ENTRY(StackTrace_current, 0, 0) {
  return CurrentStackTrace(thread);
}

static void AppendFrames(const GrowableObjectArray& code_list,
                         GrowableArray<uword>* pc_offset_list,
                         int skip_frames) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
                            StackFrameIterator::kNoCrossThreadIteration);
  StackFrame* frame = frames.NextFrame();
  ASSERT(frame != nullptr);  // We expect to find a dart invocation frame.
  Code& code = Code::Handle(zone);
  Bytecode& bytecode = Bytecode::Handle(zone);
  for (; frame != nullptr; frame = frames.NextFrame()) {
    if (!frame->IsDartFrame()) {
      continue;
    }
    if (skip_frames > 0) {
      skip_frames--;
      continue;
    }

    if (frame->is_interpreted()) {
      bytecode = frame->LookupDartBytecode();
      if (bytecode.function() == Function::null()) {
        continue;
      }
      const intptr_t pc_offset = frame->pc() - bytecode.PayloadStart();
      code_list.Add(bytecode);
      pc_offset_list->Add(pc_offset);
    } else {
      code = frame->LookupDartCode();
      const intptr_t pc_offset = frame->pc() - code.PayloadStart();
      code_list.Add(code);
      pc_offset_list->Add(pc_offset);
    }
  }
}

// Creates a StackTrace object from the current stack.
//
// Skips the first skip_frames Dart frames.
const StackTrace& GetCurrentStackTrace(int skip_frames) {
  Zone* zone = Thread::Current()->zone();
  const GrowableObjectArray& code_list =
      GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
  GrowableArray<uword> pc_offset_list;
  AppendFrames(code_list, &pc_offset_list, skip_frames);

  const StackTrace& stacktrace = StackTrace::Handle(
      zone, CreateStackTraceObject(zone, code_list, pc_offset_list));
  return stacktrace;
}

bool HasStack() {
  Thread* thread = Thread::Current();
  StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
                            StackFrameIterator::kNoCrossThreadIteration);
  StackFrame* frame = frames.NextFrame();
  return frame != nullptr;
}

}  // namespace dart
