From 41621c3afbd513d9ea738cfa19f6f55c6ca33bea Mon Sep 17 00:00:00 2001 From: Jonathan Phippen Date: Wed, 23 Oct 2024 21:19:41 -0700 Subject: [PATCH] Debugger: Add Value Formatting support for StackTrace request Add support for the "format" property of the Debug Adapter Protocol StackTrace request to fulfill the host's request to format the resulting StackFrame name differently. --- Source/cmDebuggerAdapter.cxx | 3 +- Source/cmDebuggerStackFrame.cxx | 6 ++++ Source/cmDebuggerStackFrame.h | 3 ++ Source/cmDebuggerThread.cxx | 43 ++++++++++++++++++++++++-- Source/cmDebuggerThread.h | 8 ++++- Source/cmDebuggerThreadManager.cxx | 8 +++-- Source/cmDebuggerThreadManager.h | 3 +- Tests/CMakeLib/testDebuggerAdapter.cxx | 1 + Tests/CMakeLib/testDebuggerThread.cxx | 42 ++++++++++++++++++++++--- 9 files changed, 104 insertions(+), 13 deletions(-) diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx index 0eacd0c121..c3587a1055 100644 --- a/Source/cmDebuggerAdapter.cxx +++ b/Source/cmDebuggerAdapter.cxx @@ -148,6 +148,7 @@ cmDebuggerAdapter::cmDebuggerAdapter( SupportsVariableType = req.supportsVariableType.value(false); dap::CMakeInitializeResponse response; response.supportsConfigurationDoneRequest = true; + response.supportsValueFormattingOptions = true; response.cmakeVersion.major = CMake_VERSION_MAJOR; response.cmakeVersion.minor = CMake_VERSION_MINOR; response.cmakeVersion.patch = CMake_VERSION_PATCH; @@ -186,7 +187,7 @@ cmDebuggerAdapter::cmDebuggerAdapter( std::unique_lock lock(Mutex); cm::optional response = - ThreadManager->GetThreadStackTraceResponse(request.threadId); + ThreadManager->GetThreadStackTraceResponse(request); if (response.has_value()) { return response.value(); } diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx index 789b0a5939..89cdbc6a02 100644 --- a/Source/cmDebuggerStackFrame.cxx +++ b/Source/cmDebuggerStackFrame.cxx @@ -25,4 +25,10 @@ int64_t cmDebuggerStackFrame::GetLine() const noexcept return this->Function.Line(); } +std::vector const& cmDebuggerStackFrame::GetArguments() + const noexcept +{ + return this->Function.Arguments(); +} + } // namespace cmDebugger diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h index f4e6612e9c..2133bcdf40 100644 --- a/Source/cmDebuggerStackFrame.h +++ b/Source/cmDebuggerStackFrame.h @@ -7,8 +7,10 @@ #include #include #include +#include class cmListFileFunction; +struct cmListFileArgument; class cmMakefile; namespace cmDebugger { @@ -32,6 +34,7 @@ public: { return this->Function; } + std::vector const& GetArguments() const noexcept; }; } // namespace cmDebugger diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx index f7a17781d4..047dd2d203 100644 --- a/Source/cmDebuggerThread.cxx +++ b/Source/cmDebuggerThread.cxx @@ -14,6 +14,7 @@ #include "cmDebuggerVariablesHelper.h" #include "cmDebuggerVariablesManager.h" #include "cmListFileCache.h" +#include "cmStringAlgorithms.h" namespace cmDebugger { @@ -117,8 +118,27 @@ dap::VariablesResponse cmDebuggerThread::GetVariablesResponse( } dap::StackTraceResponse GetStackTraceResponse( - std::shared_ptr const& thread) + std::shared_ptr const& thread, + dap::optional format) { + dap::boolean showParameters = false; + dap::boolean showParameterValues = false; + dap::boolean showLine = false; + if (format.has_value()) { + auto formatValue = format.value(); + if (formatValue.parameters.has_value()) { + showParameters = formatValue.parameters.value(); + } + + if (formatValue.parameterValues.has_value()) { + showParameterValues = formatValue.parameterValues.value(); + } + + if (formatValue.line.has_value()) { + showLine = formatValue.line.value(); + } + } + dap::StackTraceResponse response; std::unique_lock lock(thread->Mutex); for (int i = static_cast(thread->Frames.size()) - 1; i >= 0; --i) { @@ -136,10 +156,29 @@ dap::StackTraceResponse GetStackTraceResponse( #endif stackFrame.line = thread->Frames[i]->GetLine(); stackFrame.column = 1; - stackFrame.name = thread->Frames[i]->GetFunction().OriginalName(); stackFrame.id = thread->Frames[i]->GetId(); stackFrame.source = source; + auto stackName = thread->Frames[i]->GetFunction().OriginalName(); + if (showParameters) { + stackName.push_back('('); + if (showParameterValues && !thread->Frames[i]->GetArguments().empty()) { + for (auto const& arg : thread->Frames[i]->GetArguments()) { + stackName = cmStrCat(stackName, arg.Value, ", "); + } + + stackName.erase(stackName.end() - 2, stackName.end()); + } + + stackName.push_back(')'); + } + + if (showLine) { + stackName = + cmStrCat(stackName, " Line: ", static_cast(stackFrame.line)); + } + + stackFrame.name = stackName; response.stackFrames.push_back(stackFrame); } diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h index 65ee2cf15b..81664b50c7 100644 --- a/Source/cmDebuggerThread.h +++ b/Source/cmDebuggerThread.h @@ -23,6 +23,11 @@ class cmDebuggerVariables; class cmDebuggerVariablesManager; } +namespace dap { +template +class optional; +} + namespace cmDebugger { class cmDebuggerThread @@ -53,7 +58,8 @@ public: dap::VariablesResponse GetVariablesResponse( dap::VariablesRequest const& request); friend dap::StackTraceResponse GetStackTraceResponse( - std::shared_ptr const& thread); + std::shared_ptr const& thread, + dap::optional format); }; } // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx index 0eb443bd8f..0f15b6b281 100644 --- a/Source/cmDebuggerThreadManager.cxx +++ b/Source/cmDebuggerThreadManager.cxx @@ -6,6 +6,7 @@ #include #include +#include #include "cmDebuggerThread.h" @@ -30,18 +31,19 @@ void cmDebuggerThreadManager::EndThread( } cm::optional -cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id) +cmDebuggerThreadManager::GetThreadStackTraceResponse( + const dap::StackTraceRequest& request) { auto it = find_if(Threads.begin(), Threads.end(), [&](const std::shared_ptr& t) { - return t->GetId() == id; + return t->GetId() == request.threadId; }); if (it == Threads.end()) { return {}; } - return GetStackTraceResponse(*it); + return GetStackTraceResponse(*it, request.format); } } // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h index 934cf8590e..6d27a5c339 100644 --- a/Source/cmDebuggerThreadManager.h +++ b/Source/cmDebuggerThreadManager.h @@ -17,6 +17,7 @@ class cmDebuggerThread; } namespace dap { +struct StackTraceRequest; struct StackTraceResponse; } @@ -32,7 +33,7 @@ public: std::shared_ptr StartThread(std::string const& name); void EndThread(std::shared_ptr const& thread); cm::optional GetThreadStackTraceResponse( - std::int64_t id); + const dap::StackTraceRequest& request); }; } // namespace cmDebugger diff --git a/Tests/CMakeLib/testDebuggerAdapter.cxx b/Tests/CMakeLib/testDebuggerAdapter.cxx index a055cb7c6e..b2c9458881 100644 --- a/Tests/CMakeLib/testDebuggerAdapter.cxx +++ b/Tests/CMakeLib/testDebuggerAdapter.cxx @@ -132,6 +132,7 @@ bool runTest(std::function onThreadExitedEvent) ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest); ASSERT_TRUE( initializeResponse.response.exceptionBreakpointFilters.has_value()); + ASSERT_TRUE(initializeResponse.response.supportsValueFormattingOptions); dap::LaunchRequest launchRequest; auto launchResponse = client->send(launchRequest).get(); diff --git a/Tests/CMakeLib/testDebuggerThread.cxx b/Tests/CMakeLib/testDebuggerThread.cxx index 3b7fe6e2fa..4eed247aef 100644 --- a/Tests/CMakeLib/testDebuggerThread.cxx +++ b/Tests/CMakeLib/testDebuggerThread.cxx @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,21 +11,52 @@ #include "testCommon.h" -static bool testStackFrameFunctionName() +static bool testStackFrameFunctionName( + dap::optional format, const char* expectedName) { auto thread = std::make_shared(0, "name"); const auto* functionName = "function_name"; - auto arguments = std::vector{}; + auto arguments = std::vector{ cmListFileArgument( + "arg", cmListFileArgument::Delimiter::Unquoted, 0) }; cmListFileFunction func(functionName, 10, 20, arguments); thread->PushStackFrame(nullptr, "CMakeLists.txt", func); - auto stackTrace = GetStackTraceResponse(thread); + auto stackTrace = GetStackTraceResponse(thread, format); - ASSERT_TRUE(stackTrace.stackFrames[0].name == functionName); + ASSERT_TRUE(stackTrace.stackFrames[0].name == expectedName); return true; } +bool testStackFrameNoFormatting() +{ + return testStackFrameFunctionName({}, "function_name"); +} + +bool testStackFrameFormatParameters() +{ + dap::StackFrameFormat format; + format.parameters = true; + return testStackFrameFunctionName(format, "function_name()"); +} + +bool testStackFrameFormatParameterValues() +{ + dap::StackFrameFormat format; + format.parameters = true; + format.parameterValues = true; + return testStackFrameFunctionName(format, "function_name(arg)"); +} + +bool testStackFrameFormatLine() +{ + dap::StackFrameFormat format; + format.line = true; + return testStackFrameFunctionName(format, "function_name Line: 10"); +} + int testDebuggerThread(int, char*[]) { - return runTests({ testStackFrameFunctionName }); + return runTests({ testStackFrameNoFormatting, testStackFrameFormatParameters, + testStackFrameFormatParameterValues, + testStackFrameFormatLine }); }