// 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 frame) { int line = GetFrameInfo(frame)->line_number(); return line != StackFrameBase::kNone ? line : Message::kNoLineNumberInfo; } // static int StackTraceFrame::GetOneBasedLineNumber(Handle 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 frame) { int column = GetFrameInfo(frame)->column_number(); return column != StackFrameBase::kNone ? column : Message::kNoColumnInfo; } // static int StackTraceFrame::GetOneBasedColumnNumber(Handle 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 frame) { int id = GetFrameInfo(frame)->script_id(); return id != StackFrameBase::kNone ? id : Message::kNoScriptIdInfo; } // static int StackTraceFrame::GetPromiseAllIndex(Handle frame) { return GetFrameInfo(frame)->promise_all_index(); } // static int StackTraceFrame::GetFunctionOffset(Handle frame) { DCHECK(IsWasm(frame)); return GetFrameInfo(frame)->function_offset(); } // static Handle StackTraceFrame::GetFileName(Handle frame) { auto name = GetFrameInfo(frame)->script_name(); return handle(name, frame->GetIsolate()); } // static Handle StackTraceFrame::GetScriptNameOrSourceUrl( Handle frame) { auto name = GetFrameInfo(frame)->script_name_or_source_url(); return handle(name, frame->GetIsolate()); } // static Handle StackTraceFrame::GetFunctionName(Handle frame) { auto name = GetFrameInfo(frame)->function_name(); return handle(name, frame->GetIsolate()); } // static Handle StackTraceFrame::GetMethodName(Handle frame) { auto name = GetFrameInfo(frame)->method_name(); return handle(name, frame->GetIsolate()); } // static Handle StackTraceFrame::GetTypeName(Handle frame) { auto name = GetFrameInfo(frame)->type_name(); return handle(name, frame->GetIsolate()); } // static Handle StackTraceFrame::GetEvalOrigin(Handle frame) { auto origin = GetFrameInfo(frame)->eval_origin(); return handle(origin, frame->GetIsolate()); } // static Handle StackTraceFrame::GetWasmModuleName( Handle frame) { auto module = GetFrameInfo(frame)->wasm_module_name(); return handle(module, frame->GetIsolate()); } // static Handle StackTraceFrame::GetWasmInstance( Handle frame) { Object instance = GetFrameInfo(frame)->wasm_instance(); return handle(WasmInstanceObject::cast(instance), frame->GetIsolate()); } // static bool StackTraceFrame::IsEval(Handle frame) { return GetFrameInfo(frame)->is_eval(); } // static bool StackTraceFrame::IsConstructor(Handle frame) { return GetFrameInfo(frame)->is_constructor(); } // static bool StackTraceFrame::IsWasm(Handle frame) { return GetFrameInfo(frame)->is_wasm(); } // static bool StackTraceFrame::IsAsmJsWasm(Handle frame) { return GetFrameInfo(frame)->is_asmjs_wasm(); } // static bool StackTraceFrame::IsUserJavaScript(Handle frame) { return GetFrameInfo(frame)->is_user_java_script(); } // static bool StackTraceFrame::IsToplevel(Handle frame) { return GetFrameInfo(frame)->is_toplevel(); } // static bool StackTraceFrame::IsAsync(Handle frame) { return GetFrameInfo(frame)->is_async(); } // static bool StackTraceFrame::IsPromiseAll(Handle frame) { return GetFrameInfo(frame)->is_promise_all(); } // static Handle StackTraceFrame::GetFrameInfo( Handle frame) { if (frame->frame_info().IsUndefined()) InitializeFrameInfo(frame); return handle(StackFrameInfo::cast(frame->frame_info()), frame->GetIsolate()); } // static void StackTraceFrame::InitializeFrameInfo(Handle frame) { Isolate* isolate = frame->GetIsolate(); Handle 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 GetFrameArrayFromStackTrace(Isolate* isolate, Handle 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 frame(StackTraceFrame::cast(stack_trace->get(0)), isolate); return handle(FrameArray::cast(frame->frame_array()), isolate); } namespace { bool IsNonEmptyString(Handle object) { return (object->IsString() && String::cast(*object).length() > 0); } void AppendFileLocation(Isolate* isolate, Handle frame, IncrementalStringBuilder* builder) { Handle file_name = StackTraceFrame::GetScriptNameOrSourceUrl(frame); if (!file_name->IsString() && StackTraceFrame::IsEval(frame)) { Handle eval_origin = StackTraceFrame::GetEvalOrigin(frame); DCHECK(eval_origin->IsString()); builder->AppendString(Handle::cast(eval_origin)); builder->AppendCString(", "); // Expecting source position to follow. } if (IsNonEmptyString(file_name)) { builder->AppendString(Handle::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(""); } 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 subject, Handle 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 subject, Handle 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 frame, IncrementalStringBuilder* builder) { Handle type_name = StackTraceFrame::GetTypeName(frame); Handle method_name = StackTraceFrame::GetMethodName(frame); Handle function_name = StackTraceFrame::GetFunctionName(frame); if (IsNonEmptyString(function_name)) { Handle function_string = Handle::cast(function_name); if (IsNonEmptyString(type_name)) { Handle type_string = Handle::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 method_string = Handle::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::cast(type_name)); builder->AppendCharacter('.'); } if (IsNonEmptyString(method_name)) { builder->AppendString(Handle::cast(method_name)); } else { builder->AppendCString(""); } } } void SerializeJSStackFrame( Isolate* isolate, Handle frame, IncrementalStringBuilder& builder // NOLINT(runtime/references) ) { Handle 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_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::GetPromiseAllIndex(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::cast(function_name)); } else { builder.AppendCString(""); } } else if (IsNonEmptyString(function_name)) { builder.AppendString(Handle::cast(function_name)); } else { AppendFileLocation(isolate, frame, &builder); return; } builder.AppendCString(" ("); AppendFileLocation(isolate, frame, &builder); builder.AppendCString(")"); } void SerializeAsmJsWasmStackFrame( Isolate* isolate, Handle frame, IncrementalStringBuilder& builder // NOLINT(runtime/references) ) { // The string should look exactly as the respective javascript frame string. // Keep this method in line to // JSStackFrame::ToString(IncrementalStringBuilder&). Handle function_name = StackTraceFrame::GetFunctionName(frame); if (IsNonEmptyString(function_name)) { builder.AppendString(Handle::cast(function_name)); builder.AppendCString(" ("); } AppendFileLocation(isolate, frame, &builder); if (IsNonEmptyString(function_name)) builder.AppendCString(")"); return; } void SerializeWasmStackFrame( Isolate* isolate, Handle frame, IncrementalStringBuilder& builder // NOLINT(runtime/references) ) { Handle module_name = StackTraceFrame::GetWasmModuleName(frame); Handle 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::cast(function_name)); } else { builder.AppendString(Handle::cast(module_name)); if (!function_name->IsNull()) { builder.AppendCString("."); builder.AppendString(Handle::cast(function_name)); } } builder.AppendCString(" ("); } const int wasm_func_index = StackTraceFrame::GetLineNumber(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 frame, IncrementalStringBuilder& builder // NOLINT(runtime/references) ) { // 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 SerializeStackTraceFrame(Isolate* isolate, Handle frame) { IncrementalStringBuilder builder(isolate); SerializeStackTraceFrame(isolate, frame, builder); return builder.Finish(); } } // namespace internal } // namespace v8