https://github.com/DrSergei created https://github.com/llvm/llvm-project/pull/173226
This patch finishes migration to structured types and removes `LegacyRequestHandler`. >From 45ddcb394c5648e4d0b1f3a2f203dec90531dba0 Mon Sep 17 00:00:00 2001 From: Druzhkov Sergei <[email protected]> Date: Sat, 20 Dec 2025 22:28:19 +0300 Subject: [PATCH] [lldb-dap] Migrate stackTrace request to structured types --- lldb/tools/lldb-dap/Handler/RequestHandler.h | 19 +- .../Handler/StackTraceRequestHandler.cpp | 255 +++++++++--------- lldb/tools/lldb-dap/JSONUtils.cpp | 158 ----------- lldb/tools/lldb-dap/JSONUtils.h | 50 ---- .../lldb-dap/Protocol/ProtocolRequests.cpp | 18 ++ .../lldb-dap/Protocol/ProtocolRequests.h | 38 +++ .../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 54 ++++ lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 86 ++++++ lldb/unittests/DAP/ProtocolRequestsTest.cpp | 68 +++++ 9 files changed, 394 insertions(+), 352 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index a18a8049c804d..a01fb23005b41 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -79,16 +79,6 @@ class BaseRequestHandler { DAP &dap; }; -/// FIXME: Migrate callers to typed RequestHandler for improved type handling. -class LegacyRequestHandler : public BaseRequestHandler { - using BaseRequestHandler::BaseRequestHandler; - virtual void operator()(const llvm::json::Object &request) const = 0; - void operator()(const protocol::Request &request) const override { - auto req = toJSON(request); - (*this)(*req.getAsObject()); - } -}; - template <typename Args> llvm::Expected<Args> parseArgs(const protocol::Request &request) { if (!is_optional_v<Args> && !request.arguments) @@ -542,11 +532,14 @@ class SourceRequestHandler final Run(const protocol::SourceArguments &args) const override; }; -class StackTraceRequestHandler : public LegacyRequestHandler { +class StackTraceRequestHandler + : public RequestHandler<protocol::StackTraceArguments, + llvm::Expected<protocol::StackTraceResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "stackTrace"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::StackTraceResponseBody> + Run(const protocol::StackTraceArguments &args) const override; FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureDelayedStackTraceLoading}; } diff --git a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp index 77ef952a1e343..362d9bbfb7a4d 100644 --- a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp @@ -8,14 +8,105 @@ #include "DAP.h" #include "EventHelper.h" -#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" +#include "ProtocolUtils.h" #include "RequestHandler.h" +#include "lldb/API/SBStream.h" -namespace lldb_dap { +using namespace lldb_dap; +using namespace lldb_dap::protocol; -/// Page size used for reporting addtional frames in the 'stackTrace' request. +/// Page size used for reporting additional frames in the 'stackTrace' request. static constexpr int StackPageSize = 20; +// Create a "StackFrame" object for a LLDB frame object. +static StackFrame CreateStackFrame(DAP &dap, lldb::SBFrame &frame, + lldb::SBFormat &format) { + StackFrame stack_frame; + stack_frame.id = MakeDAPFrameID(frame); + + lldb::SBStream stream; + if (format && frame.GetDescriptionWithFormat(format, stream).Success()) { + stack_frame.name = stream.GetData(); + + // `function_name` can be a nullptr, which throws an error when assigned to + // an `std::string`. + } else if (const char *name = frame.GetDisplayFunctionName()) { + stack_frame.name = name; + } + + if (stack_frame.name.empty()) { + // If the function name is unavailable, display the pc address as a 16-digit + // hex string, e.g. "0x0000000000012345" + stack_frame.name = GetLoadAddressString(frame.GetPC()); + } + + // We only include `[opt]` if a custom frame format is not specified. + if (!format && frame.GetFunction().GetIsOptimized()) + stack_frame.name += " [opt]"; + + std::optional<protocol::Source> source = dap.ResolveSource(frame); + if (source && !IsAssemblySource(*source)) { + // This is a normal source with a valid line entry. + auto line_entry = frame.GetLineEntry(); + stack_frame.line = line_entry.GetLine(); + stack_frame.column = line_entry.GetColumn(); + } else if (frame.GetSymbol().IsValid()) { + // This is a source where the disassembly is used, but there is a valid + // symbol. Calculate the line of the current PC from the start of the + // current symbol. + lldb::SBInstructionList inst_list = dap.target.ReadInstructions( + frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr); + size_t inst_line = inst_list.GetSize(); + + // Line numbers are 1-based. + stack_frame.line = inst_line + 1; + stack_frame.column = 1; + } else { + // No valid line entry or symbol. + stack_frame.line = 1; + stack_frame.column = 1; + } + + stack_frame.source = std::move(source); + stack_frame.instructionPointerReference = frame.GetPC(); + + if (frame.IsArtificial() || frame.IsHidden()) + stack_frame.presentationHint = StackFrame::ePresentationHintSubtle; + lldb::SBModule module = frame.GetModule(); + if (module.IsValid()) { + if (const llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty()) + stack_frame.moduleId = uuid.str(); + } + + return stack_frame; +} + +// Create a "StackFrame" label object for a LLDB thread. +static StackFrame CreateExtendedStackFrameLabel(lldb::SBThread &thread, + lldb::SBFormat &format) { + StackFrame stack_frame; + lldb::SBStream stream; + if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { + stack_frame.name = stream.GetData(); + } else { + const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); + const char *queue_name = thread.GetQueueName(); + if (queue_name != nullptr) { + stack_frame.name = llvm::formatv("Enqueued from {0} (Thread {1})", + queue_name, thread_idx); + } else { + stack_frame.name = llvm::formatv("Thread {0}", thread_idx); + } + } + + stack_frame.id = thread.GetThreadID() + 1; + stack_frame.presentationHint = StackFrame::ePresentationHintLabel; + + return stack_frame; +} + // Fill in the stack frames of the thread. // // Threads stacks may contain runtime specific extended backtraces, when @@ -50,13 +141,12 @@ static constexpr int StackPageSize = 20; // s=3,l=3 = [th0->s3, label1, th1->s0] static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, lldb::SBFormat &frame_format, - llvm::json::Array &stack_frames, int64_t &offset, - const int64_t start_frame, const int64_t levels, - const bool include_all) { + std::vector<StackFrame> &stack_frames, + uint32_t &offset, const uint32_t start_frame, + const uint32_t levels, const bool include_all) { bool reached_end_of_stack = false; - for (int64_t i = start_frame; - static_cast<int64_t>(stack_frames.size()) < levels; i++) { - if (i == -1) { + for (uint32_t i = start_frame; stack_frames.size() < levels; i++) { + if (i == UINT32_MAX) { stack_frames.emplace_back( CreateExtendedStackFrameLabel(thread, frame_format)); continue; @@ -83,9 +173,9 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, reached_end_of_stack = FillStackFrames( dap, backtrace, frame_format, stack_frames, offset, - (start_frame - offset) > 0 ? start_frame - offset : -1, levels, + start_frame > offset ? start_frame - offset : UINT32_MAX, levels, include_all); - if (static_cast<int64_t>(stack_frames.size()) >= levels) + if (stack_frames.size() >= levels) break; } } @@ -93,150 +183,53 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, return reached_end_of_stack; } -// "StackTraceRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "StackTrace request; value of command field is -// 'stackTrace'. The request returns a stacktrace from the current execution -// state.", "properties": { -// "command": { -// "type": "string", -// "enum": [ "stackTrace" ] -// }, -// "arguments": { -// "$ref": "#/definitions/StackTraceArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "StackTraceArguments": { -// "type": "object", -// "description": "Arguments for 'stackTrace' request.", -// "properties": { -// "threadId": { -// "type": "integer", -// "description": "Retrieve the stacktrace for this thread." -// }, -// "startFrame": { -// "type": "integer", -// "description": "The index of the first frame to return; if omitted -// frames start at 0." -// }, -// "levels": { -// "type": "integer", -// "description": "The maximum number of frames to return. If levels is -// not specified or 0, all frames are returned." -// }, -// "format": { -// "$ref": "#/definitions/StackFrameFormat", -// "description": "Specifies details on how to format the stack frames. -// The attribute is only honored by a debug adapter if the corresponding -// capability `supportsValueFormattingOptions` is true." -// } -// }, -// "required": [ "threadId" ] -// }, -// "StackTraceResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to `stackTrace` request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "stackFrames": { -// "type": "array", -// "items": { -// "$ref": "#/definitions/StackFrame" -// }, -// "description": "The frames of the stackframe. If the array has -// length zero, there are no stackframes available. This means that -// there is no location information available." -// }, -// "totalFrames": { -// "type": "integer", -// "description": "The total number of frames available in the -// stack. If omitted or if `totalFrames` is larger than the -// available frames, a client is expected to request frames until -// a request returns less frames than requested (which indicates -// the end of the stack). Returning monotonically increasing -// `totalFrames` values for subsequent requests can be used to -// enforce paging in the client." -// } -// }, -// "required": [ "stackFrames" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -void StackTraceRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - lldb::SBError error; - const auto *arguments = request.getObject("arguments"); - lldb::SBThread thread = dap.GetLLDBThread(*arguments); - llvm::json::Array stack_frames; - llvm::json::Object body; +llvm::Expected<protocol::StackTraceResponseBody> +StackTraceRequestHandler::Run(const protocol::StackTraceArguments &args) const { + lldb::SBThread thread = dap.GetLLDBThread(args.threadId); lldb::SBFormat frame_format = dap.frame_format; bool include_all = dap.configuration.displayExtendedBacktrace; - if (const auto *format = arguments->getObject("format")) { - // Indicates that all stack frames should be included, even those the debug - // adapter might otherwise hide. - include_all = GetBoolean(format, "includeAll").value_or(false); + if (args.format) { + const StackFrameFormat &format = *args.format; - // Parse the properties that have a corresponding format string. - // FIXME: Support "parameterTypes" and "hex". - const bool module = GetBoolean(format, "module").value_or(false); - const bool line = GetBoolean(format, "line").value_or(false); - const bool parameters = GetBoolean(format, "parameters").value_or(false); - const bool parameter_names = - GetBoolean(format, "parameterNames").value_or(false); - const bool parameter_values = - GetBoolean(format, "parameterValues").value_or(false); + include_all = format.includeAll; + // FIXME: Support "parameterTypes" and "hex". // Only change the format string if we have to. - if (module || line || parameters || parameter_names || parameter_values) { + if (format.module || format.line || format.parameters || + format.parameterNames || format.parameterValues) { std::string format_str; llvm::raw_string_ostream os(format_str); - if (module) + if (format.module) os << "{${module.file.basename} }"; - if (line) + if (format.line) os << "{${line.file.basename}:${line.number}:${line.column} }"; - if (parameters || parameter_names || parameter_values) + if (format.parameters || format.parameterNames || format.parameterValues) os << "{${function.name-with-args}}"; else os << "{${function.name-without-args}}"; lldb::SBError error; frame_format = lldb::SBFormat(format_str.c_str(), error); - assert(error.Success()); + if (error.Fail()) + return ToError(error); } } + StackTraceResponseBody body; if (thread.IsValid()) { - const auto start_frame = - GetInteger<uint64_t>(arguments, "startFrame").value_or(0); - const auto levels = GetInteger<uint64_t>(arguments, "levels").value_or(0); - int64_t offset = 0; - bool reached_end_of_stack = FillStackFrames( - dap, thread, frame_format, stack_frames, offset, start_frame, - levels == 0 ? INT64_MAX : levels, include_all); - body.try_emplace("totalFrames", - start_frame + stack_frames.size() + - (reached_end_of_stack ? 0 : StackPageSize)); + const auto levels = args.levels == 0 ? UINT32_MAX : args.levels; + uint32_t offset = 0; + bool reached_end_of_stack = + FillStackFrames(dap, thread, frame_format, body.stackFrames, offset, + args.startFrame, levels, include_all); + body.totalFrames = args.startFrame + body.stackFrames.size() + + (reached_end_of_stack ? 0 : StackPageSize); } - body.try_emplace("stackFrames", std::move(stack_frames)); - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + return body; } - -} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 1beee416bf333..5815f1ea394a0 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -430,164 +430,6 @@ llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { return event; } -// "StackFrame": { -// "type": "object", -// "description": "A Stackframe contains the source location.", -// "properties": { -// "id": { -// "type": "integer", -// "description": "An identifier for the stack frame. It must be unique -// across all threads. This id can be used to retrieve -// the scopes of the frame with the 'scopesRequest' or -// to restart the execution of a stackframe." -// }, -// "name": { -// "type": "string", -// "description": "The name of the stack frame, typically a method name." -// }, -// "source": { -// "$ref": "#/definitions/Source", -// "description": "The optional source of the frame." -// }, -// "line": { -// "type": "integer", -// "description": "The line within the file of the frame. If source is -// null or doesn't exist, line is 0 and must be ignored." -// }, -// "column": { -// "type": "integer", -// "description": "The column within the line. If source is null or -// doesn't exist, column is 0 and must be ignored." -// }, -// "endLine": { -// "type": "integer", -// "description": "An optional end line of the range covered by the -// stack frame." -// }, -// "endColumn": { -// "type": "integer", -// "description": "An optional end column of the range covered by the -// stack frame." -// }, -// "instructionPointerReference": { -// "type": "string", -// "description": "A memory reference for the current instruction -// pointer in this frame." -// }, -// "moduleId": { -// "type": ["integer", "string"], -// "description": "The module associated with this frame, if any." -// }, -// "presentationHint": { -// "type": "string", -// "enum": [ "normal", "label", "subtle" ], -// "description": "An optional hint for how to present this frame in -// the UI. A value of 'label' can be used to indicate -// that the frame is an artificial frame that is used -// as a visual label or separator. A value of 'subtle' -// can be used to change the appearance of a frame in -// a 'subtle' way." -// } -// }, -// "required": [ "id", "name", "line", "column" ] -// } -llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame, - lldb::SBFormat &format) { - llvm::json::Object object; - int64_t frame_id = MakeDAPFrameID(frame); - object.try_emplace("id", frame_id); - - std::string frame_name; - lldb::SBStream stream; - if (format && frame.GetDescriptionWithFormat(format, stream).Success()) { - frame_name = stream.GetData(); - - // `function_name` can be a nullptr, which throws an error when assigned to - // an `std::string`. - } else if (const char *name = frame.GetDisplayFunctionName()) { - frame_name = name; - } - - if (frame_name.empty()) { - // If the function name is unavailable, display the pc address as a 16-digit - // hex string, e.g. "0x0000000000012345" - frame_name = GetLoadAddressString(frame.GetPC()); - } - - // We only include `[opt]` if a custom frame format is not specified. - if (!format && frame.GetFunction().GetIsOptimized()) - frame_name += " [opt]"; - - EmplaceSafeString(object, "name", frame_name); - - std::optional<protocol::Source> source = dap.ResolveSource(frame); - - if (source && !IsAssemblySource(*source)) { - // This is a normal source with a valid line entry. - auto line_entry = frame.GetLineEntry(); - object.try_emplace("line", line_entry.GetLine()); - auto column = line_entry.GetColumn(); - object.try_emplace("column", column); - } else if (frame.GetSymbol().IsValid()) { - // This is a source where the disassembly is used, but there is a valid - // symbol. Calculate the line of the current PC from the start of the - // current symbol. - lldb::SBInstructionList inst_list = dap.target.ReadInstructions( - frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr); - size_t inst_line = inst_list.GetSize(); - - // Line numbers are 1-based. - object.try_emplace("line", inst_line + 1); - object.try_emplace("column", 1); - } else { - // No valid line entry or symbol. - object.try_emplace("line", 1); - object.try_emplace("column", 1); - } - - if (source) - object.try_emplace("source", std::move(source).value()); - - const auto pc = frame.GetPC(); - if (pc != LLDB_INVALID_ADDRESS) { - std::string formatted_addr = "0x" + llvm::utohexstr(pc); - object.try_emplace("instructionPointerReference", formatted_addr); - } - - if (frame.IsArtificial() || frame.IsHidden()) - object.try_emplace("presentationHint", "subtle"); - - lldb::SBModule module = frame.GetModule(); - if (module.IsValid()) { - if (const llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty()) - object.try_emplace("moduleId", uuid.str()); - } - - return llvm::json::Value(std::move(object)); -} - -llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, - lldb::SBFormat &format) { - std::string name; - lldb::SBStream stream; - if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { - name = stream.GetData(); - } else { - const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); - const char *queue_name = thread.GetQueueName(); - if (queue_name != nullptr) { - name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name, - thread_idx); - } else { - name = llvm::formatv("Thread {0}", thread_idx); - } - } - - return llvm::json::Value(llvm::json::Object{{"id", thread.GetThreadID() + 1}, - {"name", name}, - {"presentationHint", "label"}}); -} - // "StoppedEvent": { // "allOf": [ { "$ref": "#/definitions/Event" }, { // "type": "object", diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 0a907599fc9ee..c5c538dd03def 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -234,56 +234,6 @@ void FillResponse(const llvm::json::Object &request, /// definition outlined by Microsoft. llvm::json::Object CreateEventObject(const llvm::StringRef event_name); -/// Create a "StackFrame" object for a LLDB frame object. -/// -/// This function will fill in the following keys in the returned -/// object: -/// "id" - the stack frame ID as an integer -/// "name" - the function name as a string -/// "source" - source file information as a "Source" DAP object -/// "line" - the source file line number as an integer -/// "column" - the source file column number as an integer -/// -/// \param[in] dap -/// The DAP session associated with the stopped thread. -/// -/// \param[in] frame -/// The LLDB stack frame to use when populating out the "StackFrame" -/// object. -/// -/// \param[in] format -/// The LLDB format to use when populating out the "StackFrame" -/// object. -/// -/// \return -/// A "StackFrame" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame, - lldb::SBFormat &format); - -/// Create a "StackFrame" label object for a LLDB thread. -/// -/// This function will fill in the following keys in the returned -/// object: -/// "id" - the thread ID as an integer -/// "name" - the thread name as a string which combines the LLDB -/// thread index ID along with the string name of the thread -/// from the OS if it has a name. -/// "presentationHint" - "label" -/// -/// \param[in] thread -/// The LLDB thread to use when populating out the "Thread" -/// object. -/// -/// \param[in] format -/// The configured formatter for the DAP session. -/// -/// \return -/// A "StackFrame" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, - lldb::SBFormat &format); - /// Create a "StoppedEvent" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index c87111d8f1b78..c3225f6ba0e35 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -802,4 +802,22 @@ bool fromJSON(const llvm::json::Value &Params, RestartArguments &Args, return false; } +bool fromJSON(const llvm::json::Value &Params, StackTraceArguments &Args, + llvm::json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.map("threadId", Args.threadId) && + O.mapOptional("startFrame", Args.startFrame) && + O.mapOptional("levels", Args.levels) && + O.mapOptional("format", Args.format); +} + +llvm::json::Value toJSON(const StackTraceResponseBody &Body) { + json::Object result{{"stackFrames", Body.stackFrames}}; + + if (Body.totalFrames) + result.insert({"totalFrames", Body.totalFrames}); + + return result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 33fcaae1710b5..9a99af9068ef1 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -1268,6 +1268,44 @@ bool fromJSON(const llvm::json::Value &, RestartArguments &, llvm::json::Path); /// field is required. using RestartResponse = VoidResponse; +/// Arguments for `stackTrace` request. +struct StackTraceArguments { + /// Retrieve the stacktrace for this thread. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; + + /// The index of the first frame to return; if omitted frames start at 0. + uint32_t startFrame = 0; + + /// The maximum number of frames to return. If levels is not specified or 0, + /// all frames are returned. + uint32_t levels = 0; + + /// Specifies details on how to format the returned `StackFrame.name`. The + /// debug adapter may format requested details in any way that would make + /// sense to a developer. The attribute is only honored by a debug adapter if + /// the corresponding capability `supportsValueFormattingOptions` is true. + std::optional<StackFrameFormat> format; +}; +bool fromJSON(const llvm::json::Value &, StackTraceArguments &, + llvm::json::Path); + +/// Response to `stackTrace` request. +struct StackTraceResponseBody { + /// The frames of the stack frame. If the array has length zero, there are no + /// stack frames available. + /// This means that there is no location information available. + std::vector<StackFrame> stackFrames; + + /// The total number of frames available in the stack. If omitted or if + /// `totalFrames` is larger than the available frames, a client is expected to + /// request frames until a request returns less frames than requested (which + /// indicates the end of the stack). Returning monotonically increasing + /// `totalFrames` values for subsequent requests can be used to enforce paging + /// in the client. + uint32_t totalFrames = 0; +}; +llvm::json::Value toJSON(const StackTraceResponseBody &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index c7f7c447b5b6f..a5c7fc283b796 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -1174,4 +1174,58 @@ llvm::json::Value toJSON(const CompileUnit &CU) { return result; } +bool fromJSON(const llvm::json::Value &Params, StackFrameFormat &SFF, + llvm::json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.mapOptional("parameters", SFF.parameters) && + O.mapOptional("parameterTypes", SFF.parameterTypes) && + O.mapOptional("parameterNames", SFF.parameterNames) && + O.mapOptional("parameterValues", SFF.parameterValues) && + O.mapOptional("line", SFF.line) && + O.mapOptional("module", SFF.module) && + O.mapOptional("includeAll", SFF.includeAll); +} + +llvm::json::Value toJSON(const StackFrame::PresentationHint &PH) { + switch (PH) { + case StackFrame::ePresentationHintNormal: + return "normal"; + case StackFrame::ePresentationHintLabel: + return "label"; + case StackFrame::ePresentationHintSubtle: + return "subtle"; + } + llvm_unreachable("unhandled stackFrame presentationHint."); +} + +llvm::json::Value toJSON(const StackFrame &SF) { + json::Object result{{"id", SF.id}, {"name", SF.name}}; + + if (SF.source) { + result.insert({"source", *SF.source}); + assert(SF.line != LLDB_INVALID_LINE_NUMBER); + result.insert({"line", SF.line}); + assert(SF.column != LLDB_INVALID_COLUMN_NUMBER); + result.insert({"column", SF.column}); + if (SF.endLine != 0 && SF.endLine != LLDB_INVALID_LINE_NUMBER) + result.insert({"endLine", SF.endLine}); + if (SF.endColumn != 0 && SF.endColumn != LLDB_INVALID_COLUMN_NUMBER) + result.insert({"endColumn", SF.endColumn}); + } else { + result.insert({"line", 0}); + result.insert({"column", 0}); + } + if (SF.canRestart) + result.insert({"canRestart", SF.canRestart}); + if (SF.instructionPointerReference != LLDB_INVALID_ADDRESS) + result.insert({"instructionPointerReference", + EncodeMemoryReference(SF.instructionPointerReference)}); + if (SF.moduleId) + result.insert({"moduleId", *SF.moduleId}); + if (SF.presentationHint) + result.insert({"presentationHint", *SF.presentationHint}); + + return result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 4ead4786bc661..cad09df145367 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -1044,6 +1044,92 @@ struct CompileUnit { }; llvm::json::Value toJSON(const CompileUnit &); +/// Provides formatting information for a stack frame. +struct StackFrameFormat { + /// Displays parameters for the stack frame. + bool parameters = false; + + /// Displays the types of parameters for the stack frame. + bool parameterTypes = false; + + /// Displays the names of parameters for the stack frame. + bool parameterNames = false; + + /// Displays the values of parameters for the stack frame. + bool parameterValues = false; + + /// Displays the line number of the stack frame. + bool line = false; + + /// Displays the module of the stack frame. + bool module = false; + + /// Includes all stack frames, including those the debug adapter might + /// otherwise hide. + bool includeAll = false; +}; +bool fromJSON(const llvm::json::Value &, StackFrameFormat &, llvm::json::Path); + +/// A Stackframe contains the source location. +struct StackFrame { + enum PresentationHint : unsigned { + ePresentationHintNormal, + ePresentationHintLabel, + ePresentationHintSubtle, + }; + + /// An identifier for the stack frame. It must be unique across all threads. + /// This id can be used to retrieve the scopes of the frame with the `scopes` + /// request or to restart the execution of a stack frame. + int64_t id; + + /// The name of the stack frame, typically a method name. + std::string name; + + /// The source of the frame. + std::optional<Source> source; + + /// The line within the source of the frame. If the source attribute is + /// missing or doesn't exist, `line` is 0 and should be ignored by the client. + uint32_t line = LLDB_INVALID_LINE_NUMBER; + + /// Start position of the range covered by the stack frame. It is measured in + /// UTF-16 code units and the client capability `columnsStartAt1` determines + /// whether it is 0- or 1-based. If attribute `source` is missing or doesn't + /// exist, `column` is 0 and should be ignored by the client. + uint32_t column = LLDB_INVALID_COLUMN_NUMBER; + + /// The end line of the range covered by the stack frame. + uint32_t endLine = LLDB_INVALID_LINE_NUMBER; + + /// End position of the range covered by the stack frame. It is measured in + /// UTF-16 code units and the client capability `columnsStartAt1` determines + /// whether it is 0- or 1-based. + uint32_t endColumn = LLDB_INVALID_COLUMN_NUMBER; + + /// Indicates whether this frame can be restarted with the `restartFrame` + /// request. Clients should only use this if the debug adapter supports the + /// `restart` request and the corresponding capability `supportsRestartFrame` + /// is true. If a debug adapter has this capability, then `canRestart` + /// defaults to `true` if the property is absent. + bool canRestart = false; + + /// A memory reference for the current instruction pointer in this frame. + lldb::addr_t instructionPointerReference = LLDB_INVALID_ADDRESS; + + /// The module associated with this frame, if any. + std::optional<std::string> moduleId; + + /// A hint for how to present this frame in the UI. A value of `label` can be + /// used to indicate that the frame is an artificial frame that is used as a + /// visual label or separator. A value of `subtle` can be used to change the + /// appearance of a frame in a 'subtle' way. Values: 'normal', 'label', + /// 'subtle' + std::optional<PresentationHint> presentationHint; +}; +llvm::json::Value toJSON(const StackFrame::PresentationHint &); +llvm::json::Value toJSON(const StackFrame &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/unittests/DAP/ProtocolRequestsTest.cpp b/lldb/unittests/DAP/ProtocolRequestsTest.cpp index c639e40453fb0..cdc012b448c8f 100644 --- a/lldb/unittests/DAP/ProtocolRequestsTest.cpp +++ b/lldb/unittests/DAP/ProtocolRequestsTest.cpp @@ -344,3 +344,71 @@ TEST(ProtocolRequestsTest, RestartArguments) { EXPECT_NE(attach_args, nullptr); EXPECT_EQ(attach_args->pid, 123U); } + +TEST(ProtocolRequestsTest, StackTraceArguments) { + llvm::Expected<StackTraceArguments> expected = parse<StackTraceArguments>(R"({ + "threadId": 42, + "startFrame": 1, + "levels": 10, + "format": { + "parameters": true, + "line": true + } + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->threadId, 42U); + EXPECT_EQ(expected->startFrame, 1U); + EXPECT_EQ(expected->levels, 10U); + EXPECT_EQ(expected->format->parameters, true); + EXPECT_EQ(expected->format->line, true); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<StackTraceArguments>(R"({})"), + FailedWithMessage("missing value at (root).threadId")); +} + +TEST(ProtocolRequestsTest, StackTraceResponseBody) { + StackFrame frame1; + frame1.id = 1; + frame1.name = "main"; + frame1.source = Source{}; + frame1.source->name = "main.cpp"; + frame1.source->sourceReference = 123; + frame1.line = 23; + frame1.column = 1; + StackFrame frame2; + frame2.id = 2; + frame2.name = "test"; + frame2.presentationHint = StackFrame::ePresentationHintLabel; + + StackTraceResponseBody body; + body.stackFrames = {frame1, frame2}; + body.totalFrames = 2; + + // Check required keys. + Expected<json::Value> expected = parse(R"({ + "stackFrames": [ + { + "id": 1, + "name": "main", + "source": { + "name": "main.cpp", + "sourceReference": 123 + }, + "line": 23, + "column": 1 + }, + { + "id": 2, + "name": "test", + "line": 0, + "column": 0, + "presentationHint": "label" + } + ], + "totalFrames": 2 + })"); + + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
