diff options
Diffstat (limited to 'Source')
34 files changed, 3516 insertions, 2 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 2354f3d..bcaf890 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -762,6 +762,38 @@ target_link_libraries( ZLIB::ZLIB ) +if(CMake_ENABLE_DEBUGGER) + target_sources( + CMakeLib + PRIVATE + cmDebuggerAdapter.cxx + cmDebuggerAdapter.h + cmDebuggerBreakpointManager.cxx + cmDebuggerBreakpointManager.h + cmDebuggerExceptionManager.cxx + cmDebuggerExceptionManager.h + cmDebuggerPipeConnection.cxx + cmDebuggerPipeConnection.h + cmDebuggerProtocol.cxx + cmDebuggerProtocol.h + cmDebuggerSourceBreakpoint.cxx + cmDebuggerSourceBreakpoint.h + cmDebuggerStackFrame.cxx + cmDebuggerStackFrame.h + cmDebuggerThread.cxx + cmDebuggerThread.h + cmDebuggerThreadManager.cxx + cmDebuggerThreadManager.h + cmDebuggerVariables.cxx + cmDebuggerVariables.h + cmDebuggerVariablesHelper.cxx + cmDebuggerVariablesHelper.h + cmDebuggerVariablesManager.cxx + cmDebuggerVariablesManager.h + ) + target_link_libraries(CMakeLib PUBLIC cppdap::cppdap) +endif() + # Check if we can build the Mach-O parser. if(CMake_USE_MACH_PARSER) target_sources( diff --git a/Source/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake index 7d1e7da..c891fe9 100644 --- a/Source/Modules/CMakeBuildUtilities.cmake +++ b/Source/Modules/CMakeBuildUtilities.cmake @@ -379,7 +379,7 @@ endif() #--------------------------------------------------------------------- # Build cppdap library. -if(CMake_ENABLE_CPPDAP) +if(CMake_ENABLE_DEBUGGER) if(CMAKE_USE_SYSTEM_CPPDAP) find_package(cppdap CONFIG) if(NOT cppdap_FOUND) diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in index 3f19a11..de74716 100644 --- a/Source/cmConfigure.cmake.h.in +++ b/Source/cmConfigure.cmake.h.in @@ -20,6 +20,7 @@ #cmakedefine HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE #cmakedefine HAVE_UNSETENV +#cmakedefine CMake_ENABLE_DEBUGGER #cmakedefine CMake_USE_MACH_PARSER #cmakedefine CMake_USE_XCOFF_PARSER #cmakedefine CMAKE_USE_WMAKE diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx new file mode 100644 index 0000000..d03f79d --- /dev/null +++ b/Source/cmDebuggerAdapter.cxx @@ -0,0 +1,462 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmDebuggerAdapter.h" + +#include <algorithm> +#include <climits> +#include <condition_variable> +#include <cstdint> +#include <functional> +#include <iostream> +#include <stdexcept> +#include <utility> + +#include <cm/memory> +#include <cm/optional> + +#include <cm3p/cppdap/io.h> // IWYU pragma: keep +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/session.h> + +#include "cmDebuggerBreakpointManager.h" +#include "cmDebuggerExceptionManager.h" +#include "cmDebuggerProtocol.h" +#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerThread.h" +#include "cmDebuggerThreadManager.h" +#include "cmListFileCache.h" +#include "cmMakefile.h" +#include "cmValue.h" +#include "cmVersionConfig.h" +#include <cmcppdap/include/dap/optional.h> +#include <cmcppdap/include/dap/types.h> + +namespace cmDebugger { + +// Event provides a basic wait and signal synchronization primitive. +class SyncEvent +{ +public: + // Wait() blocks until the event is fired. + void Wait() + { + std::unique_lock<std::mutex> lock(Mutex); + Cv.wait(lock, [&] { return Fired; }); + } + + // Fire() sets signals the event, and unblocks any calls to Wait(). + void Fire() + { + std::unique_lock<std::mutex> lock(Mutex); + Fired = true; + Cv.notify_all(); + } + +private: + std::mutex Mutex; + std::condition_variable Cv; + bool Fired = false; +}; + +class Semaphore +{ +public: + Semaphore(int count_ = 0) + : Count(count_) + { + } + + inline void Notify() + { + std::unique_lock<std::mutex> lock(Mutex); + Count++; + // notify the waiting thread + Cv.notify_one(); + } + + inline void Wait() + { + std::unique_lock<std::mutex> lock(Mutex); + while (Count == 0) { + // wait on the mutex until notify is called + Cv.wait(lock); + } + Count--; + } + +private: + std::mutex Mutex; + std::condition_variable Cv; + int Count; +}; + +cmDebuggerAdapter::cmDebuggerAdapter( + std::shared_ptr<cmDebuggerConnection> connection, + std::string const& dapLogPath) + : cmDebuggerAdapter(std::move(connection), + dapLogPath.empty() + ? cm::nullopt + : cm::optional<std::shared_ptr<dap::Writer>>( + dap::file(dapLogPath.c_str()))) +{ +} + +cmDebuggerAdapter::cmDebuggerAdapter( + std::shared_ptr<cmDebuggerConnection> connection, + cm::optional<std::shared_ptr<dap::Writer>> logger) + : Connection(std::move(connection)) + , SessionActive(true) + , DisconnectEvent(cm::make_unique<SyncEvent>()) + , ConfigurationDoneEvent(cm::make_unique<SyncEvent>()) + , ContinueSem(cm::make_unique<Semaphore>()) + , ThreadManager(cm::make_unique<cmDebuggerThreadManager>()) +{ + if (logger.has_value()) { + SessionLog = std::move(logger.value()); + } + ClearStepRequests(); + + Session = dap::Session::create(); + BreakpointManager = + cm::make_unique<cmDebuggerBreakpointManager>(Session.get()); + ExceptionManager = + cm::make_unique<cmDebuggerExceptionManager>(Session.get()); + + // Handle errors reported by the Session. These errors include protocol + // parsing errors and receiving messages with no handler. + Session->onError([this](const char* msg) { + if (SessionLog) { + dap::writef(SessionLog, "dap::Session error: %s\n", msg); + } + + std::cout << "[CMake Debugger] DAP session error: " << msg << std::endl; + + BreakpointManager->ClearAll(); + ExceptionManager->ClearAll(); + ClearStepRequests(); + ContinueSem->Notify(); + DisconnectEvent->Fire(); + SessionActive.store(false); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize + Session->registerHandler([this](const dap::CMakeInitializeRequest& req) { + SupportsVariableType = req.supportsVariableType.value(false); + dap::CMakeInitializeResponse response; + response.supportsConfigurationDoneRequest = true; + response.cmakeVersion.major = CMake_VERSION_MAJOR; + response.cmakeVersion.minor = CMake_VERSION_MINOR; + response.cmakeVersion.patch = CMake_VERSION_PATCH; + response.cmakeVersion.full = CMake_VERSION; + ExceptionManager->HandleInitializeRequest(response); + return response; + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized + Session->registerSentHandler( + [&](const dap::ResponseOrError<dap::CMakeInitializeResponse>&) { + Session->send(dap::InitializedEvent()); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads + Session->registerHandler([this](const dap::ThreadsRequest& req) { + (void)req; + std::unique_lock<std::mutex> lock(Mutex); + dap::ThreadsResponse response; + dap::Thread thread; + thread.id = DefaultThread->GetId(); + thread.name = DefaultThread->GetName(); + response.threads.push_back(thread); + return response; + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace + Session->registerHandler([this](const dap::StackTraceRequest& request) + -> dap::ResponseOrError<dap::StackTraceResponse> { + std::unique_lock<std::mutex> lock(Mutex); + + cm::optional<dap::StackTraceResponse> response = + ThreadManager->GetThreadStackTraceResponse(request.threadId); + if (response.has_value()) { + return response.value(); + } + + return dap::Error("Unknown threadId '%d'", int(request.threadId)); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes + Session->registerHandler([this](const dap::ScopesRequest& request) + -> dap::ResponseOrError<dap::ScopesResponse> { + std::unique_lock<std::mutex> lock(Mutex); + return DefaultThread->GetScopesResponse(request.frameId, + SupportsVariableType); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables + Session->registerHandler([this](const dap::VariablesRequest& request) + -> dap::ResponseOrError<dap::VariablesResponse> { + return DefaultThread->GetVariablesResponse(request); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause + Session->registerHandler([this](const dap::PauseRequest& req) { + (void)req; + PauseRequest.store(true); + return dap::PauseResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue + Session->registerHandler([this](const dap::ContinueRequest& req) { + (void)req; + ContinueSem->Notify(); + return dap::ContinueResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next + Session->registerHandler([this](const dap::NextRequest& req) { + (void)req; + NextStepFrom.store(DefaultThread->GetStackFrameSize()); + ContinueSem->Notify(); + return dap::NextResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn + Session->registerHandler([this](const dap::StepInRequest& req) { + (void)req; + // This would stop after stepped in, single line stepped or stepped out. + StepInRequest.store(true); + ContinueSem->Notify(); + return dap::StepInResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut + Session->registerHandler([this](const dap::StepOutRequest& req) { + (void)req; + StepOutDepth.store(DefaultThread->GetStackFrameSize() - 1); + ContinueSem->Notify(); + return dap::StepOutResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch + Session->registerHandler([](const dap::LaunchRequest& req) { + (void)req; + return dap::LaunchResponse(); + }); + + // Handler for disconnect requests + Session->registerHandler([this](const dap::DisconnectRequest& request) { + (void)request; + BreakpointManager->ClearAll(); + ExceptionManager->ClearAll(); + ClearStepRequests(); + ContinueSem->Notify(); + DisconnectEvent->Fire(); + SessionActive.store(false); + return dap::DisconnectResponse(); + }); + + Session->registerHandler([this](const dap::EvaluateRequest& request) { + dap::EvaluateResponse response; + if (request.frameId.has_value()) { + std::shared_ptr<cmDebuggerStackFrame> frame = + DefaultThread->GetStackFrame(request.frameId.value()); + + auto var = frame->GetMakefile()->GetDefinition(request.expression); + if (var) { + response.type = "string"; + response.result = var; + return response; + } + } + + return response; + }); + + // The ConfigurationDone request is made by the client once all configuration + // requests have been made. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone + Session->registerHandler([this](const dap::ConfigurationDoneRequest& req) { + (void)req; + ConfigurationDoneEvent->Fire(); + return dap::ConfigurationDoneResponse(); + }); + + std::string errorMessage; + if (!Connection->StartListening(errorMessage)) { + throw std::runtime_error(errorMessage); + } + + // Connect to the client. Write a well-known message to stdout so that + // clients know it is safe to attempt to connect. + std::cout << "Waiting for debugger client to connect..." << std::endl; + Connection->WaitForConnection(); + std::cout << "Debugger client connected." << std::endl; + + if (SessionLog) { + Session->connect(spy(Connection->GetReader(), SessionLog), + spy(Connection->GetWriter(), SessionLog)); + } else { + Session->connect(Connection->GetReader(), Connection->GetWriter()); + } + + // Start the processing thread. + SessionThread = std::thread([this] { + while (SessionActive.load()) { + if (auto payload = Session->getPayload()) { + payload(); + } + } + }); + + ConfigurationDoneEvent->Wait(); + + DefaultThread = ThreadManager->StartThread("CMake script"); + dap::ThreadEvent threadEvent; + threadEvent.reason = "started"; + threadEvent.threadId = DefaultThread->GetId(); + Session->send(threadEvent); +} + +cmDebuggerAdapter::~cmDebuggerAdapter() +{ + if (SessionThread.joinable()) { + SessionThread.join(); + } + + Session.reset(nullptr); + + if (SessionLog) { + SessionLog->close(); + } +} + +void cmDebuggerAdapter::ReportExitCode(int exitCode) +{ + ThreadManager->EndThread(DefaultThread); + dap::ThreadEvent threadEvent; + threadEvent.reason = "exited"; + threadEvent.threadId = DefaultThread->GetId(); + DefaultThread.reset(); + + dap::ExitedEvent exitEvent; + exitEvent.exitCode = exitCode; + + dap::TerminatedEvent terminatedEvent; + + if (SessionActive.load()) { + Session->send(threadEvent); + Session->send(exitEvent); + Session->send(terminatedEvent); + } + + // Wait until disconnected or error. + DisconnectEvent->Wait(); +} + +void cmDebuggerAdapter::OnFileParsedSuccessfully( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions) +{ + BreakpointManager->SourceFileLoaded(sourcePath, functions); +} + +void cmDebuggerAdapter::OnBeginFunctionCall(cmMakefile* mf, + std::string const& sourcePath, + cmListFileFunction const& lff) +{ + std::unique_lock<std::mutex> lock(Mutex); + DefaultThread->PushStackFrame(mf, sourcePath, lff); + + if (lff.Line() == 0) { + // File just loaded, continue to first valid function call. + return; + } + + auto hits = BreakpointManager->GetBreakpoints(sourcePath, lff.Line()); + lock.unlock(); + + bool waitSem = false; + dap::StoppedEvent stoppedEvent; + stoppedEvent.allThreadsStopped = true; + stoppedEvent.threadId = DefaultThread->GetId(); + if (!hits.empty()) { + ClearStepRequests(); + waitSem = true; + + dap::array<dap::integer> hitBreakpoints; + hitBreakpoints.resize(hits.size()); + std::transform(hits.begin(), hits.end(), hitBreakpoints.begin(), + [&](const int64_t& id) { return dap::integer(id); }); + stoppedEvent.reason = "breakpoint"; + stoppedEvent.hitBreakpointIds = hitBreakpoints; + } + + if (long(DefaultThread->GetStackFrameSize()) <= NextStepFrom.load() || + StepInRequest.load() || + long(DefaultThread->GetStackFrameSize()) <= StepOutDepth.load()) { + ClearStepRequests(); + waitSem = true; + + stoppedEvent.reason = "step"; + } + + if (PauseRequest.load()) { + ClearStepRequests(); + waitSem = true; + + stoppedEvent.reason = "pause"; + } + + if (waitSem) { + Session->send(stoppedEvent); + ContinueSem->Wait(); + } +} + +void cmDebuggerAdapter::OnEndFunctionCall() +{ + DefaultThread->PopStackFrame(); +} + +static std::shared_ptr<cmListFileFunction> listFileFunction; + +void cmDebuggerAdapter::OnBeginFileParse(cmMakefile* mf, + std::string const& sourcePath) +{ + std::unique_lock<std::mutex> lock(Mutex); + + listFileFunction = std::make_shared<cmListFileFunction>( + sourcePath, 0, 0, std::vector<cmListFileArgument>()); + DefaultThread->PushStackFrame(mf, sourcePath, *listFileFunction); +} + +void cmDebuggerAdapter::OnEndFileParse() +{ + DefaultThread->PopStackFrame(); + listFileFunction = nullptr; +} + +void cmDebuggerAdapter::OnMessageOutput(MessageType t, std::string const& text) +{ + cm::optional<dap::StoppedEvent> stoppedEvent = + ExceptionManager->RaiseExceptionIfAny(t, text); + if (stoppedEvent.has_value()) { + stoppedEvent->threadId = DefaultThread->GetId(); + Session->send(*stoppedEvent); + ContinueSem->Wait(); + } +} + +void cmDebuggerAdapter::ClearStepRequests() +{ + NextStepFrom.store(INT_MIN); + StepInRequest.store(false); + StepOutDepth.store(INT_MIN); + PauseRequest.store(false); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerAdapter.h b/Source/cmDebuggerAdapter.h new file mode 100644 index 0000000..f261d88 --- /dev/null +++ b/Source/cmDebuggerAdapter.h @@ -0,0 +1,93 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <vector> + +#include <cm/optional> + +#include <cm3p/cppdap/io.h> // IWYU pragma: keep + +#include "cmMessageType.h" + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { +class Semaphore; +class SyncEvent; +class cmDebuggerBreakpointManager; +class cmDebuggerExceptionManager; +class cmDebuggerThread; +class cmDebuggerThreadManager; +} + +namespace dap { +class Session; +} + +namespace cmDebugger { + +class cmDebuggerConnection +{ +public: + virtual ~cmDebuggerConnection() = default; + virtual bool StartListening(std::string& errorMessage) = 0; + virtual void WaitForConnection() = 0; + virtual std::shared_ptr<dap::Reader> GetReader() = 0; + virtual std::shared_ptr<dap::Writer> GetWriter() = 0; +}; + +class cmDebuggerAdapter +{ +public: + cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection, + std::string const& dapLogPath); + cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection, + cm::optional<std::shared_ptr<dap::Writer>> logger); + ~cmDebuggerAdapter(); + + void ReportExitCode(int exitCode); + + void OnFileParsedSuccessfully( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions); + void OnBeginFunctionCall(cmMakefile* mf, std::string const& sourcePath, + cmListFileFunction const& lff); + void OnEndFunctionCall(); + void OnBeginFileParse(cmMakefile* mf, std::string const& sourcePath); + void OnEndFileParse(); + + void OnMessageOutput(MessageType t, std::string const& text); + +private: + void ClearStepRequests(); + std::shared_ptr<cmDebuggerConnection> Connection; + std::unique_ptr<dap::Session> Session; + std::shared_ptr<dap::Writer> SessionLog; + std::thread SessionThread; + std::atomic<bool> SessionActive; + std::mutex Mutex; + std::unique_ptr<SyncEvent> DisconnectEvent; + std::unique_ptr<SyncEvent> ConfigurationDoneEvent; + std::unique_ptr<Semaphore> ContinueSem; + std::atomic<int64_t> NextStepFrom; + std::atomic<bool> StepInRequest; + std::atomic<int64_t> StepOutDepth; + std::atomic<bool> PauseRequest; + std::unique_ptr<cmDebuggerThreadManager> ThreadManager; + std::shared_ptr<cmDebuggerThread> DefaultThread; + std::unique_ptr<cmDebuggerBreakpointManager> BreakpointManager; + std::unique_ptr<cmDebuggerExceptionManager> ExceptionManager; + bool SupportsVariableType; +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerBreakpointManager.cxx b/Source/cmDebuggerBreakpointManager.cxx new file mode 100644 index 0000000..152f0f5 --- /dev/null +++ b/Source/cmDebuggerBreakpointManager.cxx @@ -0,0 +1,200 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerBreakpointManager.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <memory> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/session.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerSourceBreakpoint.h" +#include "cmListFileCache.h" +#include "cmSystemTools.h" + +namespace cmDebugger { + +cmDebuggerBreakpointManager::cmDebuggerBreakpointManager( + dap::Session* dapSession) + : DapSession(dapSession) +{ + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints + DapSession->registerHandler([&](const dap::SetBreakpointsRequest& request) { + return HandleSetBreakpointsRequest(request); + }); +} + +int64_t cmDebuggerBreakpointManager::FindFunctionStartLine( + std::string const& sourcePath, int64_t line) +{ + auto location = + find_if(ListFileFunctionLines[sourcePath].begin(), + ListFileFunctionLines[sourcePath].end(), + [=](cmDebuggerFunctionLocation const& loc) { + return loc.StartLine <= line && loc.EndLine >= line; + }); + + if (location != ListFileFunctionLines[sourcePath].end()) { + return location->StartLine; + } + + return 0; +} + +int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine( + std::string const& sourcePath, int64_t line) +{ + auto location = find_if(ListFileFunctionLines[sourcePath].begin(), + ListFileFunctionLines[sourcePath].end(), + [=](cmDebuggerFunctionLocation const& loc) { + return loc.StartLine >= line; + }); + + if (location != ListFileFunctionLines[sourcePath].end()) { + return location->StartLine; + } + + if (!ListFileFunctionLines[sourcePath].empty() && + ListFileFunctionLines[sourcePath].back().EndLine <= line) { + // return last function start line for any breakpoints after. + return ListFileFunctionLines[sourcePath].back().StartLine; + } + + return 0; +} + +dap::SetBreakpointsResponse +cmDebuggerBreakpointManager::HandleSetBreakpointsRequest( + dap::SetBreakpointsRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + + dap::SetBreakpointsResponse response; + + auto sourcePath = + cmSystemTools::GetActualCaseForPath(request.source.path.value()); + const dap::array<dap::SourceBreakpoint> defaultValue{}; + const auto& breakpoints = request.breakpoints.value(defaultValue); + if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) { + // The file has loaded, we can validate breakpoints. + if (Breakpoints.find(sourcePath) != Breakpoints.end()) { + Breakpoints[sourcePath].clear(); + } + response.breakpoints.resize(breakpoints.size()); + for (size_t i = 0; i < breakpoints.size(); i++) { + int64_t correctedLine = + CalibrateBreakpointLine(sourcePath, breakpoints[i].line); + if (correctedLine > 0) { + Breakpoints[sourcePath].emplace_back(NextBreakpointId++, + correctedLine); + response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); + response.breakpoints[i].line = + Breakpoints[sourcePath].back().GetLine(); + response.breakpoints[i].verified = true; + } else { + response.breakpoints[i].verified = false; + response.breakpoints[i].line = breakpoints[i].line; + } + dap::Source dapSrc; + dapSrc.path = sourcePath; + response.breakpoints[i].source = dapSrc; + } + } else { + // The file has not loaded, validate breakpoints later. + ListFilePendingValidations.emplace(sourcePath); + + response.breakpoints.resize(breakpoints.size()); + for (size_t i = 0; i < breakpoints.size(); i++) { + Breakpoints[sourcePath].emplace_back(NextBreakpointId++, + breakpoints[i].line); + response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); + response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine(); + response.breakpoints[i].verified = false; + dap::Source dapSrc; + dapSrc.path = sourcePath; + response.breakpoints[i].source = dapSrc; + } + } + + return response; +} + +void cmDebuggerBreakpointManager::SourceFileLoaded( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions) +{ + std::unique_lock<std::mutex> lock(Mutex); + if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) { + // this is not expected. + return; + } + + for (cmListFileFunction const& func : functions) { + ListFileFunctionLines[sourcePath].emplace_back( + cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() }); + } + + if (ListFilePendingValidations.find(sourcePath) == + ListFilePendingValidations.end()) { + return; + } + + ListFilePendingValidations.erase(sourcePath); + + for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) { + dap::BreakpointEvent breakpointEvent; + breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId(); + breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine(); + auto source = dap::Source(); + source.path = sourcePath; + breakpointEvent.breakpoint.source = source; + int64_t correctedLine = CalibrateBreakpointLine( + sourcePath, Breakpoints[sourcePath][i].GetLine()); + if (correctedLine != Breakpoints[sourcePath][i].GetLine()) { + Breakpoints[sourcePath][i].ChangeLine(correctedLine); + } + breakpointEvent.reason = "changed"; + breakpointEvent.breakpoint.verified = (correctedLine > 0); + if (breakpointEvent.breakpoint.verified) { + breakpointEvent.breakpoint.line = correctedLine; + } else { + Breakpoints[sourcePath][i].Invalid(); + } + + DapSession->send(breakpointEvent); + } +} + +std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints( + std::string const& sourcePath, int64_t line) +{ + std::unique_lock<std::mutex> lock(Mutex); + const auto& all = Breakpoints[sourcePath]; + std::vector<int64_t> breakpoints; + if (all.empty()) { + return breakpoints; + } + + auto it = all.begin(); + + while ((it = std::find_if( + it, all.end(), [&](const cmDebuggerSourceBreakpoint& breakpoint) { + return (breakpoint.GetIsValid() && breakpoint.GetLine() == line); + })) != all.end()) { + breakpoints.emplace_back(it->GetId()); + ++it; + } + + return breakpoints; +} + +void cmDebuggerBreakpointManager::ClearAll() +{ + std::unique_lock<std::mutex> lock(Mutex); + Breakpoints.clear(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerBreakpointManager.h b/Source/cmDebuggerBreakpointManager.h new file mode 100644 index 0000000..a4e5df5 --- /dev/null +++ b/Source/cmDebuggerBreakpointManager.h @@ -0,0 +1,61 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> +#include <mutex> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +class cmListFileFunction; + +namespace cmDebugger { +class cmDebuggerSourceBreakpoint; +} + +namespace dap { +class Session; +} + +namespace cmDebugger { + +struct cmDebuggerFunctionLocation +{ + int64_t StartLine; + int64_t EndLine; +}; + +/** The breakpoint manager. */ +class cmDebuggerBreakpointManager +{ + dap::Session* DapSession; + std::mutex Mutex; + std::unordered_map<std::string, std::vector<cmDebuggerSourceBreakpoint>> + Breakpoints; + std::unordered_map<std::string, + std::vector<struct cmDebuggerFunctionLocation>> + ListFileFunctionLines; + std::unordered_set<std::string> ListFilePendingValidations; + int64_t NextBreakpointId = 0; + + dap::SetBreakpointsResponse HandleSetBreakpointsRequest( + dap::SetBreakpointsRequest const& request); + int64_t FindFunctionStartLine(std::string const& sourcePath, int64_t line); + int64_t CalibrateBreakpointLine(std::string const& sourcePath, int64_t line); + +public: + cmDebuggerBreakpointManager(dap::Session* dapSession); + void SourceFileLoaded(std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions); + std::vector<int64_t> GetBreakpoints(std::string const& sourcePath, + int64_t line); + void ClearAll(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerExceptionManager.cxx b/Source/cmDebuggerExceptionManager.cxx new file mode 100644 index 0000000..a27426c --- /dev/null +++ b/Source/cmDebuggerExceptionManager.cxx @@ -0,0 +1,129 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerExceptionManager.h" + +#include <utility> +#include <vector> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/session.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerProtocol.h" +#include "cmMessageType.h" + +namespace cmDebugger { + +cmDebuggerExceptionManager::cmDebuggerExceptionManager( + dap::Session* dapSession) + : DapSession(dapSession) +{ + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints + DapSession->registerHandler( + [&](const dap::SetExceptionBreakpointsRequest& request) { + return HandleSetExceptionBreakpointsRequest(request); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ExceptionInfo + DapSession->registerHandler([&](const dap::ExceptionInfoRequest& request) { + (void)request; + return HandleExceptionInfoRequest(); + }); + + ExceptionMap[MessageType::AUTHOR_WARNING] = + cmDebuggerExceptionFilter{ "AUTHOR_WARNING", "Warning (dev)" }; + ExceptionMap[MessageType::AUTHOR_ERROR] = + cmDebuggerExceptionFilter{ "AUTHOR_ERROR", "Error (dev)" }; + ExceptionMap[MessageType::FATAL_ERROR] = + cmDebuggerExceptionFilter{ "FATAL_ERROR", "Fatal error" }; + ExceptionMap[MessageType::INTERNAL_ERROR] = + cmDebuggerExceptionFilter{ "INTERNAL_ERROR", "Internal error" }; + ExceptionMap[MessageType::MESSAGE] = + cmDebuggerExceptionFilter{ "MESSAGE", "Other messages" }; + ExceptionMap[MessageType::WARNING] = + cmDebuggerExceptionFilter{ "WARNING", "Warning" }; + ExceptionMap[MessageType::LOG] = + cmDebuggerExceptionFilter{ "LOG", "Debug log" }; + ExceptionMap[MessageType::DEPRECATION_ERROR] = + cmDebuggerExceptionFilter{ "DEPRECATION_ERROR", "Deprecation error" }; + ExceptionMap[MessageType::DEPRECATION_WARNING] = + cmDebuggerExceptionFilter{ "DEPRECATION_WARNING", "Deprecation warning" }; + RaiseExceptions["AUTHOR_ERROR"] = true; + RaiseExceptions["FATAL_ERROR"] = true; + RaiseExceptions["INTERNAL_ERROR"] = true; + RaiseExceptions["DEPRECATION_ERROR"] = true; +} + +dap::SetExceptionBreakpointsResponse +cmDebuggerExceptionManager::HandleSetExceptionBreakpointsRequest( + dap::SetExceptionBreakpointsRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + dap::SetExceptionBreakpointsResponse response; + RaiseExceptions.clear(); + for (const auto& filter : request.filters) { + RaiseExceptions[filter] = true; + } + + return response; +} + +dap::ExceptionInfoResponse +cmDebuggerExceptionManager::HandleExceptionInfoRequest() +{ + std::unique_lock<std::mutex> lock(Mutex); + + dap::ExceptionInfoResponse response; + if (TheException.has_value()) { + response.exceptionId = TheException->Id; + response.breakMode = "always"; + response.description = TheException->Description; + TheException = {}; + } + return response; +} + +void cmDebuggerExceptionManager::HandleInitializeRequest( + dap::CMakeInitializeResponse& response) +{ + std::unique_lock<std::mutex> lock(Mutex); + response.supportsExceptionInfoRequest = true; + + dap::array<dap::ExceptionBreakpointsFilter> exceptionBreakpointFilters; + for (auto& pair : ExceptionMap) { + dap::ExceptionBreakpointsFilter filter; + filter.filter = pair.second.Filter; + filter.label = pair.second.Label; + filter.def = RaiseExceptions[filter.filter]; + exceptionBreakpointFilters.emplace_back(filter); + } + + response.exceptionBreakpointFilters = exceptionBreakpointFilters; +} + +cm::optional<dap::StoppedEvent> +cmDebuggerExceptionManager::RaiseExceptionIfAny(MessageType t, + std::string const& text) +{ + cm::optional<dap::StoppedEvent> maybeStoppedEvent; + std::unique_lock<std::mutex> lock(Mutex); + if (RaiseExceptions[ExceptionMap[t].Filter]) { + dap::StoppedEvent stoppedEvent; + stoppedEvent.allThreadsStopped = true; + stoppedEvent.reason = "exception"; + stoppedEvent.description = "Pause on exception"; + stoppedEvent.text = text; + TheException = cmDebuggerException{ ExceptionMap[t].Filter, text }; + maybeStoppedEvent = std::move(stoppedEvent); + } + + return maybeStoppedEvent; +} + +void cmDebuggerExceptionManager::ClearAll() +{ + std::unique_lock<std::mutex> lock(Mutex); + RaiseExceptions.clear(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerExceptionManager.h b/Source/cmDebuggerExceptionManager.h new file mode 100644 index 0000000..b819128 --- /dev/null +++ b/Source/cmDebuggerExceptionManager.h @@ -0,0 +1,70 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstddef> +#include <functional> +#include <mutex> +#include <string> +#include <unordered_map> + +#include <cm/optional> + +#include <cm3p/cppdap/protocol.h> + +#include "cmMessageType.h" + +namespace dap { +class Session; +struct CMakeInitializeResponse; +} + +namespace cmDebugger { + +struct cmDebuggerException +{ + std::string Id; + std::string Description; +}; + +struct cmDebuggerExceptionFilter +{ + std::string Filter; + std::string Label; +}; + +/** The exception manager. */ +class cmDebuggerExceptionManager +{ + // Some older C++ standard libraries cannot hash an enum class by default. + struct MessageTypeHash + { + std::size_t operator()(MessageType t) const + { + return std::hash<int>{}(static_cast<int>(t)); + } + }; + + dap::Session* DapSession; + std::mutex Mutex; + std::unordered_map<std::string, bool> RaiseExceptions; + std::unordered_map<MessageType, cmDebuggerExceptionFilter, MessageTypeHash> + ExceptionMap; + cm::optional<cmDebuggerException> TheException; + + dap::SetExceptionBreakpointsResponse HandleSetExceptionBreakpointsRequest( + dap::SetExceptionBreakpointsRequest const& request); + + dap::ExceptionInfoResponse HandleExceptionInfoRequest(); + +public: + cmDebuggerExceptionManager(dap::Session* dapSession); + void HandleInitializeRequest(dap::CMakeInitializeResponse& response); + cm::optional<dap::StoppedEvent> RaiseExceptionIfAny(MessageType t, + std::string const& text); + void ClearAll(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerPipeConnection.cxx b/Source/cmDebuggerPipeConnection.cxx new file mode 100644 index 0000000..1b54346 --- /dev/null +++ b/Source/cmDebuggerPipeConnection.cxx @@ -0,0 +1,293 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerPipeConnection.h" + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <stdexcept> +#include <utility> + +namespace cmDebugger { + +struct write_req_t +{ + uv_write_t req; + uv_buf_t buf; +}; + +cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name) + : PipeName(std::move(name)) +{ + Loop.init(); + LoopExit.init( + *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); }, + Loop); + WriteEvent.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data); + conn->WriteInternal(); + }, + this); + PipeClose.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data); + if (conn->Pipe.get()) { + conn->Pipe->data = nullptr; + conn->Pipe.reset(); + } + }, + this); +} + +void cmDebuggerPipeBase::WaitForConnection() +{ + std::unique_lock<std::mutex> lock(Mutex); + Connected.wait(lock, [this] { return isOpen() || FailedToOpen; }); + if (FailedToOpen) { + throw std::runtime_error("Failed to open debugger connection."); + } +} + +void cmDebuggerPipeBase::close() +{ + std::unique_lock<std::mutex> lock(Mutex); + + CloseConnection(); + PipeClose.send(); + lock.unlock(); + ReadReady.notify_all(); +} + +size_t cmDebuggerPipeBase::read(void* buffer, size_t n) +{ + std::unique_lock<std::mutex> lock(Mutex); + ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); }); + + if (!isOpen() && ReadBuffer.empty()) { + return 0; + } + + auto size = std::min(n, ReadBuffer.size()); + memcpy(buffer, ReadBuffer.data(), size); + ReadBuffer.erase(0, size); + return size; +} + +bool cmDebuggerPipeBase::write(const void* buffer, size_t n) +{ + std::unique_lock<std::mutex> lock(Mutex); + WriteBuffer.append(static_cast<const char*>(buffer), n); + lock.unlock(); + WriteEvent.send(); + + lock.lock(); + WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); }); + return true; +} + +void cmDebuggerPipeBase::StopLoop() +{ + LoopExit.send(); + + if (LoopThread.joinable()) { + LoopThread.join(); + } +} + +void cmDebuggerPipeBase::BufferData(const std::string& data) +{ + std::unique_lock<std::mutex> lock(Mutex); + ReadBuffer += data; + lock.unlock(); + ReadReady.notify_all(); +} + +void cmDebuggerPipeBase::WriteInternal() +{ + std::unique_lock<std::mutex> lock(Mutex); + auto n = WriteBuffer.length(); + assert(this->Pipe.get()); + write_req_t* req = new write_req_t; + req->req.data = &WriteComplete; + char* rawBuffer = new char[n]; + req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n)); + memcpy(req->buf.base, WriteBuffer.data(), n); + WriteBuffer.clear(); + lock.unlock(); + + uv_write( + reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1, + [](uv_write_t* cb_req, int status) { + (void)status; // We need to free memory even if the write failed. + write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req); + reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all(); + delete[] (wr->buf.base); + delete wr; + }); + +#ifdef __clang_analyzer__ + // Tell clang-analyzer that 'rawBuffer' does not leak. + // We pass ownership to the closure. + delete[] rawBuffer; +#endif +} + +cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name) + : cmDebuggerPipeBase(std::move(name)) +{ + ServerPipeClose.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data); + if (conn->ServerPipe.get()) { + conn->ServerPipe->data = nullptr; + conn->ServerPipe.reset(); + } + }, + this); +} + +cmDebuggerPipeConnection::~cmDebuggerPipeConnection() +{ + StopLoop(); +} + +bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage) +{ + this->ServerPipe.init(*Loop, 0, + static_cast<cmDebuggerPipeConnection*>(this)); + + int r; + if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) { + errorMessage = + "Internal Error with " + this->PipeName + ": " + uv_err_name(r); + return false; + } + + r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) { + if (status >= 0) { + auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data); + if (conn) { + conn->Connect(stream); + } + } + }); + + if (r != 0) { + errorMessage = + "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r); + return false; + } + + // Start the libuv event loop thread so that a client can connect. + LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); }); + + StartedListening.set_value(); + + return true; +} + +std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader() +{ + return std::static_pointer_cast<dap::Reader>(shared_from_this()); +} + +std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter() +{ + return std::static_pointer_cast<dap::Writer>(shared_from_this()); +} + +bool cmDebuggerPipeConnection::isOpen() +{ + return this->Pipe.get() != nullptr; +} + +void cmDebuggerPipeConnection::CloseConnection() +{ + ServerPipeClose.send(); +} + +void cmDebuggerPipeConnection::Connect(uv_stream_t* server) +{ + if (this->Pipe.get()) { + // Accept and close all pipes but the first: + cm::uv_pipe_ptr rejectPipe; + + rejectPipe.init(*Loop, 0); + uv_accept(server, rejectPipe); + + return; + } + + cm::uv_pipe_ptr ClientPipe; + ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this)); + + if (uv_accept(server, ClientPipe) != 0) { + return; + } + + StartReading<cmDebuggerPipeConnection>(ClientPipe); + + std::unique_lock<std::mutex> lock(Mutex); + Pipe = std::move(ClientPipe); + lock.unlock(); + Connected.notify_all(); +} + +cmDebuggerPipeClient::~cmDebuggerPipeClient() +{ + StopLoop(); +} + +void cmDebuggerPipeClient::Start() +{ + this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this)); + + uv_connect_t* connect = new uv_connect_t; + connect->data = this; + uv_pipe_connect( + connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) { + auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data); + if (status >= 0) { + conn->Connect(); + } else { + conn->FailConnection(); + } + delete cb_connect; + }); + + // Start the libuv event loop so that the pipe can connect. + LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); }); +} + +bool cmDebuggerPipeClient::isOpen() +{ + return IsConnected; +} + +void cmDebuggerPipeClient::CloseConnection() +{ + IsConnected = false; +} + +void cmDebuggerPipeClient::Connect() +{ + StartReading<cmDebuggerPipeClient>(Pipe); + std::unique_lock<std::mutex> lock(Mutex); + IsConnected = true; + lock.unlock(); + Connected.notify_all(); +} + +void cmDebuggerPipeClient::FailConnection() +{ + std::unique_lock<std::mutex> lock(Mutex); + FailedToOpen = true; + lock.unlock(); + Connected.notify_all(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerPipeConnection.h b/Source/cmDebuggerPipeConnection.h new file mode 100644 index 0000000..0991ff7 --- /dev/null +++ b/Source/cmDebuggerPipeConnection.h @@ -0,0 +1,139 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <condition_variable> +#include <cstddef> +#include <future> +#include <memory> +#include <mutex> +#include <string> +#include <thread> + +#include <cm3p/cppdap/io.h> +#include <cm3p/uv.h> + +#include "cmDebuggerAdapter.h" +#include "cmUVHandlePtr.h" + +namespace cmDebugger { + +class cmDebuggerPipeBase : public dap::ReaderWriter +{ +public: + cmDebuggerPipeBase(std::string name); + + void WaitForConnection(); + + // dap::ReaderWriter implementation + + void close() final; + size_t read(void* buffer, size_t n) final; + bool write(const void* buffer, size_t n) final; + +protected: + virtual void CloseConnection(){}; + template <typename T> + void StartReading(uv_stream_t* stream) + { + uv_read_start( + stream, + // alloc_cb + [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + (void)handle; + char* rawBuffer = new char[suggested_size]; + *buf = + uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size)); + }, + // read_cb + [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) { + auto conn = static_cast<T*>(readStream->data); + if (conn) { + if (nread >= 0) { + conn->BufferData(std::string(buf->base, buf->base + nread)); + } else { + conn->close(); + } + } + delete[] (buf->base); + }); + } + void StopLoop(); + + const std::string PipeName; + std::thread LoopThread; + cm::uv_loop_ptr Loop; + cm::uv_pipe_ptr Pipe; + std::mutex Mutex; + std::condition_variable Connected; + bool FailedToOpen = false; + +private: + void BufferData(const std::string& data); + void WriteInternal(); + + cm::uv_async_ptr LoopExit; + cm::uv_async_ptr WriteEvent; + cm::uv_async_ptr PipeClose; + std::string WriteBuffer; + std::string ReadBuffer; + std::condition_variable ReadReady; + std::condition_variable WriteComplete; +}; + +class cmDebuggerPipeConnection + : public cmDebuggerPipeBase + , public cmDebuggerConnection + , public std::enable_shared_from_this<cmDebuggerPipeConnection> +{ +public: + cmDebuggerPipeConnection(std::string name); + ~cmDebuggerPipeConnection() override; + + void WaitForConnection() override + { + cmDebuggerPipeBase::WaitForConnection(); + } + + bool StartListening(std::string& errorMessage) override; + std::shared_ptr<dap::Reader> GetReader() override; + std::shared_ptr<dap::Writer> GetWriter() override; + + // dap::ReaderWriter implementation + + bool isOpen() override; + + // Used for unit test synchronization + std::promise<void> StartedListening; + +private: + void CloseConnection() override; + void Connect(uv_stream_t* server); + + cm::uv_pipe_ptr ServerPipe; + cm::uv_async_ptr ServerPipeClose; +}; + +class cmDebuggerPipeClient : public cmDebuggerPipeBase +{ +public: + using cmDebuggerPipeBase::cmDebuggerPipeBase; + ~cmDebuggerPipeClient() override; + + void Start(); + + // dap::ReaderWriter implementation + + bool isOpen() override; + +private: + void CloseConnection() override; + void Connect(); + void FailConnection(); + + bool IsConnected = false; +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerProtocol.cxx b/Source/cmDebuggerProtocol.cxx new file mode 100644 index 0000000..505de35 --- /dev/null +++ b/Source/cmDebuggerProtocol.cxx @@ -0,0 +1,80 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerProtocol.h" + +#include <string> + +namespace dap { +DAP_IMPLEMENT_STRUCT_TYPEINFO(CMakeVersion, "", DAP_FIELD(major, "major"), + DAP_FIELD(minor, "minor"), + DAP_FIELD(patch, "patch"), + DAP_FIELD(full, "full")); + +DAP_IMPLEMENT_STRUCT_TYPEINFO( + CMakeInitializeResponse, "", + DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), + DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), + DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"), + DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"), + DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"), + DAP_FIELD(supportsBreakpointLocationsRequest, + "supportsBreakpointLocationsRequest"), + DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"), + DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"), + DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"), + DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"), + DAP_FIELD(supportsConfigurationDoneRequest, + "supportsConfigurationDoneRequest"), + DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"), + DAP_FIELD(supportsDelayedStackTraceLoading, + "supportsDelayedStackTraceLoading"), + DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"), + DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"), + DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"), + DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"), + DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"), + DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"), + DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"), + DAP_FIELD(supportsHitConditionalBreakpoints, + "supportsHitConditionalBreakpoints"), + DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"), + DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"), + DAP_FIELD(supportsLogPoints, "supportsLogPoints"), + DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"), + DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"), + DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"), + DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"), + DAP_FIELD(supportsSetExpression, "supportsSetExpression"), + DAP_FIELD(supportsSetVariable, "supportsSetVariable"), + DAP_FIELD(supportsSingleThreadExecutionRequests, + "supportsSingleThreadExecutionRequests"), + DAP_FIELD(supportsStepBack, "supportsStepBack"), + DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"), + DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"), + DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"), + DAP_FIELD(supportsTerminateThreadsRequest, + "supportsTerminateThreadsRequest"), + DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"), + DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"), + DAP_FIELD(cmakeVersion, "cmakeVersion")); + +DAP_IMPLEMENT_STRUCT_TYPEINFO( + CMakeInitializeRequest, "initialize", DAP_FIELD(adapterID, "adapterID"), + DAP_FIELD(clientID, "clientID"), DAP_FIELD(clientName, "clientName"), + DAP_FIELD(columnsStartAt1, "columnsStartAt1"), + DAP_FIELD(linesStartAt1, "linesStartAt1"), DAP_FIELD(locale, "locale"), + DAP_FIELD(pathFormat, "pathFormat"), + DAP_FIELD(supportsArgsCanBeInterpretedByShell, + "supportsArgsCanBeInterpretedByShell"), + DAP_FIELD(supportsInvalidatedEvent, "supportsInvalidatedEvent"), + DAP_FIELD(supportsMemoryEvent, "supportsMemoryEvent"), + DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"), + DAP_FIELD(supportsProgressReporting, "supportsProgressReporting"), + DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"), + DAP_FIELD(supportsStartDebuggingRequest, "supportsStartDebuggingRequest"), + DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"), + DAP_FIELD(supportsVariableType, "supportsVariableType")); + +} // namespace dap diff --git a/Source/cmDebuggerProtocol.h b/Source/cmDebuggerProtocol.h new file mode 100644 index 0000000..4334aed --- /dev/null +++ b/Source/cmDebuggerProtocol.h @@ -0,0 +1,191 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +#include <cmcppdap/include/dap/optional.h> +#include <cmcppdap/include/dap/typeof.h> +#include <cmcppdap/include/dap/types.h> + +namespace dap { + +// Represents the cmake version. +struct CMakeVersion : public InitializeResponse +{ + // The major version number. + integer major; + // The minor version number. + integer minor; + // The patch number. + integer patch; + // The full version string. + string full; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeVersion); + +// Response to `initialize` request. +struct CMakeInitializeResponse : public Response +{ + // The set of additional module information exposed by the debug adapter. + optional<array<ColumnDescriptor>> additionalModuleColumns; + // The set of characters that should trigger completion in a REPL. If not + // specified, the UI should assume the `.` character. + optional<array<string>> completionTriggerCharacters; + // Available exception filter options for the `setExceptionBreakpoints` + // request. + optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters; + // The debug adapter supports the `suspendDebuggee` attribute on the + // `disconnect` request. + optional<boolean> supportSuspendDebuggee; + // The debug adapter supports the `terminateDebuggee` attribute on the + // `disconnect` request. + optional<boolean> supportTerminateDebuggee; + // Checksum algorithms supported by the debug adapter. + optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms; + // The debug adapter supports the `breakpointLocations` request. + optional<boolean> supportsBreakpointLocationsRequest; + // The debug adapter supports the `cancel` request. + optional<boolean> supportsCancelRequest; + // The debug adapter supports the `clipboard` context value in the `evaluate` + // request. + optional<boolean> supportsClipboardContext; + // The debug adapter supports the `completions` request. + optional<boolean> supportsCompletionsRequest; + // The debug adapter supports conditional breakpoints. + optional<boolean> supportsConditionalBreakpoints; + // The debug adapter supports the `configurationDone` request. + optional<boolean> supportsConfigurationDoneRequest; + // The debug adapter supports data breakpoints. + optional<boolean> supportsDataBreakpoints; + // The debug adapter supports the delayed loading of parts of the stack, + // which requires that both the `startFrame` and `levels` arguments and the + // `totalFrames` result of the `stackTrace` request are supported. + optional<boolean> supportsDelayedStackTraceLoading; + // The debug adapter supports the `disassemble` request. + optional<boolean> supportsDisassembleRequest; + // The debug adapter supports a (side effect free) `evaluate` request for + // data hovers. + optional<boolean> supportsEvaluateForHovers; + // The debug adapter supports `filterOptions` as an argument on the + // `setExceptionBreakpoints` request. + optional<boolean> supportsExceptionFilterOptions; + // The debug adapter supports the `exceptionInfo` request. + optional<boolean> supportsExceptionInfoRequest; + // The debug adapter supports `exceptionOptions` on the + // `setExceptionBreakpoints` request. + optional<boolean> supportsExceptionOptions; + // The debug adapter supports function breakpoints. + optional<boolean> supportsFunctionBreakpoints; + // The debug adapter supports the `gotoTargets` request. + optional<boolean> supportsGotoTargetsRequest; + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + optional<boolean> supportsHitConditionalBreakpoints; + // The debug adapter supports adding breakpoints based on instruction + // references. + optional<boolean> supportsInstructionBreakpoints; + // The debug adapter supports the `loadedSources` request. + optional<boolean> supportsLoadedSourcesRequest; + // The debug adapter supports log points by interpreting the `logMessage` + // attribute of the `SourceBreakpoint`. + optional<boolean> supportsLogPoints; + // The debug adapter supports the `modules` request. + optional<boolean> supportsModulesRequest; + // The debug adapter supports the `readMemory` request. + optional<boolean> supportsReadMemoryRequest; + // The debug adapter supports restarting a frame. + optional<boolean> supportsRestartFrame; + // The debug adapter supports the `restart` request. In this case a client + // should not implement `restart` by terminating and relaunching the adapter + // but by calling the `restart` request. + optional<boolean> supportsRestartRequest; + // The debug adapter supports the `setExpression` request. + optional<boolean> supportsSetExpression; + // The debug adapter supports setting a variable to a value. + optional<boolean> supportsSetVariable; + // The debug adapter supports the `singleThread` property on the execution + // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, + // `stepBack`). + optional<boolean> supportsSingleThreadExecutionRequests; + // The debug adapter supports stepping back via the `stepBack` and + // `reverseContinue` requests. + optional<boolean> supportsStepBack; + // The debug adapter supports the `stepInTargets` request. + optional<boolean> supportsStepInTargetsRequest; + // The debug adapter supports stepping granularities (argument `granularity`) + // for the stepping requests. + optional<boolean> supportsSteppingGranularity; + // The debug adapter supports the `terminate` request. + optional<boolean> supportsTerminateRequest; + // The debug adapter supports the `terminateThreads` request. + optional<boolean> supportsTerminateThreadsRequest; + // The debug adapter supports a `format` attribute on the `stackTrace`, + // `variables`, and `evaluate` requests. + optional<boolean> supportsValueFormattingOptions; + // The debug adapter supports the `writeMemory` request. + optional<boolean> supportsWriteMemoryRequest; + // The CMake version. + CMakeVersion cmakeVersion; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeResponse); + +// The `initialize` request is sent as the first request from the client to the +// debug adapter in order to configure it with client capabilities and to +// retrieve capabilities from the debug adapter. Until the debug adapter has +// responded with an `initialize` response, the client must not send any +// additional requests or events to the debug adapter. In addition the debug +// adapter is not allowed to send any requests or events to the client until it +// has responded with an `initialize` response. The `initialize` request may +// only be sent once. +struct CMakeInitializeRequest : public Request +{ + using Response = CMakeInitializeResponse; + // The ID of the debug adapter. + string adapterID; + // The ID of the client using this adapter. + optional<string> clientID; + // The human-readable name of the client using this adapter. + optional<string> clientName; + // If true all column numbers are 1-based (default). + optional<boolean> columnsStartAt1; + // If true all line numbers are 1-based (default). + optional<boolean> linesStartAt1; + // The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH. + optional<string> locale; + // Determines in what format paths are specified. The default is `path`, + // which is the native format. + // + // May be one of the following enumeration values: + // 'path', 'uri' + optional<string> pathFormat; + // Client supports the `argsCanBeInterpretedByShell` attribute on the + // `runInTerminal` request. + optional<boolean> supportsArgsCanBeInterpretedByShell; + // Client supports the `invalidated` event. + optional<boolean> supportsInvalidatedEvent; + // Client supports the `memory` event. + optional<boolean> supportsMemoryEvent; + // Client supports memory references. + optional<boolean> supportsMemoryReferences; + // Client supports progress reporting. + optional<boolean> supportsProgressReporting; + // Client supports the `runInTerminal` request. + optional<boolean> supportsRunInTerminalRequest; + // Client supports the `startDebugging` request. + optional<boolean> supportsStartDebuggingRequest; + // Client supports the paging of variables. + optional<boolean> supportsVariablePaging; + // Client supports the `type` attribute for variables. + optional<boolean> supportsVariableType; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeRequest); + +} // namespace dap diff --git a/Source/cmDebuggerSourceBreakpoint.cxx b/Source/cmDebuggerSourceBreakpoint.cxx new file mode 100644 index 0000000..d4665e6 --- /dev/null +++ b/Source/cmDebuggerSourceBreakpoint.cxx @@ -0,0 +1,14 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerSourceBreakpoint.h" + +namespace cmDebugger { + +cmDebuggerSourceBreakpoint::cmDebuggerSourceBreakpoint(int64_t id, + int64_t line) + : Id(id) + , Line(line) +{ +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerSourceBreakpoint.h b/Source/cmDebuggerSourceBreakpoint.h new file mode 100644 index 0000000..f6d6cac --- /dev/null +++ b/Source/cmDebuggerSourceBreakpoint.h @@ -0,0 +1,26 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> + +namespace cmDebugger { + +class cmDebuggerSourceBreakpoint +{ + int64_t Id; + int64_t Line; + bool IsValid = true; + +public: + cmDebuggerSourceBreakpoint(int64_t id, int64_t line); + int64_t GetId() const noexcept { return this->Id; } + int64_t GetLine() const noexcept { return this->Line; } + void ChangeLine(int64_t line) noexcept { this->Line = line; } + bool GetIsValid() const noexcept { return this->IsValid; } + void Invalid() noexcept { this->IsValid = false; } +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx new file mode 100644 index 0000000..789b0a5 --- /dev/null +++ b/Source/cmDebuggerStackFrame.cxx @@ -0,0 +1,28 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerStackFrame.h" + +#include <utility> + +#include "cmListFileCache.h" + +namespace cmDebugger { + +std::atomic<int64_t> cmDebuggerStackFrame::NextId(1); + +cmDebuggerStackFrame::cmDebuggerStackFrame(cmMakefile* mf, + std::string sourcePath, + cmListFileFunction const& lff) + : Id(NextId.fetch_add(1)) + , FileName(std::move(sourcePath)) + , Function(lff) + , Makefile(mf) +{ +} + +int64_t cmDebuggerStackFrame::GetLine() const noexcept +{ + return this->Function.Line(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h new file mode 100644 index 0000000..dc3b2ab --- /dev/null +++ b/Source/cmDebuggerStackFrame.h @@ -0,0 +1,33 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <string> + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { + +class cmDebuggerStackFrame +{ + static std::atomic<std::int64_t> NextId; + std::int64_t Id; + std::string FileName; + cmListFileFunction const& Function; + cmMakefile* Makefile; + +public: + cmDebuggerStackFrame(cmMakefile* mf, std::string sourcePath, + cmListFileFunction const& lff); + int64_t GetId() const noexcept { return this->Id; } + std::string const& GetFileName() const noexcept { return this->FileName; } + int64_t GetLine() const noexcept; + cmMakefile* GetMakefile() const noexcept { return this->Makefile; } +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx new file mode 100644 index 0000000..fd52f5a --- /dev/null +++ b/Source/cmDebuggerThread.cxx @@ -0,0 +1,150 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerThread.h" + +#include <cstdint> +#include <utility> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerVariables.h" +#include "cmDebuggerVariablesHelper.h" +#include "cmDebuggerVariablesManager.h" + +namespace cmDebugger { + +cmDebuggerThread::cmDebuggerThread(int64_t id, std::string name) + : Id(id) + , Name(std::move(name)) + , VariablesManager(std::make_shared<cmDebuggerVariablesManager>()) +{ +} + +void cmDebuggerThread::PushStackFrame(cmMakefile* mf, + std::string const& sourcePath, + cmListFileFunction const& lff) +{ + std::unique_lock<std::mutex> lock(Mutex); + Frames.emplace_back( + std::make_shared<cmDebuggerStackFrame>(mf, sourcePath, lff)); + FrameMap.insert({ Frames.back()->GetId(), Frames.back() }); +} + +void cmDebuggerThread::PopStackFrame() +{ + std::unique_lock<std::mutex> lock(Mutex); + FrameMap.erase(Frames.back()->GetId()); + FrameScopes.erase(Frames.back()->GetId()); + FrameVariables.erase(Frames.back()->GetId()); + Frames.pop_back(); +} + +std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetTopStackFrame() +{ + std::unique_lock<std::mutex> lock(Mutex); + if (!Frames.empty()) { + return Frames.back(); + } + + return {}; +} + +std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetStackFrame( + int64_t frameId) +{ + std::unique_lock<std::mutex> lock(Mutex); + auto it = FrameMap.find(frameId); + + if (it == FrameMap.end()) { + return {}; + } + + return it->second; +} + +dap::ScopesResponse cmDebuggerThread::GetScopesResponse( + int64_t frameId, bool supportsVariableType) +{ + std::unique_lock<std::mutex> lock(Mutex); + auto it = FrameScopes.find(frameId); + + if (it != FrameScopes.end()) { + dap::ScopesResponse response; + response.scopes = it->second; + return response; + } + + auto it2 = FrameMap.find(frameId); + if (it2 == FrameMap.end()) { + return dap::ScopesResponse(); + } + + std::shared_ptr<cmDebuggerStackFrame> frame = it2->second; + std::shared_ptr<cmDebuggerVariables> localVariables = + cmDebuggerVariablesHelper::Create(VariablesManager, "Locals", + supportsVariableType, frame); + + FrameVariables[frameId].emplace_back(localVariables); + + dap::Scope scope; + scope.name = localVariables->GetName(); + scope.presentationHint = "locals"; + scope.variablesReference = localVariables->GetId(); + + dap::Source source; + source.name = frame->GetFileName(); + source.path = source.name; + scope.source = source; + + FrameScopes[frameId].push_back(scope); + + dap::ScopesResponse response; + response.scopes.push_back(scope); + return response; +} + +dap::VariablesResponse cmDebuggerThread::GetVariablesResponse( + dap::VariablesRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + dap::VariablesResponse response; + response.variables = VariablesManager->HandleVariablesRequest(request); + return response; +} + +dap::StackTraceResponse GetStackTraceResponse( + std::shared_ptr<cmDebuggerThread> const& thread) +{ + dap::StackTraceResponse response; + std::unique_lock<std::mutex> lock(thread->Mutex); + for (int i = static_cast<int>(thread->Frames.size()) - 1; i >= 0; --i) { + dap::Source source; + source.name = thread->Frames[i]->GetFileName(); + source.path = source.name; + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + dap::StackFrame stackFrame; +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + stackFrame.line = thread->Frames[i]->GetLine(); + stackFrame.column = 1; + stackFrame.name = thread->Frames[i]->GetFileName() + " Line " + + std::to_string(stackFrame.line); + stackFrame.id = thread->Frames[i]->GetId(); + stackFrame.source = source; + + response.stackFrames.push_back(stackFrame); + } + + response.totalFrames = response.stackFrames.size(); + return response; +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h new file mode 100644 index 0000000..65ee2cf --- /dev/null +++ b/Source/cmDebuggerThread.h @@ -0,0 +1,59 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { +class cmDebuggerStackFrame; +class cmDebuggerVariables; +class cmDebuggerVariablesManager; +} + +namespace cmDebugger { + +class cmDebuggerThread +{ + int64_t Id; + std::string Name; + std::vector<std::shared_ptr<cmDebuggerStackFrame>> Frames; + std::unordered_map<int64_t, std::shared_ptr<cmDebuggerStackFrame>> FrameMap; + std::mutex Mutex; + std::unordered_map<int64_t, std::vector<dap::Scope>> FrameScopes; + std::unordered_map<int64_t, + std::vector<std::shared_ptr<cmDebuggerVariables>>> + FrameVariables; + std::shared_ptr<cmDebuggerVariablesManager> VariablesManager; + +public: + cmDebuggerThread(int64_t id, std::string name); + int64_t GetId() const { return this->Id; } + const std::string& GetName() const { return this->Name; } + void PushStackFrame(cmMakefile* mf, std::string const& sourcePath, + cmListFileFunction const& lff); + void PopStackFrame(); + std::shared_ptr<cmDebuggerStackFrame> GetTopStackFrame(); + std::shared_ptr<cmDebuggerStackFrame> GetStackFrame(int64_t frameId); + size_t GetStackFrameSize() const { return this->Frames.size(); } + dap::ScopesResponse GetScopesResponse(int64_t frameId, + bool supportsVariableType); + dap::VariablesResponse GetVariablesResponse( + dap::VariablesRequest const& request); + friend dap::StackTraceResponse GetStackTraceResponse( + std::shared_ptr<cmDebuggerThread> const& thread); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx new file mode 100644 index 0000000..0eb443b --- /dev/null +++ b/Source/cmDebuggerThreadManager.cxx @@ -0,0 +1,47 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerThreadManager.h" + +#include <algorithm> + +#include <cm3p/cppdap/protocol.h> + +#include "cmDebuggerThread.h" + +namespace cmDebugger { + +std::atomic<int64_t> cmDebuggerThreadManager::NextThreadId(1); + +std::shared_ptr<cmDebuggerThread> cmDebuggerThreadManager::StartThread( + std::string const& name) +{ + std::shared_ptr<cmDebuggerThread> thread = + std::make_shared<cmDebuggerThread>( + cmDebuggerThreadManager::NextThreadId.fetch_add(1), name); + Threads.emplace_back(thread); + return thread; +} + +void cmDebuggerThreadManager::EndThread( + std::shared_ptr<cmDebuggerThread> const& thread) +{ + Threads.remove(thread); +} + +cm::optional<dap::StackTraceResponse> +cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id) +{ + auto it = find_if(Threads.begin(), Threads.end(), + [&](const std::shared_ptr<cmDebuggerThread>& t) { + return t->GetId() == id; + }); + + if (it == Threads.end()) { + return {}; + } + + return GetStackTraceResponse(*it); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h new file mode 100644 index 0000000..934cf85 --- /dev/null +++ b/Source/cmDebuggerThreadManager.h @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <list> +#include <memory> +#include <string> + +#include <cm/optional> + +namespace cmDebugger { +class cmDebuggerThread; +} + +namespace dap { +struct StackTraceResponse; +} + +namespace cmDebugger { + +class cmDebuggerThreadManager +{ + static std::atomic<std::int64_t> NextThreadId; + std::list<std::shared_ptr<cmDebuggerThread>> Threads; + +public: + cmDebuggerThreadManager() = default; + std::shared_ptr<cmDebuggerThread> StartThread(std::string const& name); + void EndThread(std::shared_ptr<cmDebuggerThread> const& thread); + cm::optional<dap::StackTraceResponse> GetThreadStackTraceResponse( + std::int64_t id); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariables.cxx b/Source/cmDebuggerVariables.cxx new file mode 100644 index 0000000..40fe41f --- /dev/null +++ b/Source/cmDebuggerVariables.cxx @@ -0,0 +1,133 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariables.h" + +#include <algorithm> +#include <vector> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerVariablesManager.h" + +namespace cmDebugger { + +namespace { +const dap::VariablePresentationHint PrivatePropertyHint = { {}, + "property", + {}, + "private" }; +const dap::VariablePresentationHint PrivateDataHint = { {}, + "data", + {}, + "private" }; +} + +std::atomic<int64_t> cmDebuggerVariables::NextId(1); + +cmDebuggerVariables::cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType) + : Id(NextId.fetch_add(1)) + , Name(std::move(name)) + , SupportsVariableType(supportsVariableType) + , VariablesManager(std::move(variablesManager)) +{ + VariablesManager->RegisterHandler( + Id, [this](dap::VariablesRequest const& request) { + (void)request; + return this->HandleVariablesRequest(); + }); +} + +cmDebuggerVariables::cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType, + std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunction) + : Id(NextId.fetch_add(1)) + , Name(std::move(name)) + , GetKeyValuesFunction(std::move(getKeyValuesFunction)) + , SupportsVariableType(supportsVariableType) + , VariablesManager(std::move(variablesManager)) +{ + VariablesManager->RegisterHandler( + Id, [this](dap::VariablesRequest const& request) { + (void)request; + return this->HandleVariablesRequest(); + }); +} + +void cmDebuggerVariables::AddSubVariables( + std::shared_ptr<cmDebuggerVariables> const& variables) +{ + if (variables != nullptr) { + SubVariables.emplace_back(variables); + } +} + +dap::array<dap::Variable> cmDebuggerVariables::HandleVariablesRequest() +{ + dap::array<dap::Variable> variables; + + if (GetKeyValuesFunction != nullptr) { + auto values = GetKeyValuesFunction(); + for (auto const& entry : values) { + if (IgnoreEmptyStringEntries && entry.Type == "string" && + entry.Value.empty()) { + continue; + } + variables.push_back(dap::Variable{ {}, + {}, + {}, + entry.Name, + {}, + PrivateDataHint, + entry.Type, + entry.Value, + 0 }); + } + } + + EnumerateSubVariablesIfAny(variables); + + if (EnableSorting) { + std::sort(variables.begin(), variables.end(), + [](dap::Variable const& a, dap::Variable const& b) { + return a.name < b.name; + }); + } + return variables; +} + +void cmDebuggerVariables::EnumerateSubVariablesIfAny( + dap::array<dap::Variable>& toBeReturned) const +{ + dap::array<dap::Variable> ret; + for (auto const& variables : SubVariables) { + toBeReturned.emplace_back( + dap::Variable{ {}, + {}, + {}, + variables->GetName(), + {}, + PrivatePropertyHint, + SupportsVariableType ? "collection" : nullptr, + variables->GetValue(), + variables->GetId() }); + } +} + +void cmDebuggerVariables::ClearSubVariables() +{ + SubVariables.clear(); +} + +cmDebuggerVariables::~cmDebuggerVariables() +{ + ClearSubVariables(); + VariablesManager->UnregisterHandler(Id); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariables.h b/Source/cmDebuggerVariables.h new file mode 100644 index 0000000..eaaf2a8 --- /dev/null +++ b/Source/cmDebuggerVariables.h @@ -0,0 +1,124 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <cm3p/cppdap/types.h> // IWYU pragma: keep + +namespace cmDebugger { +class cmDebuggerVariablesManager; +} + +namespace dap { +struct Variable; +} + +namespace cmDebugger { + +struct cmDebuggerVariableEntry +{ + cmDebuggerVariableEntry() + : cmDebuggerVariableEntry("", "", "") + { + } + cmDebuggerVariableEntry(std::string name, std::string value, + std::string type) + : Name(std::move(name)) + , Value(std::move(value)) + , Type(std::move(type)) + { + } + cmDebuggerVariableEntry(std::string name, std::string value) + : Name(std::move(name)) + , Value(std::move(value)) + , Type("string") + { + } + cmDebuggerVariableEntry(std::string name, const char* value) + : Name(std::move(name)) + , Value(value == nullptr ? "" : value) + , Type("string") + { + } + cmDebuggerVariableEntry(std::string name, bool value) + : Name(std::move(name)) + , Value(value ? "TRUE" : "FALSE") + , Type("bool") + { + } + cmDebuggerVariableEntry(std::string name, int64_t value) + : Name(std::move(name)) + , Value(std::to_string(value)) + , Type("int") + { + } + cmDebuggerVariableEntry(std::string name, int value) + : Name(std::move(name)) + , Value(std::to_string(value)) + , Type("int") + { + } + std::string const Name; + std::string const Value; + std::string const Type; +}; + +class cmDebuggerVariables +{ + static std::atomic<int64_t> NextId; + int64_t Id; + std::string Name; + std::string Value; + + std::function<std::vector<cmDebuggerVariableEntry>()> GetKeyValuesFunction; + std::vector<std::shared_ptr<cmDebuggerVariables>> SubVariables; + bool IgnoreEmptyStringEntries = false; + bool EnableSorting = true; + + virtual dap::array<dap::Variable> HandleVariablesRequest(); + friend class cmDebuggerVariablesManager; + +protected: + const bool SupportsVariableType; + std::shared_ptr<cmDebuggerVariablesManager> VariablesManager; + void EnumerateSubVariablesIfAny( + dap::array<dap::Variable>& toBeReturned) const; + void ClearSubVariables(); + +public: + cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType); + cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType, + std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunc); + inline int64_t GetId() const noexcept { return this->Id; } + inline std::string GetName() const noexcept { return this->Name; } + inline std::string GetValue() const noexcept { return this->Value; } + inline void SetValue(std::string const& value) noexcept + { + this->Value = value; + } + void AddSubVariables(std::shared_ptr<cmDebuggerVariables> const& variables); + inline void SetIgnoreEmptyStringEntries(bool value) noexcept + { + this->IgnoreEmptyStringEntries = value; + } + inline void SetEnableSorting(bool value) noexcept + { + this->EnableSorting = value; + } + virtual ~cmDebuggerVariables(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesHelper.cxx b/Source/cmDebuggerVariablesHelper.cxx new file mode 100644 index 0000000..42ce5e7 --- /dev/null +++ b/Source/cmDebuggerVariablesHelper.cxx @@ -0,0 +1,644 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariablesHelper.h" + +#include <algorithm> +#include <cstddef> +#include <functional> +#include <iomanip> +#include <map> +#include <sstream> + +#include "cm_codecvt.hxx" + +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerVariables.h" +#include "cmFileSet.h" +#include "cmGlobalGenerator.h" +#include "cmList.h" +#include "cmListFileCache.h" +#include "cmMakefile.h" +#include "cmPropertyMap.h" +#include "cmState.h" +#include "cmStateSnapshot.h" +#include "cmTarget.h" +#include "cmTest.h" +#include "cmValue.h" +#include "cmake.h" + +namespace cmDebugger { + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmPolicies::PolicyMap const& policyMap) +{ + static std::map<cmPolicies::PolicyStatus, std::string> policyStatusString = { + { cmPolicies::PolicyStatus::OLD, "OLD" }, + { cmPolicies::PolicyStatus::WARN, "WARN" }, + { cmPolicies::PolicyStatus::NEW, "NEW" }, + { cmPolicies::PolicyStatus::REQUIRED_IF_USED, "REQUIRED_IF_USED" }, + { cmPolicies::PolicyStatus::REQUIRED_ALWAYS, "REQUIRED_ALWAYS" } + }; + + return std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(cmPolicies::CMPCOUNT); + for (int i = 0; i < cmPolicies::CMPCOUNT; ++i) { + if (policyMap.IsDefined(static_cast<cmPolicies::PolicyID>(i))) { + auto status = policyMap.Get(static_cast<cmPolicies::PolicyID>(i)); + std::ostringstream ss; + ss << "CMP" << std::setfill('0') << std::setw(4) << i; + ret.emplace_back(ss.str(), policyStatusString[status]); + } + } + return ret; + }); +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::pair<std::string, std::string>> const& list) +{ + if (list.empty()) { + return {}; + } + + auto listVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(list.size()); + for (auto const& kv : list) { + ret.emplace_back(kv.first, kv.second); + } + return ret; + }); + + listVariables->SetValue(std::to_string(list.size())); + return listVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmBTStringRange const& entries) +{ + if (entries.empty()) { + return {}; + } + + auto sourceEntries = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& entry : entries) { + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, entry.Value, supportsVariableType, [=]() { + cmList items{ entry.Value }; + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(items.size()); + int i = 0; + for (std::string const& item : items) { + ret.emplace_back("[" + std::to_string(i++) + "]", item); + } + return ret; + }); + arrayVariables->SetEnableSorting(false); + sourceEntries->AddSubVariables(arrayVariables); + } + + sourceEntries->SetValue(std::to_string(entries.size())); + return sourceEntries; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::set<std::string> const& values) +{ + if (values.empty()) { + return {}; + } + + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(values.size()); + int i = 0; + for (std::string const& value : values) { + ret.emplace_back("[" + std::to_string(i++) + "]", value); + } + return ret; + }); + arrayVariables->SetValue(std::to_string(values.size())); + arrayVariables->SetEnableSorting(false); + return arrayVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::string> const& values) +{ + if (values.empty()) { + return {}; + } + + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(values.size()); + int i = 0; + for (std::string const& value : values) { + ret.emplace_back("[" + std::to_string(i++) + "]", value); + } + return ret; + }); + + arrayVariables->SetValue(std::to_string(values.size())); + arrayVariables->SetEnableSorting(false); + return arrayVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<BT<std::string>> const& list) +{ + if (list.empty()) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(list.size()); + int i = 0; + for (auto const& item : list) { + ret.emplace_back("[" + std::to_string(i++) + "]", item.Value); + } + + return ret; + }); + + variables->SetValue(std::to_string(list.size())); + variables->SetEnableSorting(false); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmFileSet* fileSet) +{ + if (fileSet == nullptr) { + return {}; + } + + static auto visibilityString = [](cmFileSetVisibility visibility) { + switch (visibility) { + case cmFileSetVisibility::Private: + return "Private"; + case cmFileSetVisibility::Public: + return "Public"; + case cmFileSetVisibility::Interface: + return "Interface"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret{ + { "Name", fileSet->GetName() }, + { "Type", fileSet->GetType() }, + { "Visibility", visibilityString(fileSet->GetVisibility()) }, + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny(variablesManager, "Directories", + supportsVariableType, + fileSet->GetDirectoryEntries())); + variables->AddSubVariables(CreateIfAny(variablesManager, "Files", + supportsVariableType, + fileSet->GetFileEntries())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmFileSet*> const& fileSets) +{ + if (fileSets.empty()) { + return {}; + } + + auto fileSetsVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& fileSet : fileSets) { + fileSetsVariables->AddSubVariables(CreateIfAny( + variablesManager, fileSet->GetName(), supportsVariableType, fileSet)); + } + + return fileSetsVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTarget*> const& targets) +{ + if (targets.empty()) { + return {}; + } + + auto targetsVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& target : targets) { + auto targetVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, target->GetName(), supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "InstallPath", target->GetInstallPath() }, + { "IsAIX", target->IsAIX() }, + { "IsAndroidGuiExecutable", target->IsAndroidGuiExecutable() }, + { "IsAppBundleOnApple", target->IsAppBundleOnApple() }, + { "IsDLLPlatform", target->IsDLLPlatform() }, + { "IsExecutableWithExports", target->IsExecutableWithExports() }, + { "IsFrameworkOnApple", target->IsFrameworkOnApple() }, + { "IsImported", target->IsImported() }, + { "IsImportedGloballyVisible", target->IsImportedGloballyVisible() }, + { "IsPerConfig", target->IsPerConfig() }, + { "Name", target->GetName() }, + { "RuntimeInstallPath", target->GetRuntimeInstallPath() }, + { "Type", cmState::GetTargetTypeName(target->GetType()) }, + }; + + return ret; + }); + targetVariables->SetValue(cmState::GetTargetTypeName(target->GetType())); + + targetVariables->AddSubVariables(Create(variablesManager, "PolicyMap", + supportsVariableType, + target->GetPolicyMap())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "Properties", supportsVariableType, + target->GetProperties().GetList())); + + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "IncludeDirectories", supportsVariableType, + target->GetIncludeDirectoriesEntries())); + targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Sources", + supportsVariableType, + target->GetSourceEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileDefinitions", supportsVariableType, + target->GetCompileDefinitionsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileFeatures", supportsVariableType, + target->GetCompileFeaturesEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileOptions", supportsVariableType, + target->GetCompileOptionsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "CxxModuleHeaderSets", supportsVariableType, + target->GetCxxModuleHeaderSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CxxModuleSets", supportsVariableType, + target->GetCxxModuleSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "HeaderSets", supportsVariableType, + target->GetHeaderSetsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "InterfaceCxxModuleHeaderSets", supportsVariableType, + target->GetInterfaceCxxModuleHeaderSetsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "InterfaceHeaderSets", supportsVariableType, + target->GetInterfaceHeaderSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkDirectories", supportsVariableType, + target->GetLinkDirectoriesEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkImplementations", supportsVariableType, + target->GetLinkImplementationEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkInterfaceDirects", supportsVariableType, + target->GetLinkInterfaceDirectEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkInterfaceDirectExcludes", supportsVariableType, + target->GetLinkInterfaceDirectExcludeEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkInterfaces", supportsVariableType, + target->GetLinkInterfaceEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkOptions", supportsVariableType, + target->GetLinkOptionsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "SystemIncludeDirectories", supportsVariableType, + target->GetSystemIncludeDirectories())); + targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Makefile", + supportsVariableType, + target->GetMakefile())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "GlobalGenerator", supportsVariableType, + target->GetGlobalGenerator())); + + std::vector<cmFileSet*> allFileSets; + auto allFileSetNames = target->GetAllFileSetNames(); + allFileSets.reserve(allFileSetNames.size()); + for (auto const& fileSetName : allFileSetNames) { + allFileSets.emplace_back(target->GetFileSet(fileSetName)); + } + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "AllFileSets", supportsVariableType, allFileSets)); + + std::vector<cmFileSet*> allInterfaceFileSets; + auto allInterfaceFileSetNames = target->GetAllInterfaceFileSets(); + allInterfaceFileSets.reserve(allInterfaceFileSetNames.size()); + for (auto const& interfaceFileSetName : allInterfaceFileSetNames) { + allInterfaceFileSets.emplace_back( + target->GetFileSet(interfaceFileSetName)); + } + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "AllInterfaceFileSets", + supportsVariableType, allInterfaceFileSets)); + + targetVariables->SetIgnoreEmptyStringEntries(true); + targetsVariables->AddSubVariables(targetVariables); + } + + targetsVariables->SetValue(std::to_string(targets.size())); + return targetsVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::shared_ptr<cmDebuggerStackFrame> const& frame) +{ + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + return std::vector<cmDebuggerVariableEntry>{ { "CurrentLine", + frame->GetLine() } }; + }); + + auto closureKeys = frame->GetMakefile()->GetStateSnapshot().ClosureKeys(); + auto locals = std::make_shared<cmDebuggerVariables>( + variablesManager, "Locals", supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(closureKeys.size()); + for (auto const& key : closureKeys) { + ret.emplace_back( + key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key)); + } + return ret; + }); + locals->SetValue(std::to_string(closureKeys.size())); + variables->AddSubVariables(locals); + + std::function<bool(std::string const&)> isDirectory = + [](std::string const& key) { + size_t pos1 = key.rfind("_DIR"); + size_t pos2 = key.rfind("_DIRECTORY"); + return !((pos1 == std::string::npos || pos1 != key.size() - 4) && + (pos2 == std::string::npos || pos2 != key.size() - 10)); + }; + auto directorySize = + std::count_if(closureKeys.begin(), closureKeys.end(), isDirectory); + auto directories = std::make_shared<cmDebuggerVariables>( + variablesManager, "Directories", supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(directorySize); + for (auto const& key : closureKeys) { + if (isDirectory(key)) { + ret.emplace_back( + key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key)); + } + } + return ret; + }); + directories->SetValue(std::to_string(directorySize)); + variables->AddSubVariables(directories); + + auto cacheVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, "CacheVariables", supportsVariableType); + auto* state = frame->GetMakefile()->GetCMakeInstance()->GetState(); + auto keys = state->GetCacheEntryKeys(); + for (auto const& key : keys) { + auto entry = std::make_shared<cmDebuggerVariables>( + variablesManager, + key + ":" + + cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)), + supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + auto properties = state->GetCacheEntryPropertyList(key); + ret.reserve(properties.size() + 2); + for (auto const& propertyName : properties) { + ret.emplace_back(propertyName, + state->GetCacheEntryProperty(key, propertyName)); + } + + ret.emplace_back( + "TYPE", + cmState::CacheEntryTypeToString(state->GetCacheEntryType(key))); + ret.emplace_back("VALUE", state->GetCacheEntryValue(key)); + return ret; + }); + + entry->SetValue(state->GetCacheEntryValue(key)); + cacheVariables->AddSubVariables(entry); + } + + cacheVariables->SetValue(std::to_string(keys.size())); + variables->AddSubVariables(cacheVariables); + + auto targetVariables = + CreateIfAny(variablesManager, "Targets", supportsVariableType, + frame->GetMakefile()->GetOrderedTargets()); + + variables->AddSubVariables(targetVariables); + std::vector<cmTest*> tests; + frame->GetMakefile()->GetTests( + frame->GetMakefile()->GetDefaultConfiguration(), tests); + variables->AddSubVariables( + CreateIfAny(variablesManager, "Tests", supportsVariableType, tests)); + + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmTest* test) +{ + if (test == nullptr) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret{ + { "CommandExpandLists", test->GetCommandExpandLists() }, + { "Name", test->GetName() }, + { "OldStyle", test->GetOldStyle() }, + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny( + variablesManager, "Command", supportsVariableType, test->GetCommand())); + + variables->AddSubVariables(CreateIfAny(variablesManager, "Properties", + supportsVariableType, + test->GetProperties().GetList())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTest*> const& tests) +{ + if (tests.empty()) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& test : tests) { + variables->AddSubVariables(CreateIfAny(variablesManager, test->GetName(), + supportsVariableType, test)); + } + variables->SetValue(std::to_string(tests.size())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmMakefile* mf) +{ + if (mf == nullptr) { + return {}; + } + + auto AppleSDKTypeString = [&](cmMakefile::AppleSDK sdk) { + switch (sdk) { + case cmMakefile::AppleSDK::MacOS: + return "MacOS"; + case cmMakefile::AppleSDK::IPhoneOS: + return "IPhoneOS"; + case cmMakefile::AppleSDK::IPhoneSimulator: + return "IPhoneSimulator"; + case cmMakefile::AppleSDK::AppleTVOS: + return "AppleTVOS"; + case cmMakefile::AppleSDK::AppleTVSimulator: + return "AppleTVSimulator"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "DefineFlags", mf->GetDefineFlags() }, + { "DirectoryId", mf->GetDirectoryId().String }, + { "IsRootMakefile", mf->IsRootMakefile() }, + { "HomeDirectory", mf->GetHomeDirectory() }, + { "HomeOutputDirectory", mf->GetHomeOutputDirectory() }, + { "CurrentSourceDirectory", mf->GetCurrentSourceDirectory() }, + { "CurrentBinaryDirectory", mf->GetCurrentBinaryDirectory() }, + { "PlatformIs32Bit", mf->PlatformIs32Bit() }, + { "PlatformIs64Bit", mf->PlatformIs64Bit() }, + { "PlatformIsx32", mf->PlatformIsx32() }, + { "AppleSDKType", AppleSDKTypeString(mf->GetAppleSDKType()) }, + { "PlatformIsAppleEmbedded", mf->PlatformIsAppleEmbedded() } + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny( + variablesManager, "ListFiles", supportsVariableType, mf->GetListFiles())); + variables->AddSubVariables(CreateIfAny(variablesManager, "OutputFiles", + supportsVariableType, + mf->GetOutputFiles())); + + variables->SetIgnoreEmptyStringEntries(true); + variables->SetValue(mf->GetDirectoryId().String); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmGlobalGenerator* gen) +{ + if (gen == nullptr) { + return {}; + } + + auto makeFileEncodingString = [](codecvt::Encoding encoding) { + switch (encoding) { + case codecvt::Encoding::None: + return "None"; + case codecvt::Encoding::UTF8: + return "UTF8"; + case codecvt::Encoding::UTF8_WITH_BOM: + return "UTF8_WITH_BOM"; + case codecvt::Encoding::ANSI: + return "ANSI"; + case codecvt::Encoding::ConsoleOutput: + return "ConsoleOutput"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "AllTargetName", gen->GetAllTargetName() }, + { "CleanTargetName", gen->GetCleanTargetName() }, + { "EditCacheCommand", gen->GetEditCacheCommand() }, + { "EditCacheTargetName", gen->GetEditCacheTargetName() }, + { "ExtraGeneratorName", gen->GetExtraGeneratorName() }, + { "ForceUnixPaths", gen->GetForceUnixPaths() }, + { "InstallLocalTargetName", gen->GetInstallLocalTargetName() }, + { "InstallStripTargetName", gen->GetInstallStripTargetName() }, + { "InstallTargetName", gen->GetInstallTargetName() }, + { "IsMultiConfig", gen->IsMultiConfig() }, + { "Name", gen->GetName() }, + { "MakefileEncoding", + makeFileEncodingString(gen->GetMakefileEncoding()) }, + { "PackageSourceTargetName", gen->GetPackageSourceTargetName() }, + { "PackageTargetName", gen->GetPackageTargetName() }, + { "PreinstallTargetName", gen->GetPreinstallTargetName() }, + { "NeedSymbolicMark", gen->GetNeedSymbolicMark() }, + { "RebuildCacheTargetName", gen->GetRebuildCacheTargetName() }, + { "TestTargetName", gen->GetTestTargetName() }, + { "UseLinkScript", gen->GetUseLinkScript() }, + }; + + return ret; + }); + + if (gen->GetInstallComponents() != nullptr) { + variables->AddSubVariables( + CreateIfAny(variablesManager, "InstallComponents", supportsVariableType, + *gen->GetInstallComponents())); + } + + variables->SetIgnoreEmptyStringEntries(true); + variables->SetValue(gen->GetName()); + + return variables; +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesHelper.h b/Source/cmDebuggerVariablesHelper.h new file mode 100644 index 0000000..9b11eaf --- /dev/null +++ b/Source/cmDebuggerVariablesHelper.h @@ -0,0 +1,106 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "cmAlgorithms.h" +#include "cmPolicies.h" + +class cmFileSet; +class cmGlobalGenerator; +class cmMakefile; +class cmTarget; +class cmTest; + +namespace cmDebugger { +class cmDebuggerStackFrame; +class cmDebuggerVariables; +class cmDebuggerVariablesManager; +} + +template <typename T> +class BT; + +namespace cmDebugger { + +class cmDebuggerVariablesHelper +{ + cmDebuggerVariablesHelper() = default; + +public: + static std::shared_ptr<cmDebuggerVariables> Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmPolicies::PolicyMap const& policyMap); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::pair<std::string, std::string>> const& list); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmBTStringRange const& entries); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::set<std::string> const& values); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::string> const& values); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<BT<std::string>> const& list); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmFileSet* fileSet); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmFileSet*> const& fileSets); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmTest* test); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTest*> const& tests); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTarget*> const& targets); + + static std::shared_ptr<cmDebuggerVariables> Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::shared_ptr<cmDebuggerStackFrame> const& frame); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmMakefile* mf); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmGlobalGenerator* gen); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesManager.cxx b/Source/cmDebuggerVariablesManager.cxx new file mode 100644 index 0000000..9b9b476 --- /dev/null +++ b/Source/cmDebuggerVariablesManager.cxx @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariablesManager.h" + +#include <utility> + +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/types.h> + +namespace cmDebugger { + +void cmDebuggerVariablesManager::RegisterHandler( + int64_t id, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)> + handler) +{ + VariablesHandlers[id] = std::move(handler); +} + +void cmDebuggerVariablesManager::UnregisterHandler(int64_t id) +{ + VariablesHandlers.erase(id); +} + +dap::array<dap::Variable> cmDebuggerVariablesManager::HandleVariablesRequest( + dap::VariablesRequest const& request) +{ + auto it = VariablesHandlers.find(request.variablesReference); + + if (it != VariablesHandlers.end()) { + return it->second(request); + } + + return dap::array<dap::Variable>(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesManager.h b/Source/cmDebuggerVariablesManager.h new file mode 100644 index 0000000..c219164 --- /dev/null +++ b/Source/cmDebuggerVariablesManager.h @@ -0,0 +1,40 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> +#include <functional> +#include <unordered_map> +#include <vector> + +#include <cm3p/cppdap/types.h> // IWYU pragma: keep + +namespace dap { +struct Variable; +struct VariablesRequest; +} + +namespace cmDebugger { + +class cmDebuggerVariablesManager +{ + std::unordered_map< + int64_t, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>> + VariablesHandlers; + void RegisterHandler( + int64_t id, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)> + handler); + void UnregisterHandler(int64_t id); + friend class cmDebuggerVariables; + +public: + cmDebuggerVariablesManager() = default; + dap::array<dap::Variable> HandleVariablesRequest( + dap::VariablesRequest const& request); +}; + +} // namespace cmDebugger diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 585924d..92ba2d4 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -68,6 +68,10 @@ # include "cmVariableWatch.h" #endif +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + #ifndef __has_feature # define __has_feature(x) 0 #endif @@ -424,6 +428,13 @@ public: return argsValue; }); #endif +#ifdef CMake_ENABLE_DEBUGGER + if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->Makefile->GetCMakeInstance() + ->GetDebugAdapter() + ->OnBeginFunctionCall(mf, lfc.FilePath, lff); + } +#endif } ~cmMakefileCall() @@ -434,6 +445,13 @@ public: this->Makefile->ExecutionStatusStack.pop_back(); --this->Makefile->RecursionDepth; this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); +#ifdef CMake_ENABLE_DEBUGGER + if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->Makefile->GetCMakeInstance() + ->GetDebugAdapter() + ->OnEndFunctionCall(); + } +#endif } cmMakefileCall(const cmMakefileCall&) = delete; @@ -663,12 +681,33 @@ bool cmMakefile::ReadDependentFile(const std::string& filename, IncludeScope incScope(this, filenametoread, noPolicyScope); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, filenametoread); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { incScope.Quiet(); @@ -764,12 +803,33 @@ bool cmMakefile::ReadListFile(const std::string& filename) ListFileScope scope(this, filenametoread); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, filenametoread); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); @@ -791,6 +851,13 @@ bool cmMakefile::ReadListFileAsString(const std::string& content, return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); @@ -1658,11 +1725,33 @@ void cmMakefile::Configure() assert(cmSystemTools::FileExists(currentStart, true)); this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, currentStart); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return; } + +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + currentStart, listFile.Functions); + } +#endif + if (this->IsRootMakefile()) { bool hasVersion = false; // search for the right policy command @@ -3769,6 +3858,12 @@ void cmMakefile::DisplayStatus(const std::string& message, float s) const return; } cm->UpdateProgress(message, s); + +#ifdef CMake_ENABLE_DEBUGGER + if (cm->GetDebugAdapter() != nullptr) { + cm->GetDebugAdapter()->OnMessageOutput(MessageType::MESSAGE, message); + } +#endif } std::string cmMakefile::GetModulesFile(const std::string& filename, diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx index baf40f8..68b3a5d 100644 --- a/Source/cmMessageCommand.cxx +++ b/Source/cmMessageCommand.cxx @@ -3,6 +3,7 @@ #include "cmMessageCommand.h" #include <cassert> +#include <memory> #include <utility> #include <cm/string_view> @@ -19,6 +20,10 @@ #include "cmSystemTools.h" #include "cmake.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + namespace { enum class CheckingType @@ -202,6 +207,12 @@ bool cmMessageCommand(std::vector<std::string> const& args, case Message::LogLevel::LOG_NOTICE: cmSystemTools::Message(IndentText(message, mf)); +#ifdef CMake_ENABLE_DEBUGGER + if (mf.GetCMakeInstance()->GetDebugAdapter() != nullptr) { + mf.GetCMakeInstance()->GetDebugAdapter()->OnMessageOutput(type, + message); + } +#endif break; case Message::LogLevel::LOG_STATUS: diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx index 7de8936..4e975d1 100644 --- a/Source/cmMessenger.cxx +++ b/Source/cmMessenger.cxx @@ -16,6 +16,10 @@ #include "cmsys/Terminal.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + MessageType cmMessenger::ConvertMessageType(MessageType t) const { if (t == MessageType::AUTHOR_WARNING || t == MessageType::AUTHOR_ERROR) { @@ -207,6 +211,12 @@ void cmMessenger::DisplayMessage(MessageType t, const std::string& text, PrintCallStack(msg, backtrace, this->TopSource); displayMessage(t, msg); + +#ifdef CMake_ENABLE_DEBUGGER + if (DebuggerAdapter != nullptr) { + DebuggerAdapter->OnMessageOutput(t, msg.str()); + } +#endif } void cmMessenger::PrintBacktraceTitle(std::ostream& out, diff --git a/Source/cmMessenger.h b/Source/cmMessenger.h index 451add0..bdefb00 100644 --- a/Source/cmMessenger.h +++ b/Source/cmMessenger.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include <iosfwd> +#include <memory> #include <string> #include <cm/optional> @@ -12,6 +13,12 @@ #include "cmListFileCache.h" #include "cmMessageType.h" +#ifdef CMake_ENABLE_DEBUGGER +namespace cmDebugger { +class cmDebuggerAdapter; +} +#endif + class cmMessenger { public: @@ -55,6 +62,13 @@ public: // Print the top of a backtrace. void PrintBacktraceTitle(std::ostream& out, cmListFileBacktrace const& bt) const; +#ifdef CMake_ENABLE_DEBUGGER + void SetDebuggerAdapter( + std::shared_ptr<cmDebugger::cmDebuggerAdapter> const& debuggerAdapter) + { + DebuggerAdapter = debuggerAdapter; + } +#endif private: bool IsMessageTypeVisible(MessageType t) const; @@ -66,4 +80,7 @@ private: bool SuppressDeprecatedWarnings = false; bool DevWarningsAsErrors = false; bool DeprecatedWarningsAsErrors = false; +#ifdef CMake_ENABLE_DEBUGGER + std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebuggerAdapter; +#endif }; diff --git a/Source/cmake.cxx b/Source/cmake.cxx index c5b467d..0a1e7ab 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -38,6 +38,10 @@ #include "cmCMakePresetsGraph.h" #include "cmCommandLineArgument.h" #include "cmCommands.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +# include "cmDebuggerPipeConnection.h" +#endif #include "cmDocumentation.h" #include "cmDocumentationEntry.h" #include "cmDuration.h" @@ -411,6 +415,11 @@ Json::Value cmake::ReportCapabilitiesJson() const obj["fileApi"] = cmFileAPI::ReportCapabilities(); obj["serverMode"] = false; obj["tls"] = static_cast<bool>(curlVersion->features & CURL_VERSION_SSL); +# ifdef CMake_ENABLE_DEBUGGER + obj["debugger"] = true; +# else + obj["debugger"] = false; +# endif return obj; } @@ -617,6 +626,13 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) }; auto ScriptLambda = [&](std::string const& path, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + // Script mode doesn't hit the usual code path in cmake::Run() that starts + // the debugger, so start it manually here instead. + if (!this->StartDebuggerIfEnabled()) { + return false; + } +#endif // Register fake project commands that hint misuse in script mode. GetProjectCommandsInScriptMode(state->GetState()); // Documented behavior of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be @@ -1233,7 +1249,52 @@ void cmake::SetArgs(const std::vector<std::string>& args) "CMAKE_COMPILE_WARNING_AS_ERROR variable.\n"; state->SetIgnoreWarningAsError(true); return true; - } } + } }, + CommandArgument{ "--debugger", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + std::cout << "Running with debugger on.\n"; + state->SetDebuggerOn(true); + return true; +#else + static_cast<void>(state); + cmSystemTools::Error( + "CMake was not built with support for --debugger"); + return false; +#endif + } }, + CommandArgument{ "--debugger-pipe", + "No path specified for --debugger-pipe", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + state->DebuggerPipe = value; + return true; +#else + static_cast<void>(value); + static_cast<void>(state); + cmSystemTools::Error("CMake was not built with support " + "for --debugger-pipe"); + return false; +#endif + } }, + CommandArgument{ + "--debugger-dap-log", "No file specified for --debugger-dap-log", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + std::string path = cmSystemTools::CollapseFullPath(value); + cmSystemTools::ConvertToUnixSlashes(path); + state->DebuggerDapLogFile = path; + return true; +#else + static_cast<void>(value); + static_cast<void>(state); + cmSystemTools::Error( + "CMake was not built with support for --debugger-dap-log"); + return false; +#endif + } }, }; #if defined(CMAKE_HAVE_VS_GENERATORS) @@ -2618,6 +2679,52 @@ void cmake::PreLoadCMakeFiles() } } +#ifdef CMake_ENABLE_DEBUGGER + +bool cmake::StartDebuggerIfEnabled() +{ + if (!this->GetDebuggerOn()) { + return true; + } + + if (DebugAdapter == nullptr) { + if (this->GetDebuggerPipe().empty()) { + std::cerr + << "Error: --debugger-pipe must be set when debugging is enabled.\n"; + return false; + } + + try { + DebugAdapter = std::make_shared<cmDebugger::cmDebuggerAdapter>( + std::make_shared<cmDebugger::cmDebuggerPipeConnection>( + this->GetDebuggerPipe()), + this->GetDebuggerDapLogFile()); + } catch (const std::runtime_error& error) { + std::cerr << "Error: Failed to create debugger adapter.\n"; + std::cerr << error.what() << "\n"; + return false; + } + Messenger->SetDebuggerAdapter(DebugAdapter); + } + + return true; +} + +void cmake::StopDebuggerIfNeeded(int exitCode) +{ + if (!this->GetDebuggerOn()) { + return; + } + + // The debug adapter may have failed to start (e.g. invalid pipe path). + if (DebugAdapter != nullptr) { + DebugAdapter->ReportExitCode(exitCode); + DebugAdapter.reset(); + } +} + +#endif + // handle a command line invocation int cmake::Run(const std::vector<std::string>& args, bool noconfigure) { @@ -2707,6 +2814,12 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) return 0; } +#ifdef CMake_ENABLE_DEBUGGER + if (!this->StartDebuggerIfEnabled()) { + return -1; + } +#endif + int ret = this->Configure(); if (ret) { #if defined(CMAKE_HAVE_VS_GENERATORS) diff --git a/Source/cmake.h b/Source/cmake.h index 955ec4f..d394a3e 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -37,6 +37,13 @@ #endif class cmConfigureLog; + +#ifdef CMake_ENABLE_DEBUGGER +namespace cmDebugger { +class cmDebuggerAdapter; +} +#endif + class cmExternalMakefileProjectGeneratorFactory; class cmFileAPI; class cmFileTimeCache; @@ -662,6 +669,23 @@ public: } #endif +#ifdef CMake_ENABLE_DEBUGGER + bool GetDebuggerOn() const { return this->DebuggerOn; } + std::string GetDebuggerPipe() const { return this->DebuggerPipe; } + std::string GetDebuggerDapLogFile() const + { + return this->DebuggerDapLogFile; + } + void SetDebuggerOn(bool b) { this->DebuggerOn = b; } + bool StartDebuggerIfEnabled(); + void StopDebuggerIfNeeded(int exitCode); + std::shared_ptr<cmDebugger::cmDebuggerAdapter> GetDebugAdapter() + const noexcept + { + return this->DebugAdapter; + } +#endif + protected: void RunCheckForUnusedVariables(); int HandleDeleteCacheVariables(const std::string& var); @@ -802,6 +826,13 @@ private: std::unique_ptr<cmMakefileProfilingData> ProfilingOutput; #endif +#ifdef CMake_ENABLE_DEBUGGER + std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebugAdapter; + bool DebuggerOn = false; + std::string DebuggerPipe; + std::string DebuggerDapLogFile; +#endif + public: static cmDocumentationEntry CMAKE_STANDARD_OPTIONS_TABLE[18]; }; diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index ad27443..ced83dc 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -392,8 +392,14 @@ int do_cmake(int ac, char const* const* av) // Always return a non-negative value. Windows tools do not always // interpret negative return values as errors. if (res != 0) { +#ifdef CMake_ENABLE_DEBUGGER + cm.StopDebuggerIfNeeded(1); +#endif return 1; } +#ifdef CMake_ENABLE_DEBUGGER + cm.StopDebuggerIfNeeded(0); +#endif return 0; } |