// 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/objects/stack-frame-info.h"

#include "src/objects/stack-frame-info-inl.h"
#include "src/strings/string-builder-inl.h"

namespace v8 {
namespace internal {

// static
int StackTraceFrame::GetLineNumber(Handle<StackTraceFrame> frame) {
  int line = GetFrameInfo(frame)->line_number();
  return line != StackFrameBase::kNone ? line : Message::kNoLineNumberInfo;
}

// static
int StackTraceFrame::GetOneBasedLineNumber(Handle<StackTraceFrame> frame) {
  // JavaScript line numbers are already 1-based. Wasm line numbers need
  // to be adjusted.
  int line = StackTraceFrame::GetLineNumber(frame);
  if (StackTraceFrame::IsWasm(frame) && line >= 0) line++;
  return line;
}

// static
int StackTraceFrame::GetColumnNumber(Handle<StackTraceFrame> frame) {
  int column = GetFrameInfo(frame)->column_number();
  return column != StackFrameBase::kNone ? column : Message::kNoColumnInfo;
}

// static
int StackTraceFrame::GetOneBasedColumnNumber(Handle<StackTraceFrame> frame) {
  // JavaScript colun numbers are already 1-based. Wasm column numbers need
  // to be adjusted.
  int column = StackTraceFrame::GetColumnNumber(frame);
  if (StackTraceFrame::IsWasm(frame) && column >= 0) column++;
  return column;
}

// static
int StackTraceFrame::GetScriptId(Handle<StackTraceFrame> frame) {
  Isolate* isolate = frame->GetIsolate();

  // Use FrameInfo if it's already there, but avoid initializing it for just
  // the script id, as it is much more expensive than just getting this
  // directly. See GetScriptNameOrSourceUrl() for more detail.
  int id;
  if (!frame->frame_info().IsUndefined()) {
    id = GetFrameInfo(frame)->script_id();
  } else {
    FrameArrayIterator it(
        isolate, handle(FrameArray::cast(frame->frame_array()), isolate),
        frame->frame_index());
    DCHECK(it.HasFrame());
    id = it.Frame()->GetScriptId();
  }
  return id != StackFrameBase::kNone ? id : Message::kNoScriptIdInfo;
}

// static
int StackTraceFrame::GetPromiseCombinatorIndex(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->promise_combinator_index();
}

// static
int StackTraceFrame::GetFunctionOffset(Handle<StackTraceFrame> frame) {
  DCHECK(IsWasm(frame));
  return GetFrameInfo(frame)->function_offset();
}

// static
int StackTraceFrame::GetWasmFunctionIndex(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->wasm_function_index();
}

// static
Handle<Object> StackTraceFrame::GetFileName(Handle<StackTraceFrame> frame) {
  Isolate* isolate = frame->GetIsolate();

  // Use FrameInfo if it's already there, but avoid initializing it for just
  // the file name, as it is much more expensive than just getting this
  // directly. See GetScriptNameOrSourceUrl() for more detail.
  if (!frame->frame_info().IsUndefined()) {
    auto name = GetFrameInfo(frame)->script_name();
    return handle(name, isolate);
  }
  FrameArrayIterator it(isolate,
                        handle(FrameArray::cast(frame->frame_array()), isolate),
                        frame->frame_index());
  DCHECK(it.HasFrame());
  return it.Frame()->GetFileName();
}

// static
Handle<Object> StackTraceFrame::GetScriptNameOrSourceUrl(
    Handle<StackTraceFrame> frame) {
  Isolate* isolate = frame->GetIsolate();
  // TODO(caseq, szuend): the logic below is a workaround for crbug.com/1057211.
  // We should probably have a dedicated API for the scenario described in the
  // bug above and make getters of this class behave consistently.
  // See https://bit.ly/2wkbuIy for further discussion.
  // Use FrameInfo if it's already there, but avoid initializing it for just
  // the script name, as it is much more expensive than just getting this
  // directly.
  if (!frame->frame_info().IsUndefined()) {
    auto name = GetFrameInfo(frame)->script_name_or_source_url();
    return handle(name, isolate);
  }
  FrameArrayIterator it(isolate,
                        handle(FrameArray::cast(frame->frame_array()), isolate),
                        frame->frame_index());
  DCHECK(it.HasFrame());
  return it.Frame()->GetScriptNameOrSourceUrl();
}

// static
Handle<Object> StackTraceFrame::GetFunctionName(Handle<StackTraceFrame> frame) {
  auto name = GetFrameInfo(frame)->function_name();
  return handle(name, frame->GetIsolate());
}

// static
Handle<Object> StackTraceFrame::GetMethodName(Handle<StackTraceFrame> frame) {
  auto name = GetFrameInfo(frame)->method_name();
  return handle(name, frame->GetIsolate());
}

// static
Handle<Object> StackTraceFrame::GetTypeName(Handle<StackTraceFrame> frame) {
  auto name = GetFrameInfo(frame)->type_name();
  return handle(name, frame->GetIsolate());
}

// static
Handle<Object> StackTraceFrame::GetEvalOrigin(Handle<StackTraceFrame> frame) {
  auto origin = GetFrameInfo(frame)->eval_origin();
  return handle(origin, frame->GetIsolate());
}

// static
Handle<Object> StackTraceFrame::GetWasmModuleName(
    Handle<StackTraceFrame> frame) {
  auto module = GetFrameInfo(frame)->wasm_module_name();
  return handle(module, frame->GetIsolate());
}

// static
Handle<WasmInstanceObject> StackTraceFrame::GetWasmInstance(
    Handle<StackTraceFrame> frame) {
  Object instance = GetFrameInfo(frame)->wasm_instance();
  return handle(WasmInstanceObject::cast(instance), frame->GetIsolate());
}

// static
bool StackTraceFrame::IsEval(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_eval();
}

// static
bool StackTraceFrame::IsConstructor(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_constructor();
}

// static
bool StackTraceFrame::IsWasm(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_wasm();
}

// static
bool StackTraceFrame::IsAsmJsWasm(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_asmjs_wasm();
}

// static
bool StackTraceFrame::IsUserJavaScript(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_user_java_script();
}

// static
bool StackTraceFrame::IsToplevel(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_toplevel();
}

// static
bool StackTraceFrame::IsAsync(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_async();
}

// static
bool StackTraceFrame::IsPromiseAll(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_promise_all();
}

// static
bool StackTraceFrame::IsPromiseAny(Handle<StackTraceFrame> frame) {
  return GetFrameInfo(frame)->is_promise_any();
}

// static
Handle<StackFrameInfo> StackTraceFrame::GetFrameInfo(
    Handle<StackTraceFrame> frame) {
  if (frame->frame_info().IsUndefined()) InitializeFrameInfo(frame);
  return handle(StackFrameInfo::cast(frame->frame_info()), frame->GetIsolate());
}

// static
void StackTraceFrame::InitializeFrameInfo(Handle<StackTraceFrame> frame) {
  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
               "SymbolizeStackFrame", "frameIndex", frame->frame_index());

  Isolate* isolate = frame->GetIsolate();
  Handle<StackFrameInfo> frame_info = isolate->factory()->NewStackFrameInfo(
      handle(FrameArray::cast(frame->frame_array()), isolate),
      frame->frame_index());
  frame->set_frame_info(*frame_info);

  // After initializing, we no longer need to keep a reference
  // to the frame_array.
  frame->set_frame_array(ReadOnlyRoots(isolate).undefined_value());
  frame->set_frame_index(-1);
}

Handle<FrameArray> GetFrameArrayFromStackTrace(Isolate* isolate,
                                               Handle<FixedArray> stack_trace) {
  // For the empty case, a empty FrameArray needs to be allocated so the rest
  // of the code doesn't has to be special cased everywhere.
  if (stack_trace->length() == 0) {
    return isolate->factory()->NewFrameArray(0);
  }

  // Retrieve the FrameArray from the first StackTraceFrame.
  DCHECK_GT(stack_trace->length(), 0);
  Handle<StackTraceFrame> frame(StackTraceFrame::cast(stack_trace->get(0)),
                                isolate);
  return handle(FrameArray::cast(frame->frame_array()), isolate);
}

namespace {

bool IsNonEmptyString(Handle<Object> object) {
  return (object->IsString() && String::cast(*object).length() > 0);
}

void AppendFileLocation(Isolate* isolate, Handle<StackTraceFrame> frame,
                        IncrementalStringBuilder* builder) {
  Handle<Object> file_name = StackTraceFrame::GetScriptNameOrSourceUrl(frame);
  if (!file_name->IsString() && StackTraceFrame::IsEval(frame)) {
    Handle<Object> eval_origin = StackTraceFrame::GetEvalOrigin(frame);
    DCHECK(eval_origin->IsString());
    builder->AppendString(Handle<String>::cast(eval_origin));
    builder->AppendCString(", ");  // Expecting source position to follow.
  }

  if (IsNonEmptyString(file_name)) {
    builder->AppendString(Handle<String>::cast(file_name));
  } else {
    // Source code does not originate from a file and is not native, but we
    // can still get the source position inside the source string, e.g. in
    // an eval string.
    builder->AppendCString("<anonymous>");
  }

  int line_number = StackTraceFrame::GetLineNumber(frame);
  if (line_number != Message::kNoLineNumberInfo) {
    builder->AppendCharacter(':');
    builder->AppendInt(line_number);

    int column_number = StackTraceFrame::GetColumnNumber(frame);
    if (column_number != Message::kNoColumnInfo) {
      builder->AppendCharacter(':');
      builder->AppendInt(column_number);
    }
  }
}

int StringIndexOf(Isolate* isolate, Handle<String> subject,
                  Handle<String> pattern) {
  if (pattern->length() > subject->length()) return -1;
  return String::IndexOf(isolate, subject, pattern, 0);
}

// Returns true iff
// 1. the subject ends with '.' + pattern, or
// 2. subject == pattern.
bool StringEndsWithMethodName(Isolate* isolate, Handle<String> subject,
                              Handle<String> pattern) {
  if (String::Equals(isolate, subject, pattern)) return true;

  FlatStringReader subject_reader(isolate, String::Flatten(isolate, subject));
  FlatStringReader pattern_reader(isolate, String::Flatten(isolate, pattern));

  int pattern_index = pattern_reader.length() - 1;
  int subject_index = subject_reader.length() - 1;
  for (int i = 0; i <= pattern_reader.length(); i++) {  // Iterate over len + 1.
    if (subject_index < 0) {
      return false;
    }

    const uc32 subject_char = subject_reader.Get(subject_index);
    if (i == pattern_reader.length()) {
      if (subject_char != '.') return false;
    } else if (subject_char != pattern_reader.Get(pattern_index)) {
      return false;
    }

    pattern_index--;
    subject_index--;
  }

  return true;
}

void AppendMethodCall(Isolate* isolate, Handle<StackTraceFrame> frame,
                      IncrementalStringBuilder* builder) {
  Handle<Object> type_name = StackTraceFrame::GetTypeName(frame);
  Handle<Object> method_name = StackTraceFrame::GetMethodName(frame);
  Handle<Object> function_name = StackTraceFrame::GetFunctionName(frame);

  if (IsNonEmptyString(function_name)) {
    Handle<String> function_string = Handle<String>::cast(function_name);
    if (IsNonEmptyString(type_name)) {
      Handle<String> type_string = Handle<String>::cast(type_name);
      bool starts_with_type_name =
          (StringIndexOf(isolate, function_string, type_string) == 0);
      if (!starts_with_type_name) {
        builder->AppendString(type_string);
        builder->AppendCharacter('.');
      }
    }
    builder->AppendString(function_string);

    if (IsNonEmptyString(method_name)) {
      Handle<String> method_string = Handle<String>::cast(method_name);
      if (!StringEndsWithMethodName(isolate, function_string, method_string)) {
        builder->AppendCString(" [as ");
        builder->AppendString(method_string);
        builder->AppendCharacter(']');
      }
    }
  } else {
    if (IsNonEmptyString(type_name)) {
      builder->AppendString(Handle<String>::cast(type_name));
      builder->AppendCharacter('.');
    }
    if (IsNonEmptyString(method_name)) {
      builder->AppendString(Handle<String>::cast(method_name));
    } else {
      builder->AppendCString("<anonymous>");
    }
  }
}

void SerializeJSStackFrame(Isolate* isolate, Handle<StackTraceFrame> frame,
                           IncrementalStringBuilder* builder) {
  Handle<Object> function_name = StackTraceFrame::GetFunctionName(frame);

  const bool is_toplevel = StackTraceFrame::IsToplevel(frame);
  const bool is_async = StackTraceFrame::IsAsync(frame);
  const bool is_promise_all = StackTraceFrame::IsPromiseAll(frame);
  const bool is_promise_any = StackTraceFrame::IsPromiseAny(frame);
  const bool is_constructor = StackTraceFrame::IsConstructor(frame);
  // Note: Keep the {is_method_call} predicate in sync with the corresponding
  //       predicate in factory.cc where the StackFrameInfo is created.
  //       Otherwise necessary fields for serialzing this frame might be
  //       missing.
  const bool is_method_call = !(is_toplevel || is_constructor);

  if (is_async) {
    builder->AppendCString("async ");
  }
  if (is_promise_all) {
    builder->AppendCString("Promise.all (index ");
    builder->AppendInt(StackTraceFrame::GetPromiseCombinatorIndex(frame));
    builder->AppendCString(")");
    return;
  }
  if (is_promise_any) {
    builder->AppendCString("Promise.any (index ");
    builder->AppendInt(StackTraceFrame::GetPromiseCombinatorIndex(frame));
    builder->AppendCString(")");
    return;
  }
  if (is_method_call) {
    AppendMethodCall(isolate, frame, builder);
  } else if (is_constructor) {
    builder->AppendCString("new ");
    if (IsNonEmptyString(function_name)) {
      builder->AppendString(Handle<String>::cast(function_name));
    } else {
      builder->AppendCString("<anonymous>");
    }
  } else if (IsNonEmptyString(function_name)) {
    builder->AppendString(Handle<String>::cast(function_name));
  } else {
    AppendFileLocation(isolate, frame, builder);
    return;
  }

  builder->AppendCString(" (");
  AppendFileLocation(isolate, frame, builder);
  builder->AppendCString(")");
}

void SerializeAsmJsWasmStackFrame(Isolate* isolate,
                                  Handle<StackTraceFrame> frame,
                                  IncrementalStringBuilder* builder) {
  // The string should look exactly as the respective javascript frame string.
  // Keep this method in line to
  // JSStackFrame::ToString(IncrementalStringBuilder&).
  Handle<Object> function_name = StackTraceFrame::GetFunctionName(frame);

  if (IsNonEmptyString(function_name)) {
    builder->AppendString(Handle<String>::cast(function_name));
    builder->AppendCString(" (");
  }

  AppendFileLocation(isolate, frame, builder);

  if (IsNonEmptyString(function_name)) builder->AppendCString(")");

  return;
}

bool IsAnonymousWasmScript(Isolate* isolate, Handle<StackTraceFrame> frame,
                           Handle<Object> url) {
  DCHECK(url->IsString());
  Handle<String> anonymous_prefix =
      isolate->factory()->InternalizeString(StaticCharVector("wasm://wasm/"));
  return (StackTraceFrame::IsWasm(frame) &&
          StringIndexOf(isolate, Handle<String>::cast(url), anonymous_prefix) >=
              0);
}

void SerializeWasmStackFrame(Isolate* isolate, Handle<StackTraceFrame> frame,
                             IncrementalStringBuilder* builder) {
  Handle<Object> module_name = StackTraceFrame::GetWasmModuleName(frame);
  Handle<Object> function_name = StackTraceFrame::GetFunctionName(frame);
  const bool has_name = !module_name->IsNull() || !function_name->IsNull();
  if (has_name) {
    if (module_name->IsNull()) {
      builder->AppendString(Handle<String>::cast(function_name));
    } else {
      builder->AppendString(Handle<String>::cast(module_name));
      if (!function_name->IsNull()) {
        builder->AppendCString(".");
        builder->AppendString(Handle<String>::cast(function_name));
      }
    }
    builder->AppendCString(" (");
  }

  Handle<Object> url = StackTraceFrame::GetScriptNameOrSourceUrl(frame);
  if (IsNonEmptyString(url) && !IsAnonymousWasmScript(isolate, frame, url)) {
    builder->AppendString(Handle<String>::cast(url));
  } else {
    builder->AppendCString("<anonymous>");
  }
  builder->AppendCString(":");

  const int wasm_func_index = StackTraceFrame::GetWasmFunctionIndex(frame);
  builder->AppendCString("wasm-function[");
  builder->AppendInt(wasm_func_index);
  builder->AppendCString("]:");

  char buffer[16];
  SNPrintF(ArrayVector(buffer), "0x%x",
           StackTraceFrame::GetColumnNumber(frame));
  builder->AppendCString(buffer);

  if (has_name) builder->AppendCString(")");
}

}  // namespace

void SerializeStackTraceFrame(Isolate* isolate, Handle<StackTraceFrame> frame,
                              IncrementalStringBuilder* builder) {
  // Ordering here is important, as AsmJs frames are also marked as Wasm.
  if (StackTraceFrame::IsAsmJsWasm(frame)) {
    SerializeAsmJsWasmStackFrame(isolate, frame, builder);
  } else if (StackTraceFrame::IsWasm(frame)) {
    SerializeWasmStackFrame(isolate, frame, builder);
  } else {
    SerializeJSStackFrame(isolate, frame, builder);
  }
}

MaybeHandle<String> SerializeStackTraceFrame(Isolate* isolate,
                                             Handle<StackTraceFrame> frame) {
  IncrementalStringBuilder builder(isolate);
  SerializeStackTraceFrame(isolate, frame, &builder);
  return builder.Finish();
}

}  // namespace internal
}  // namespace v8