// 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