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