blob: 6fe5316631554ef8172ad52a13d07a8f0755bf9e [file] [log] [blame]
// 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) {
auto name = GetFrameInfo(frame)->script_name();
return handle(name, frame->GetIsolate());
}
// 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