DAP: Add watcher feature

It is possible currently to add/remove watcher. Also it saves
expanded structure during the debugging process.

Note:
It doesn't work for CMake because of the issue #25282.

Change-Id: Ia0a48ca5cd0062617ae427b56b001ef87d5894ed
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Artem Sokolovskii
2023-09-25 16:08:16 +02:00
parent 6d92639a2d
commit 19faf1a25a
5 changed files with 188 additions and 94 deletions

View File

@@ -147,6 +147,7 @@ bool CMakeDapEngine::hasCapability(unsigned cap) const
return cap & (ReloadModuleCapability return cap & (ReloadModuleCapability
| BreakConditionCapability | BreakConditionCapability
| ShowModuleSymbolsCapability | ShowModuleSymbolsCapability
/*| AddWatcherCapability*/ // disable while the #25282 bug is not fixed
/*| RunToLineCapability*/); // disable while the #25176 bug is not fixed /*| RunToLineCapability*/); // disable while the #25176 bug is not fixed
} }

View File

@@ -114,6 +114,14 @@ void DapClient::sendStepOver(int threadId)
postRequest("next", QJsonObject{{"threadId", threadId}}); postRequest("next", QJsonObject{{"threadId", threadId}});
} }
void DapClient::evaluateVariable(const QString &expression, int frameId)
{
postRequest("evaluate",
QJsonObject{{"expression", expression},
{"frameId", frameId},
{"context", "variables"}});
}
void DapClient::stackTrace(int threadId) void DapClient::stackTrace(int threadId)
{ {
QTC_ASSERT(threadId != -1, return); QTC_ASSERT(threadId != -1, return);
@@ -214,6 +222,8 @@ void DapClient::emitSignals(const QJsonDocument &doc)
type = DapResponseType::DapThreads; type = DapResponseType::DapThreads;
} else if (command == "pause") { } else if (command == "pause") {
type = DapResponseType::Pause; type = DapResponseType::Pause;
} else if (command == "evaluate") {
type = DapResponseType::Evaluate;
} }
emit responseReady(type, ob); emit responseReady(type, ob);
return; return;

View File

@@ -51,6 +51,7 @@ enum class DapResponseType
StepOut, StepOut,
StepOver, StepOver,
Pause, Pause,
Evaluate,
Unknown Unknown
}; };
@@ -93,11 +94,14 @@ public:
void sendStepOut(int threadId); void sendStepOut(int threadId);
void sendStepOver(int threadId); void sendStepOver(int threadId);
void evaluateVariable(const QString &expression, int frameId);
void stackTrace(int threadId); void stackTrace(int threadId);
void scopes(int frameId); void scopes(int frameId);
void threads(); void threads();
void variables(int variablesReference); void variables(int variablesReference);
void setBreakpoints(const QJsonArray &breakpoints, const Utils::FilePath &fileName); void setBreakpoints(const QJsonArray &breakpoints, const Utils::FilePath &fileName);
void emitSignals(const QJsonDocument &doc); void emitSignals(const QJsonDocument &doc);
signals: signals:

View File

@@ -63,6 +63,79 @@ using namespace Utils;
namespace Debugger::Internal { namespace Debugger::Internal {
VariablesHandler::VariablesHandler(DapEngine *dapEngine)
: m_dapEngine(dapEngine)
{}
void VariablesHandler::addVariable(const QString &iname, int variablesReference)
{
VariableItem varItem = {iname, variablesReference};
bool wasEmpty = m_queue.empty();
bool inserted = false;
for (auto i = m_queue.begin(); i != m_queue.end(); ++i) {
if (i->iname > iname) {
m_queue.insert(i, varItem);
inserted = true;
break;
}
}
if (!inserted)
m_queue.push_back(varItem);
if (wasEmpty) {
startHandling();
}
}
void VariablesHandler::handleNext()
{
if (m_queue.empty())
return;
m_queue.pop_front();
startHandling();
}
void VariablesHandler::startHandling()
{
if (m_queue.empty())
return;
m_currentVarItem = m_queue.front();
WatchItem *watchItem = m_dapEngine->watchHandler()->findItem(m_currentVarItem.iname);
int variablesReference = m_currentVarItem.variablesReference;
if (variablesReference == -1 && watchItem && watchItem->iname.startsWith("watch.")
&& watchItem->iname.split('.').size() == 2) {
watchItem->removeChildren();
m_dapEngine->dapClient()->evaluateVariable(watchItem->name,
m_dapEngine->currentStackFrameId());
return;
}
if (variablesReference == -1) {
if (watchItem) {
variablesReference = watchItem->variablesReference;
} else {
handleNext();
return;
}
}
if (variablesReference == 0) {
handleNext();
return;
}
m_dapEngine->dapClient()->variables(variablesReference);
}
DapEngine::DapEngine()
: m_variablesHandler(std::make_unique<VariablesHandler>(this))
{}
void DapEngine::executeDebuggerCommand(const QString &/*command*/) void DapEngine::executeDebuggerCommand(const QString &/*command*/)
{ {
QTC_ASSERT(state() == InferiorStopOk, qCDebug(logCategory()) << state()); QTC_ASSERT(state() == InferiorStopOk, qCDebug(logCategory()) << state());
@@ -350,13 +423,6 @@ void DapEngine::refreshModules(const GdbMi &modules)
handler->endUpdateAll(); handler->endUpdateAll();
} }
void DapEngine::requestModuleSymbols(const Utils::FilePath &/*moduleName*/)
{
// DebuggerCommand cmd("listSymbols");
// cmd.arg("module", moduleName);
// runCommand(cmd);
}
void DapEngine::refreshState(const GdbMi &reportedState) void DapEngine::refreshState(const GdbMi &reportedState)
{ {
QString newState = reportedState.data(); QString newState = reportedState.data();
@@ -399,50 +465,32 @@ bool DapEngine::canHandleToolTip(const DebuggerToolTipContext &) const
return state() == InferiorStopOk; return state() == InferiorStopOk;
} }
void DapEngine::assignValueInDebugger(WatchItem *, const QString &/*expression*/, const QVariant &/*value*/)
{
//DebuggerCommand cmd("assignValue");
//cmd.arg("expression", expression);
//cmd.arg("value", value.toString());
//runCommand(cmd);
// postDirectCommand("global " + expression + ';' + expression + "=" + value.toString());
updateLocals();
}
void DapEngine::updateItem(const QString &iname) void DapEngine::updateItem(const QString &iname)
{ {
WatchItem *item = watchHandler()->findItem(iname); WatchItem *item = watchHandler()->findItem(iname);
if (item && m_currentWatchItem != item) {
m_currentWatchItem = item;
m_dapClient->variables(item->variablesReference);
}
}
void DapEngine::getVariableFromQueue() if (item && m_variablesHandler->currentItem().iname != item->iname)
{ m_variablesHandler->addVariable(item->iname, item->variablesReference);
WatchItem *item = nullptr;
while (!item && !m_variablesReferenceInameQueue.empty()) {
item = watchHandler()->findItem(m_variablesReferenceInameQueue.front());
m_variablesReferenceInameQueue.pop();
}
if (item) {
m_currentWatchItem = item;
m_dapClient->variables(item->variablesReference);
}
} }
void DapEngine::reexpandItems(const QSet<QString> &inames) void DapEngine::reexpandItems(const QSet<QString> &inames)
{ {
QList<QString> inamesVector = inames.values().toVector(); QSet<QString> expandedInames = inames;
for (auto inames : watchHandler()->watcherNames().keys())
expandedInames.insert(watchHandler()->watcherName(inames));
QList<QString> inamesVector = expandedInames.values().toVector();
inamesVector.sort(); inamesVector.sort();
for (const QString &iname : inamesVector) { for (const QString &iname : inamesVector) {
if (iname.startsWith("local.")) if (iname.startsWith("local.") || iname.startsWith("watch."))
m_variablesReferenceInameQueue.push(iname); m_variablesHandler->addVariable(iname, -1);
}
} }
getVariableFromQueue(); void DapEngine::doUpdateLocals(const UpdateParameters &params)
{
m_variablesHandler->addVariable(params.partialVariable, -1);
} }
QString DapEngine::errorMessage(QProcess::ProcessError error) const QString DapEngine::errorMessage(QProcess::ProcessError error) const
@@ -508,12 +556,6 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response
{ {
const QString command = response.value("command").toString(); const QString command = response.value("command").toString();
if (response.contains("success") && !response.value("success").toBool()) {
showMessage(QString("DAP COMMAND FAILED: %1").arg(command));
qCDebug(logCategory()) << "DAP COMMAND FAILED:" << command;
return;
}
switch (type) { switch (type) {
case DapResponseType::Initialize: case DapResponseType::Initialize:
qCDebug(logCategory()) << "initialize success"; qCDebug(logCategory()) << "initialize success";
@@ -553,9 +595,18 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response
case DapResponseType::DapThreads: case DapResponseType::DapThreads:
handleThreadsResponse(response); handleThreadsResponse(response);
break; break;
case DapResponseType::Evaluate:
handleEvaluateResponse(response);
break;
default: default:
showMessage("UNKNOWN RESPONSE:" + command); showMessage("UNKNOWN RESPONSE:" + command);
}; };
if (response.contains("success") && !response.value("success").toBool()) {
showMessage(QString("DAP COMMAND FAILED: %1").arg(command));
qCDebug(logCategory()) << "DAP COMMAND FAILED:" << command;
return;
}
} }
void DapEngine::handleStackTraceResponse(const QJsonObject &response) void DapEngine::handleStackTraceResponse(const QJsonObject &response)
@@ -581,24 +632,17 @@ void DapEngine::handleScopesResponse(const QJsonObject &response)
if (!response.value("success").toBool()) if (!response.value("success").toBool())
return; return;
watchHandler()->removeAllData();
watchHandler()->notifyUpdateStarted(); watchHandler()->notifyUpdateStarted();
m_watchItems.clear();
QJsonArray scopes = response.value("body").toObject().value("scopes").toArray(); QJsonArray scopes = response.value("body").toObject().value("scopes").toArray();
for (const QJsonValueRef &scope : scopes) { for (const QJsonValueRef &scope : scopes) {
const QString name = scope.toObject().value("name").toString(); const QString name = scope.toObject().value("name").toString();
if (name == "Registers") if (name == "Registers")
continue; continue;
m_variablesReferenceQueue.push(scope.toObject().value("variablesReference").toInt()); m_variablesHandler->addVariable("", scope.toObject().value("variablesReference").toInt());
} }
if (!m_variablesReferenceQueue.empty()) { if (m_variablesHandler->queueSize() == 0) {
m_isFirstLayer = true;
m_dapClient->variables(m_variablesReferenceQueue.front());
m_variablesReferenceQueue.pop();
} else {
watchHandler()->notifyUpdateFinished(); watchHandler()->notifyUpdateFinished();
} }
} }
@@ -625,6 +669,28 @@ void DapEngine::handleThreadsResponse(const QJsonObject &response)
} }
} }
void DapEngine::handleEvaluateResponse(const QJsonObject &response)
{
WatchItem *watchItem = watchHandler()->findItem(
m_variablesHandler->currentItem().iname);
if (watchItem
&& response.value("body").toObject().contains("variablesReference")) {
watchItem->variablesReference
= response.value("body").toObject().value("variablesReference").toInt();
watchItem->value
= response.value("body").toObject().value("result").toString();
watchItem->type = response.value("body").toObject().value("type").toString();
watchItem->wantsChildren = watchItem->variablesReference > 0;
watchItem->updateValueCache();
watchItem->update();
m_variablesHandler->addVariable(watchItem->iname,
watchItem->variablesReference);
}
m_variablesHandler->handleNext();
}
void DapEngine::handleEvent(DapEventType type, const QJsonObject &event) void DapEngine::handleEvent(DapEventType type, const QJsonObject &event)
{ {
const QString eventType = event.value("event").toString(); const QString eventType = event.value("event").toString();
@@ -740,51 +806,42 @@ void DapEngine::handleBreakpointEvent(const QJsonObject &event)
void DapEngine::refreshLocals(const QJsonArray &variables) void DapEngine::refreshLocals(const QJsonArray &variables)
{ {
WatchItem *currentItem = watchHandler()->findItem(m_variablesHandler->currentItem().iname);
if (currentItem && currentItem->iname.startsWith("watch"))
currentItem->removeChildren();
for (auto variable : variables) { for (auto variable : variables) {
WatchItem *item = new WatchItem; WatchItem *item = new WatchItem;
const QString name = variable.toObject().value("name").toString(); const QString name = variable.toObject().value("name").toString();
if (m_isFirstLayer)
item->iname = "local." + name; item->iname = (currentItem ? currentItem->iname : "local") + "."
else + name;
item->iname = m_currentWatchItem->iname + "." + name;
item->name = name; item->name = name;
item->type = variable.toObject().value("type").toString(); item->type = variable.toObject().value("type").toString();
item->value = variable.toObject().value("value").toString(); item->value = variable.toObject().value("value").toString();
item->address = variable.toObject().value("address").toInt(); item->address = variable.toObject().value("address").toInt();
item->type = variable.toObject().value("type").toString(); item->type = variable.toObject().value("type").toString();
item->variablesReference = variable.toObject().value("variablesReference").toInt();
item->wantsChildren = item->variablesReference > 0;
const int variablesReference = variable.toObject().value("variablesReference").toInt(); qCDebug(logCategory()) << "variable" << item->iname << item->variablesReference;
item->variablesReference = variablesReference; if (currentItem)
if (variablesReference > 0) currentItem->appendChild(item);
item->wantsChildren = true;
qCDebug(logCategory()) << "variable" << item->iname << variablesReference;
if (m_isFirstLayer)
m_watchItems.append(item);
else else
m_currentWatchItem->appendChild(item);
}
if (m_isFirstLayer) {
if (m_variablesReferenceQueue.empty()) {
for (auto item : m_watchItems)
watchHandler()->insertItem(item); watchHandler()->insertItem(item);
m_isFirstLayer = false; }
QModelIndex idx = watchHandler()->model()->indexForItem(currentItem);
if (currentItem && idx.isValid() && idx.data(LocalsExpandedRole).toBool()) {
emit watchHandler()->model()->inameIsExpanded(currentItem->iname);
emit watchHandler()->model()->itemIsExpanded(idx);
}
if (m_variablesHandler->queueSize() == 1 && currentItem == nullptr) {
watchHandler()->notifyUpdateFinished(); watchHandler()->notifyUpdateFinished();
} else {
m_dapClient->variables(m_variablesReferenceQueue.front());
m_variablesReferenceQueue.pop();
}
return;
} }
if (m_currentWatchItem) { m_variablesHandler->handleNext();
emit watchHandler()->model()->inameIsExpanded(m_currentWatchItem->iname);
emit watchHandler()->model()->itemIsExpanded(
watchHandler()->model()->indexForItem(m_currentWatchItem));
}
getVariableFromQueue();
} }
void DapEngine::refreshStack(const QJsonArray &stackFrames) void DapEngine::refreshStack(const QJsonArray &stackFrames)

View File

@@ -20,6 +20,30 @@ class IDataProvider;
class GdbMi; class GdbMi;
enum class DapResponseType; enum class DapResponseType;
enum class DapEventType; enum class DapEventType;
class DapEngine;
class VariablesHandler {
public:
VariablesHandler(DapEngine *dapEngine);
struct VariableItem {
QString iname;
int variablesReference;
};
void addVariable(const QString &iname, int variablesReference);
void handleNext();
VariableItem currentItem() const { return m_currentVarItem; }
int queueSize() const { return m_queue.size(); }
private:
void startHandling();
DapEngine *m_dapEngine;
std::list<VariableItem> m_queue;
VariableItem m_currentVarItem;
};
/* /*
* A debugger engine for the debugger adapter protocol. * A debugger engine for the debugger adapter protocol.
@@ -27,9 +51,12 @@ enum class DapEventType;
class DapEngine : public DebuggerEngine class DapEngine : public DebuggerEngine
{ {
public: public:
DapEngine() = default; DapEngine();
~DapEngine() override = default; ~DapEngine() override = default;
DapClient *dapClient() const { return m_dapClient; }
int currentStackFrameId() const { return m_currentStackFrameId; }
protected: protected:
void executeStepIn(bool) override; void executeStepIn(bool) override;
void executeStepOut() override; void executeStepOut() override;
@@ -55,13 +82,10 @@ protected:
void updateBreakpoint(const Breakpoint &bp) override; void updateBreakpoint(const Breakpoint &bp) override;
void removeBreakpoint(const Breakpoint &bp) override; void removeBreakpoint(const Breakpoint &bp) override;
void assignValueInDebugger(WatchItem *item,
const QString &expr, const QVariant &value) override;
void executeDebuggerCommand(const QString &command) override; void executeDebuggerCommand(const QString &command) override;
void loadSymbols(const Utils::FilePath &moduleName) override; void loadSymbols(const Utils::FilePath &moduleName) override;
void loadAllSymbols() override; void loadAllSymbols() override;
void requestModuleSymbols(const Utils::FilePath &moduleName) override;
void reloadModules() override; void reloadModules() override;
void reloadRegisters() override {} void reloadRegisters() override {}
void reloadSourceFiles() override {} void reloadSourceFiles() override {}
@@ -70,6 +94,7 @@ protected:
bool supportsThreads() const { return true; } bool supportsThreads() const { return true; }
void updateItem(const QString &iname) override; void updateItem(const QString &iname) override;
void reexpandItems(const QSet<QString> &inames) override; void reexpandItems(const QSet<QString> &inames) override;
void doUpdateLocals(const UpdateParameters &params) override;
void getVariableFromQueue(); void getVariableFromQueue();
void runCommand(const DebuggerCommand &cmd) override; void runCommand(const DebuggerCommand &cmd) override;
@@ -102,6 +127,7 @@ protected:
void handleStackTraceResponse(const QJsonObject &response); void handleStackTraceResponse(const QJsonObject &response);
void handleScopesResponse(const QJsonObject &response); void handleScopesResponse(const QJsonObject &response);
void handleThreadsResponse(const QJsonObject &response); void handleThreadsResponse(const QJsonObject &response);
void handleEvaluateResponse(const QJsonObject &response);
void handleEvent(DapEventType type, const QJsonObject &event); void handleEvent(DapEventType type, const QJsonObject &event);
void handleBreakpointEvent(const QJsonObject &event); void handleBreakpointEvent(const QJsonObject &event);
@@ -118,11 +144,7 @@ protected:
int m_currentThreadId = -1; int m_currentThreadId = -1;
int m_currentStackFrameId = -1; int m_currentStackFrameId = -1;
bool m_isFirstLayer = true; std::unique_ptr<VariablesHandler> m_variablesHandler;
std::queue<int> m_variablesReferenceQueue;
std::queue<QString> m_variablesReferenceInameQueue;
WatchItem *m_currentWatchItem = nullptr;
QList<WatchItem *> m_watchItems;
virtual const QLoggingCategory &logCategory() virtual const QLoggingCategory &logCategory()
{ {