Files
qt-creator/src/plugins/debugger/breakhandler.cpp
Marcus Tillmanns 20904b2411 Debugger: Dont show invalid hit count
The CDB Engine cannot report the hit count.
Instead of showing "0", we don't show the hit count
at all.

Task-number: QTCREATORBUG-20069
Change-Id: Ie8b5478ba85c7255b13070cb488599a2b2ca4537
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
2024-02-01 07:14:43 +00:00

2886 lines
101 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "breakhandler.h"
#include "debuggeractions.h"
#include "debuggercore.h"
#include "debuggerengine.h"
#include "debuggericons.h"
#include "debuggerinternalconstants.h"
#include "debuggertr.h"
#include "disassembleragent.h"
#include "enginemanager.h"
#include "simplifytype.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/coreplugin.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <coreplugin/session.h>
#include <texteditor/textmark.h>
#include <texteditor/texteditor.h>
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
#include <utils/checkablemessagebox.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#if USE_BREAK_MODEL_TEST
#include <modeltest.h>
#endif
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDir>
#include <QFormLayout>
#include <QGroupBox>
#include <QMenu>
#include <QSpinBox>
#include <QStyledItemDelegate>
#include <QTimer>
#include <QTimerEvent>
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
namespace Debugger {
namespace Internal {
static BreakpointManager *theBreakpointManager = nullptr;
//
// BreakpointMarker
//
// The red blob on the left side in the cpp editor.
class BreakpointMarker : public TextEditor::TextMark
{
public:
BreakpointMarker(const Breakpoint &bp, const FilePath &fileName, int lineNumber)
: TextMark(fileName,
lineNumber,
{Tr::tr("Breakpoint"), Constants::TEXT_MARK_CATEGORY_BREAKPOINT})
, m_bp(bp)
{
setColor(Theme::Debugger_Breakpoint_TextMarkColor);
setDefaultToolTip(Tr::tr("Breakpoint"));
setPriority(TextEditor::TextMark::NormalPriority);
setIconProvider([bp] { return bp->icon(); });
setToolTipProvider([bp] { return bp->toolTip(); });
}
void updateLineNumber(int lineNumber) final
{
TextMark::updateLineNumber(lineNumber);
QTC_ASSERT(m_bp, return);
m_bp->setTextPosition({lineNumber, -1});
if (GlobalBreakpoint gbp = m_bp->globalBreakpoint())
gbp->m_params.textPosition.line = lineNumber;
}
void updateFilePath(const FilePath &fileName) final
{
TextMark::updateFilePath(fileName);
QTC_ASSERT(m_bp, return);
m_bp->setFileName(fileName);
if (GlobalBreakpoint gbp = m_bp->globalBreakpoint())
gbp->m_params.fileName = fileName;
}
bool isDraggable() const final { return true; }
void dragToLine(int line) final
{
QTC_ASSERT(m_bp, return);
GlobalBreakpoint gbp = m_bp->globalBreakpoint();
if (!gbp)
return;
BreakpointParameters params = gbp->m_params;
params.textPosition.line = line;
gbp->deleteBreakpoint();
BreakpointManager::createBreakpoint(params);
}
bool isClickable() const final { return true; }
void clicked() final
{
QTC_ASSERT(m_bp, return);
m_bp->deleteGlobalOrThisBreakpoint();
}
public:
Breakpoint m_bp;
};
// The red blob on the left side in the cpp editor.
class GlobalBreakpointMarker : public TextEditor::TextMark
{
public:
GlobalBreakpointMarker(GlobalBreakpoint gbp, const FilePath &fileName, int lineNumber)
: TextMark(fileName,
lineNumber,
{Tr::tr("Breakpoint"), Constants::TEXT_MARK_CATEGORY_BREAKPOINT})
, m_gbp(gbp)
{
setDefaultToolTip(Tr::tr("Breakpoint"));
setPriority(TextEditor::TextMark::NormalPriority);
setIconProvider([this] { return m_gbp->icon(); });
setToolTipProvider([this] { return m_gbp->toolTip(); });
}
void removedFromEditor() final
{
QTC_ASSERT(m_gbp, return);
m_gbp->removeBreakpointFromModel();
}
void updateLineNumber(int lineNumber) final
{
TextMark::updateLineNumber(lineNumber);
QTC_ASSERT(m_gbp, return);
// Ignore updates to the "real" line number while the debugger is
// running, as this can be triggered by moving the breakpoint to
// the next line that generated code.
m_gbp->updateLineNumber(lineNumber);
}
void updateFilePath(const FilePath &fileName) final
{
TextMark::updateFilePath(fileName);
QTC_ASSERT(m_gbp, return);
m_gbp->updateFileName(fileName);
}
bool isDraggable() const final { return true; }
void dragToLine(int line) final
{
TextMark::move(line);
QTC_ASSERT(m_gbp, return);
QTC_ASSERT(BreakpointManager::globalBreakpoints().contains(m_gbp), return);
m_gbp->updateLineNumber(line);
}
bool isClickable() const final { return true; }
void clicked() final
{
QTC_ASSERT(m_gbp, return);
m_gbp->removeBreakpointFromModel();
}
public:
GlobalBreakpoint m_gbp;
};
static QString stateToString(BreakpointState state)
{
switch (state) {
case BreakpointNew:
return Tr::tr("New");
case BreakpointInsertionRequested:
return Tr::tr("Insertion requested");
case BreakpointInsertionProceeding:
return Tr::tr("Insertion proceeding");
case BreakpointUpdateRequested:
return Tr::tr("Change requested");
case BreakpointUpdateProceeding:
return Tr::tr("Change proceeding");
case BreakpointInserted:
return Tr::tr("Breakpoint inserted");
case BreakpointRemoveRequested:
return Tr::tr("Removal requested");
case BreakpointRemoveProceeding:
return Tr::tr("Removal proceeding");
case BreakpointDead:
return Tr::tr("Dead");
default:
break;
}
//: Invalid breakpoint state.
return Tr::tr("<invalid state>");
}
static QString msgBreakpointAtSpecialFunc(const QString &func)
{
return Tr::tr("Breakpoint at \"%1\"").arg(func);
}
static QString typeToString(BreakpointType type)
{
switch (type) {
case BreakpointByFileAndLine:
return Tr::tr("Breakpoint by File and Line");
case BreakpointByFunction:
return Tr::tr("Breakpoint by Function");
case BreakpointByAddress:
return Tr::tr("Breakpoint by Address");
case BreakpointAtThrow:
return msgBreakpointAtSpecialFunc("throw");
case BreakpointAtCatch:
return msgBreakpointAtSpecialFunc("catch");
case BreakpointAtFork:
return msgBreakpointAtSpecialFunc("fork");
case BreakpointAtExec:
return msgBreakpointAtSpecialFunc("exec");
//case BreakpointAtVFork:
// return msgBreakpointAtSpecialFunc("vfork");
case BreakpointAtSysCall:
return msgBreakpointAtSpecialFunc("syscall");
case BreakpointAtMain:
return Tr::tr("Breakpoint at Function \"main()\"");
case WatchpointAtAddress:
return Tr::tr("Watchpoint at Address");
case WatchpointAtExpression:
return Tr::tr("Watchpoint at Expression");
case BreakpointOnQmlSignalEmit:
return Tr::tr("Breakpoint on QML Signal Emit");
case BreakpointAtJavaScriptThrow:
return Tr::tr("Breakpoint at JavaScript throw");
case UnknownBreakpointType:
case LastBreakpointType:
break;
}
return Tr::tr("Unknown Breakpoint Type");
}
class LeftElideDelegate : public QStyledItemDelegate
{
public:
LeftElideDelegate() = default;
void paint(QPainter *pain, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem opt = option;
opt.textElideMode = Qt::ElideLeft;
QStyledItemDelegate::paint(pain, opt, index);
}
};
class SmallTextEdit : public QTextEdit
{
public:
explicit SmallTextEdit(QWidget *parent) : QTextEdit(parent) {}
QSize sizeHint() const override { return QSize(QTextEdit::sizeHint().width(), 100); }
QSize minimumSizeHint() const override { return sizeHint(); }
};
///////////////////////////////////////////////////////////////////////
//
// BreakpointDialog: Show a dialog for editing breakpoints. Shows controls
// for the file-and-line, function and address parameters depending on the
// breakpoint type. The controls not applicable to the current type
// (say function name for file-and-line) are disabled and cleared out.
// However,the values are saved and restored once the respective mode
// is again chosen, which is done using m_savedParameters and
// setters/getters taking the parts mask enumeration parameter.
//
///////////////////////////////////////////////////////////////////////
class BreakpointDialog : public QDialog
{
public:
explicit BreakpointDialog(unsigned int enabledParts, QWidget *parent = nullptr);
bool showDialog(BreakpointParameters *data, BreakpointParts *parts);
void setParameters(const BreakpointParameters &data);
BreakpointParameters parameters() const;
void typeChanged(int index);
private:
void setPartsEnabled(unsigned partsMask);
void clearOtherParts(unsigned partsMask);
void getParts(unsigned partsMask, BreakpointParameters *data) const;
void setParts(unsigned partsMask, const BreakpointParameters &data);
void setType(BreakpointType type);
BreakpointType type() const;
unsigned m_enabledParts;
BreakpointParameters m_savedParameters;
BreakpointType m_previousType;
bool m_firstTypeChange;
QLabel *m_labelType;
QComboBox *m_comboBoxType;
QLabel *m_labelFileName;
Utils::PathChooser *m_pathChooserFileName;
QLabel *m_labelLineNumber;
QLineEdit *m_lineEditLineNumber;
QCheckBox *m_checkBoxPropagate;
QLabel *m_labelEnabled;
QCheckBox *m_checkBoxEnabled;
QLabel *m_labelAddress;
QLineEdit *m_lineEditAddress;
QLabel *m_labelExpression;
QLineEdit *m_lineEditExpression;
QLabel *m_labelFunction;
QLineEdit *m_lineEditFunction;
QLabel *m_labelTracepoint;
QCheckBox *m_checkBoxTracepoint;
QLabel *m_labelOneShot;
QCheckBox *m_checkBoxOneShot;
QLabel *m_labelUseFullPath;
QLabel *m_labelModule;
QLineEdit *m_lineEditModule;
QLabel *m_labelCommands;
QTextEdit *m_textEditCommands;
QComboBox *m_comboBoxPathUsage;
QLabel *m_labelMessage;
QLineEdit *m_lineEditMessage;
QLabel *m_labelCondition;
QLineEdit *m_lineEditCondition;
QLabel *m_labelIgnoreCount;
QSpinBox *m_spinBoxIgnoreCount;
QLabel *m_labelThreadSpec;
QLineEdit *m_lineEditThreadSpec;
QDialogButtonBox *m_buttonBox;
};
BreakpointDialog::BreakpointDialog(unsigned int enabledParts, QWidget *parent)
: QDialog(parent), m_enabledParts(enabledParts), m_previousType(UnknownBreakpointType),
m_firstTypeChange(true)
{
setWindowTitle(Tr::tr("Edit Breakpoint Properties"));
auto groupBoxBasic = new QGroupBox(Tr::tr("Basic"), this);
// Match BreakpointType (omitting unknown type).
const QStringList types = {
Tr::tr("File Name and Line Number"),
Tr::tr("Function Name"),
Tr::tr("Break on Memory Address"),
Tr::tr("Break When C++ Exception Is Thrown"),
Tr::tr("Break When C++ Exception Is Caught"),
Tr::tr("Break When Function \"main\" Starts"),
Tr::tr("Break When a New Process Is Forked"),
Tr::tr("Break When a New Process Is Executed"),
Tr::tr("Break When a System Call Is Executed"),
Tr::tr("Break on Data Access at Fixed Address"),
Tr::tr("Break on Data Access at Address Given by Expression"),
Tr::tr("Break on QML Signal Emit"),
Tr::tr("Break When JavaScript Exception Is Thrown")
};
// We don't list UnknownBreakpointType, so 1 less:
QTC_CHECK(types.size() + 1 == LastBreakpointType);
m_comboBoxType = new QComboBox(groupBoxBasic);
m_comboBoxType->setMaxVisibleItems(20);
m_comboBoxType->addItems(types);
m_labelType = new QLabel(Tr::tr("Breakpoint &type:"), groupBoxBasic);
m_labelType->setBuddy(m_comboBoxType);
m_pathChooserFileName = new PathChooser(groupBoxBasic);
m_pathChooserFileName->setHistoryCompleter("Debugger.Breakpoint.File.History");
m_pathChooserFileName->setExpectedKind(PathChooser::File);
m_labelFileName = new QLabel(Tr::tr("&File name:"), groupBoxBasic);
m_labelFileName->setBuddy(m_pathChooserFileName);
m_lineEditLineNumber = new QLineEdit(groupBoxBasic);
m_labelLineNumber = new QLabel(Tr::tr("&Line number:"), groupBoxBasic);
m_labelLineNumber->setBuddy(m_lineEditLineNumber);
m_checkBoxEnabled = new QCheckBox(groupBoxBasic);
m_labelEnabled = new QLabel(Tr::tr("&Enabled:"), groupBoxBasic);
m_labelEnabled->setBuddy(m_checkBoxEnabled);
m_lineEditAddress = new QLineEdit(groupBoxBasic);
m_labelAddress = new QLabel(Tr::tr("&Address:"), groupBoxBasic);
m_labelAddress->setBuddy(m_lineEditAddress);
m_lineEditExpression = new QLineEdit(groupBoxBasic);
m_labelExpression = new QLabel(Tr::tr("&Expression:"), groupBoxBasic);
m_labelExpression->setBuddy(m_lineEditExpression);
m_lineEditFunction = new QLineEdit(groupBoxBasic);
m_labelFunction = new QLabel(Tr::tr("Fun&ction:"), groupBoxBasic);
m_labelFunction->setBuddy(m_lineEditFunction);
auto groupBoxAdvanced = new QGroupBox(Tr::tr("Advanced"), this);
m_checkBoxTracepoint = new QCheckBox(groupBoxAdvanced);
m_labelTracepoint = new QLabel(Tr::tr("T&racepoint only:"), groupBoxAdvanced);
m_labelTracepoint->setBuddy(m_checkBoxTracepoint);
m_checkBoxOneShot = new QCheckBox(groupBoxAdvanced);
m_labelOneShot = new QLabel(Tr::tr("&One shot only:"), groupBoxAdvanced);
m_labelOneShot->setBuddy(m_checkBoxOneShot);
const QString pathToolTip =
Tr::tr("<p>Determines how the path is specified "
"when setting breakpoints:</p><ul>"
"<li><i>Use Engine Default</i>: Preferred setting of the "
"debugger engine.</li>"
"<li><i>Use Full Path</i>: Pass full path, avoiding ambiguities "
"should files of the same name exist in several modules. "
"This is the engine default for CDB and LLDB.</li>"
"<li><i>Use File Name</i>: Pass the file name only. This is "
"useful when using a source tree whose location does "
"not match the one used when building the modules. "
"It is the engine default for GDB as using full paths can "
"be slow with this engine.</li></ul>");
m_comboBoxPathUsage = new QComboBox(groupBoxAdvanced);
m_comboBoxPathUsage->addItem(Tr::tr("Use Engine Default"));
m_comboBoxPathUsage->addItem(Tr::tr("Use Full Path"));
m_comboBoxPathUsage->addItem(Tr::tr("Use File Name"));
m_comboBoxPathUsage->setToolTip(pathToolTip);
m_labelUseFullPath = new QLabel(Tr::tr("Pat&h:"), groupBoxAdvanced);
m_labelUseFullPath->setBuddy(m_comboBoxPathUsage);
m_labelUseFullPath->setToolTip(pathToolTip);
const QString moduleToolTip =
"<p>" + Tr::tr("Specifying the module (base name of the library or executable) "
"for function or file type breakpoints can significantly speed up "
"debugger startup times (CDB, LLDB).") + "</p>";
m_lineEditModule = new QLineEdit(groupBoxAdvanced);
m_lineEditModule->setToolTip(moduleToolTip);
m_labelModule = new QLabel(Tr::tr("&Module:"), groupBoxAdvanced);
m_labelModule->setBuddy(m_lineEditModule);
m_labelModule->setToolTip(moduleToolTip);
const QString commandsToolTip =
"<p>" + Tr::tr("Debugger commands to be executed when the breakpoint is hit. "
"This feature is only available for GDB.") + "</p>";
m_textEditCommands = new SmallTextEdit(groupBoxAdvanced);
m_textEditCommands->setToolTip(commandsToolTip);
m_labelCommands = new QLabel(Tr::tr("&Commands:"), groupBoxAdvanced);
m_labelCommands->setBuddy(m_textEditCommands);
m_labelCommands->setToolTip(commandsToolTip);
m_lineEditMessage = new QLineEdit(groupBoxAdvanced);
m_labelMessage = new QLabel(Tr::tr("&Message:"), groupBoxAdvanced);
m_labelMessage->setBuddy(m_lineEditMessage);
m_lineEditCondition = new QLineEdit(groupBoxAdvanced);
m_labelCondition = new QLabel(Tr::tr("C&ondition:"), groupBoxAdvanced);
m_labelCondition->setBuddy(m_lineEditCondition);
m_spinBoxIgnoreCount = new QSpinBox(groupBoxAdvanced);
m_spinBoxIgnoreCount->setMinimum(0);
m_spinBoxIgnoreCount->setMaximum(2147483647);
m_labelIgnoreCount = new QLabel(Tr::tr("&Ignore count:"), groupBoxAdvanced);
m_labelIgnoreCount->setBuddy(m_spinBoxIgnoreCount);
m_lineEditThreadSpec = new QLineEdit(groupBoxAdvanced);
m_labelThreadSpec = new QLabel(Tr::tr("&Thread specification:"), groupBoxAdvanced);
m_labelThreadSpec->setBuddy(m_lineEditThreadSpec);
m_checkBoxPropagate = new QCheckBox(Tr::tr("Propagate Change to Preset Breakpoint"), this);
m_checkBoxPropagate->setCheckable(true);
m_checkBoxPropagate->setChecked(true);
m_checkBoxPropagate->setVisible(false); // FIXME: Make it work.
m_buttonBox = new QDialogButtonBox(this);
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
auto basicLayout = new QFormLayout(groupBoxBasic);
basicLayout->addRow(m_labelType, m_comboBoxType);
basicLayout->addRow(m_labelFileName, m_pathChooserFileName);
basicLayout->addRow(m_labelLineNumber, m_lineEditLineNumber);
basicLayout->addRow(m_labelEnabled, m_checkBoxEnabled);
basicLayout->addRow(m_labelAddress, m_lineEditAddress);
basicLayout->addRow(m_labelExpression, m_lineEditExpression);
basicLayout->addRow(m_labelFunction, m_lineEditFunction);
basicLayout->addRow(m_labelOneShot, m_checkBoxOneShot);
auto advancedLeftLayout = new QFormLayout();
advancedLeftLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
advancedLeftLayout->addRow(m_labelCondition, m_lineEditCondition);
advancedLeftLayout->addRow(m_labelIgnoreCount, m_spinBoxIgnoreCount);
advancedLeftLayout->addRow(m_labelThreadSpec, m_lineEditThreadSpec);
advancedLeftLayout->addRow(m_labelUseFullPath, m_comboBoxPathUsage);
advancedLeftLayout->addRow(m_labelModule, m_lineEditModule);
auto advancedRightLayout = new QFormLayout();
advancedRightLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
advancedRightLayout->addRow(m_labelCommands, m_textEditCommands);
advancedRightLayout->addRow(m_labelTracepoint, m_checkBoxTracepoint);
advancedRightLayout->addRow(m_labelMessage, m_lineEditMessage);
auto horizontalLayout = new QHBoxLayout(groupBoxAdvanced);
horizontalLayout->addLayout(advancedLeftLayout);
horizontalLayout->addSpacing(15);
horizontalLayout->addLayout(advancedRightLayout);
auto verticalLayout = new QVBoxLayout(this);
verticalLayout->addWidget(groupBoxBasic);
verticalLayout->addSpacing(10);
verticalLayout->addWidget(groupBoxAdvanced);
verticalLayout->addSpacing(10);
verticalLayout->addWidget(m_checkBoxPropagate);
verticalLayout->addSpacing(10);
verticalLayout->addWidget(m_buttonBox);
verticalLayout->setStretchFactor(groupBoxAdvanced, 10);
connect(m_comboBoxType, &QComboBox::activated, this, &BreakpointDialog::typeChanged);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void BreakpointDialog::setType(BreakpointType type)
{
const int comboIndex = type - 1; // Skip UnknownType.
if (comboIndex != m_comboBoxType->currentIndex() || m_firstTypeChange) {
m_comboBoxType->setCurrentIndex(comboIndex);
typeChanged(comboIndex);
m_firstTypeChange = false;
}
}
BreakpointType BreakpointDialog::type() const
{
const int type = m_comboBoxType->currentIndex() + 1; // Skip unknown type.
return static_cast<BreakpointType>(type);
}
void BreakpointDialog::setParameters(const BreakpointParameters &data)
{
m_savedParameters = data;
setType(data.type);
setParts(AllParts, data);
}
BreakpointParameters BreakpointDialog::parameters() const
{
BreakpointParameters data(type());
getParts(AllParts, &data);
return data;
}
void BreakpointDialog::setPartsEnabled(unsigned partsMask)
{
partsMask &= m_enabledParts;
m_labelFileName->setEnabled(partsMask & FileAndLinePart);
m_pathChooserFileName->setEnabled(partsMask & FileAndLinePart);
m_labelLineNumber->setEnabled(partsMask & FileAndLinePart);
m_lineEditLineNumber->setEnabled(partsMask & FileAndLinePart);
m_labelUseFullPath->setEnabled(partsMask & FileAndLinePart);
m_comboBoxPathUsage->setEnabled(partsMask & FileAndLinePart);
m_labelFunction->setEnabled(partsMask & FunctionPart);
m_lineEditFunction->setEnabled(partsMask & FunctionPart);
m_labelOneShot->setEnabled(partsMask & OneShotPart);
m_checkBoxOneShot->setEnabled(partsMask & OneShotPart);
m_labelAddress->setEnabled(partsMask & AddressPart);
m_lineEditAddress->setEnabled(partsMask & AddressPart);
m_labelExpression->setEnabled(partsMask & ExpressionPart);
m_lineEditExpression->setEnabled(partsMask & ExpressionPart);
m_labelCondition->setEnabled(partsMask & ConditionPart);
m_lineEditCondition->setEnabled(partsMask & ConditionPart);
m_labelIgnoreCount->setEnabled(partsMask & IgnoreCountPart);
m_spinBoxIgnoreCount->setEnabled(partsMask & IgnoreCountPart);
m_labelThreadSpec->setEnabled(partsMask & ThreadSpecPart);
m_lineEditThreadSpec->setEnabled(partsMask & ThreadSpecPart);
m_labelModule->setEnabled(partsMask & ModulePart);
m_lineEditModule->setEnabled(partsMask & ModulePart);
m_labelTracepoint->setEnabled(partsMask & TracePointPart);
m_checkBoxTracepoint->setEnabled(partsMask & TracePointPart);
m_labelCommands->setEnabled(partsMask & CommandPart);
m_textEditCommands->setEnabled(partsMask & CommandPart);
m_labelMessage->setEnabled(partsMask & TracePointPart);
m_lineEditMessage->setEnabled(partsMask & TracePointPart);
}
void BreakpointDialog::clearOtherParts(unsigned partsMask)
{
const unsigned invertedPartsMask = ~partsMask;
if (invertedPartsMask & FileAndLinePart) {
m_pathChooserFileName->setFilePath({});
m_lineEditLineNumber->clear();
m_comboBoxPathUsage->setCurrentIndex(BreakpointPathUsageEngineDefault);
}
if (invertedPartsMask & FunctionPart)
m_lineEditFunction->clear();
if (invertedPartsMask & AddressPart)
m_lineEditAddress->clear();
if (invertedPartsMask & ExpressionPart)
m_lineEditExpression->clear();
if (invertedPartsMask & ConditionPart)
m_lineEditCondition->clear();
if (invertedPartsMask & IgnoreCountPart)
m_spinBoxIgnoreCount->clear();
if (invertedPartsMask & ThreadSpecPart)
m_lineEditThreadSpec->clear();
if (invertedPartsMask & ModulePart)
m_lineEditModule->clear();
if (partsMask & OneShotPart)
m_checkBoxOneShot->setChecked(false);
if (invertedPartsMask & CommandPart)
m_textEditCommands->clear();
if (invertedPartsMask & TracePointPart) {
m_checkBoxTracepoint->setChecked(false);
m_lineEditMessage->clear();
}
}
void BreakpointDialog::getParts(unsigned partsMask, BreakpointParameters *data) const
{
data->enabled = m_checkBoxEnabled->isChecked();
if (partsMask & FileAndLinePart) {
data->textPosition.line = m_lineEditLineNumber->text().toInt();
data->pathUsage = static_cast<BreakpointPathUsage>(m_comboBoxPathUsage->currentIndex());
data->fileName = m_pathChooserFileName->filePath();
}
if (partsMask & FunctionPart)
data->functionName = m_lineEditFunction->text();
if (partsMask & AddressPart)
data->address = m_lineEditAddress->text().toULongLong(nullptr, 0);
if (partsMask & ExpressionPart)
data->expression = m_lineEditExpression->text();
if (partsMask & ConditionPart)
data->condition = m_lineEditCondition->text();
if (partsMask & IgnoreCountPart)
data->ignoreCount = m_spinBoxIgnoreCount->text().toInt();
if (partsMask & ThreadSpecPart)
data->threadSpec =
BreakHandler::threadSpecFromDisplay(m_lineEditThreadSpec->text());
if (partsMask & ModulePart)
data->module = m_lineEditModule->text();
if (partsMask & OneShotPart)
data->oneShot = m_checkBoxOneShot->isChecked();
if (partsMask & CommandPart)
data->command = m_textEditCommands->toPlainText().trimmed();
if (partsMask & TracePointPart) {
data->tracepoint = m_checkBoxTracepoint->isChecked();
data->message = m_lineEditMessage->text();
}
}
void BreakpointDialog::setParts(unsigned mask, const BreakpointParameters &data)
{
m_checkBoxEnabled->setChecked(data.enabled);
m_comboBoxPathUsage->setCurrentIndex(data.pathUsage);
m_lineEditMessage->setText(data.message);
if (mask & FileAndLinePart) {
m_pathChooserFileName->setFilePath(data.fileName);
m_lineEditLineNumber->setText(QString::number(data.textPosition.line));
}
if (mask & FunctionPart)
m_lineEditFunction->setText(data.functionName);
if (mask & AddressPart) {
if (data.address) {
m_lineEditAddress->setText(QString("0x%1").arg(data.address, 0, 16));
} else {
m_lineEditAddress->clear();
}
}
if (mask & ExpressionPart) {
if (!data.expression.isEmpty())
m_lineEditExpression->setText(data.expression);
else
m_lineEditExpression->clear();
}
if (mask & ConditionPart)
m_lineEditCondition->setText(data.condition);
if (mask & IgnoreCountPart)
m_spinBoxIgnoreCount->setValue(data.ignoreCount);
if (mask & ThreadSpecPart)
m_lineEditThreadSpec->
setText(BreakHandler::displayFromThreadSpec(data.threadSpec));
if (mask & ModulePart)
m_lineEditModule->setText(data.module);
if (mask & OneShotPart)
m_checkBoxOneShot->setChecked(data.oneShot);
if (mask & TracePointPart)
m_checkBoxTracepoint->setChecked(data.tracepoint);
if (mask & CommandPart)
m_textEditCommands->setPlainText(data.command);
}
void BreakpointDialog::typeChanged(int)
{
BreakpointType previousType = m_previousType;
const BreakpointType newType = type();
m_previousType = newType;
// Save current state.
switch (previousType) {
case UnknownBreakpointType:
case LastBreakpointType:
break;
case BreakpointByFileAndLine:
getParts(FileAndLinePart|ModulePart|AllConditionParts|TracePointPart|CommandPart, &m_savedParameters);
break;
case BreakpointByFunction:
getParts(FunctionPart|ModulePart|AllConditionParts|TracePointPart|CommandPart, &m_savedParameters);
break;
case BreakpointAtThrow:
case BreakpointAtCatch:
case BreakpointAtMain:
case BreakpointAtFork:
case BreakpointAtExec:
//case BreakpointAtVFork:
case BreakpointAtSysCall:
case BreakpointAtJavaScriptThrow:
break;
case BreakpointByAddress:
case WatchpointAtAddress:
getParts(AddressPart|AllConditionParts|TracePointPart|CommandPart, &m_savedParameters);
break;
case WatchpointAtExpression:
getParts(ExpressionPart|AllConditionParts|TracePointPart|CommandPart, &m_savedParameters);
break;
case BreakpointOnQmlSignalEmit:
getParts(FunctionPart, &m_savedParameters);
}
// Enable and set up new state from saved values.
switch (newType) {
case UnknownBreakpointType:
case LastBreakpointType:
break;
case BreakpointByFileAndLine:
setParts(FileAndLinePart|AllConditionParts|ModulePart|TracePointPart|CommandPart, m_savedParameters);
setPartsEnabled(FileAndLinePart|AllConditionParts|ModulePart|TracePointPart|CommandPart);
clearOtherParts(FileAndLinePart|AllConditionParts|ModulePart|TracePointPart|CommandPart);
break;
case BreakpointByFunction:
setParts(FunctionPart|AllConditionParts|ModulePart|TracePointPart|CommandPart, m_savedParameters);
setPartsEnabled(FunctionPart|AllConditionParts|ModulePart|TracePointPart|CommandPart);
clearOtherParts(FunctionPart|AllConditionParts|ModulePart|TracePointPart|CommandPart);
break;
case BreakpointAtThrow:
case BreakpointAtCatch:
case BreakpointAtFork:
case BreakpointAtExec:
//case BreakpointAtVFork:
case BreakpointAtSysCall:
clearOtherParts(AllConditionParts|ModulePart|TracePointPart|CommandPart);
setPartsEnabled(AllConditionParts|TracePointPart|CommandPart);
break;
case BreakpointAtJavaScriptThrow:
clearOtherParts(AllParts);
setPartsEnabled(0);
break;
case BreakpointAtMain:
m_lineEditFunction->setText("main"); // Just for display
clearOtherParts(0);
setPartsEnabled(0);
break;
case BreakpointByAddress:
case WatchpointAtAddress:
setParts(AddressPart|AllConditionParts|TracePointPart|CommandPart, m_savedParameters);
setPartsEnabled(AddressPart|AllConditionParts|TracePointPart|CommandPart);
clearOtherParts(AddressPart|AllConditionParts|TracePointPart|CommandPart);
break;
case WatchpointAtExpression:
setParts(ExpressionPart|AllConditionParts|TracePointPart|CommandPart, m_savedParameters);
setPartsEnabled(ExpressionPart|AllConditionParts|TracePointPart|CommandPart);
clearOtherParts(ExpressionPart|AllConditionParts|TracePointPart|CommandPart);
break;
case BreakpointOnQmlSignalEmit:
setParts(FunctionPart, m_savedParameters);
setPartsEnabled(FunctionPart);
clearOtherParts(FunctionPart);
}
}
bool BreakpointDialog::showDialog(BreakpointParameters *data,
BreakpointParts *parts)
{
setParameters(*data);
if (exec() != QDialog::Accepted)
return false;
// Check if changed.
const BreakpointParameters newParameters = parameters();
*parts = data->differencesTo(newParameters);
if (!*parts)
return false;
*data = newParameters;
return true;
}
// Dialog allowing changing properties of multiple breakpoints at a time.
class MultiBreakPointsDialog : public QDialog
{
public:
MultiBreakPointsDialog(unsigned int enabledParts, QWidget *parent);
QString condition() const { return m_lineEditCondition->text(); }
int ignoreCount() const { return m_spinBoxIgnoreCount->value(); }
int threadSpec() const
{ return BreakHandler::threadSpecFromDisplay(m_lineEditThreadSpec->text()); }
void setCondition(const QString &c) { m_lineEditCondition->setText(c); }
void setIgnoreCount(int i) { m_spinBoxIgnoreCount->setValue(i); }
void setThreadSpec(int t)
{ return m_lineEditThreadSpec->setText(BreakHandler::displayFromThreadSpec(t)); }
private:
QLineEdit *m_lineEditCondition;
QSpinBox *m_spinBoxIgnoreCount;
QLineEdit *m_lineEditThreadSpec;
QDialogButtonBox *m_buttonBox;
};
MultiBreakPointsDialog::MultiBreakPointsDialog(unsigned int enabledParts, QWidget *parent) :
QDialog(parent)
{
setWindowTitle(Tr::tr("Edit Breakpoint Properties"));
m_lineEditCondition = new QLineEdit(this);
m_spinBoxIgnoreCount = new QSpinBox(this);
m_spinBoxIgnoreCount->setMinimum(0);
m_spinBoxIgnoreCount->setMaximum(2147483647);
m_lineEditThreadSpec = new QLineEdit(this);
m_buttonBox = new QDialogButtonBox(this);
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
auto formLayout = new QFormLayout;
if (enabledParts & ConditionPart)
formLayout->addRow(Tr::tr("&Condition:"), m_lineEditCondition);
formLayout->addRow(Tr::tr("&Ignore count:"), m_spinBoxIgnoreCount);
formLayout->addRow(Tr::tr("&Thread specification:"), m_lineEditThreadSpec);
auto verticalLayout = new QVBoxLayout(this);
verticalLayout->addLayout(formLayout);
verticalLayout->addWidget(m_buttonBox);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
BreakHandler::BreakHandler(DebuggerEngine *engine)
: m_engine(engine)
{
#if USE_BREAK_MODEL_TEST
new ModelTest(this, 0);
#endif
setHeader({Tr::tr("Number"), Tr::tr("Function"), Tr::tr("File"), Tr::tr("Line"),
Tr::tr("Address"), Tr::tr("Condition"), Tr::tr("Ignore"), Tr::tr("Threads")});
}
bool BreakpointParameters::isLocatedAt(const FilePath &file, int line, const FilePath &markerFile) const
{
return textPosition.line == line && (fileName == file || fileName == markerFile);
}
static bool isSimilarTo(const BreakpointParameters &params, const BreakpointParameters &needle)
{
// Clear miss.
if (needle.type != UnknownBreakpointType && params.type != UnknownBreakpointType
&& params.type != needle.type)
return false;
// Clear hit.
if (params.address && params.address == needle.address)
return true;
// Clear hit.
if (params == needle)
return true;
// At least at a position we were looking for.
// FIXME: breaks multiple breakpoints at the same location
if (!params.fileName.isEmpty()
&& params.fileName == needle.fileName
&& params.textPosition == needle.textPosition)
return true;
return false;
}
Breakpoint BreakHandler::findBreakpointByResponseId(const QString &id) const
{
return findItemAtLevel<1>([id](const Breakpoint bp) {
return bp && bp->responseId() == id;
});
}
SubBreakpoint BreakHandler::findSubBreakpointByResponseId(const QString &id) const
{
return findItemAtLevel<2>([id](const SubBreakpoint sub) {
return sub && sub->responseId == id;
});
}
QVariant BreakHandler::data(const QModelIndex &idx, int role) const
{
if (role == BaseTreeView::ItemDelegateRole)
return QVariant::fromValue(new LeftElideDelegate);
return BreakHandlerModel::data(idx, role);
}
Breakpoint BreakHandler::findWatchpoint(const BreakpointParameters &params) const
{
return findItemAtLevel<1>([params](const Breakpoint &bp) {
return bp->m_parameters.isWatchpoint()
&& bp->m_parameters.address == params.address
&& bp->m_parameters.size == params.size
&& bp->m_parameters.expression == params.expression
&& bp->m_parameters.bitpos == params.bitpos;
});
}
Breakpoint BreakHandler::findBreakpointByIndex(const QModelIndex &index) const
{
return itemForIndexAtLevel<1>(index);
}
SubBreakpoint BreakHandler::findSubBreakpointByIndex(const QModelIndex &index) const
{
return itemForIndexAtLevel<2>(index);
}
Breakpoint BreakHandler::findBreakpointByModelId(int modelId) const
{
return findItemAtLevel<1>([modelId](const Breakpoint &bp) {
QTC_ASSERT(bp, return false);
return bp->modelId() == modelId;
});
}
Breakpoints BreakHandler::findBreakpointsByIndex(const QList<QModelIndex> &list) const
{
QSet<Breakpoint> items;
for (const QModelIndex &index : list) {
if (Breakpoint bp = findBreakpointByIndex(index))
items.insert(bp);
}
return Utils::toList(items);
}
SubBreakpoints BreakHandler::findSubBreakpointsByIndex(const QList<QModelIndex> &list) const
{
QSet<SubBreakpoint> items;
for (const QModelIndex &index : list) {
if (SubBreakpoint sbp = findSubBreakpointByIndex(index))
items.insert(sbp);
}
return Utils::toList(items);
}
QString BreakHandler::displayFromThreadSpec(int spec)
{
return spec == -1 ? Tr::tr("(all)") : QString::number(spec);
}
int BreakHandler::threadSpecFromDisplay(const QString &str)
{
bool ok = false;
int result = str.toInt(&ok);
return ok ? result : -1;
}
const QString empty("-");
QVariant BreakpointItem::data(int column, int role) const
{
if (role == Qt::ForegroundRole) {
static const QVariant gray(QColor(140, 140, 140));
switch (m_state) {
case BreakpointInsertionRequested:
case BreakpointInsertionProceeding:
case BreakpointUpdateRequested:
case BreakpointUpdateProceeding:
case BreakpointRemoveRequested:
case BreakpointRemoveProceeding:
return gray;
case BreakpointInserted:
case BreakpointNew:
case BreakpointDead:
break;
};
}
switch (column) {
case BreakpointNumberColumn:
if (role == Qt::DisplayRole)
return m_displayName.isEmpty() ? m_responseId : m_displayName;
if (role == Qt::DecorationRole)
return icon(m_needsLocationMarker);
break;
case BreakpointFunctionColumn:
if (role == Qt::DisplayRole) {
if (!m_parameters.functionName.isEmpty())
return simplifyType(m_parameters.functionName);
if (m_parameters.type == BreakpointAtMain
|| m_parameters.type == BreakpointAtThrow
|| m_parameters.type == BreakpointAtCatch
|| m_parameters.type == BreakpointAtFork
|| m_parameters.type == BreakpointAtExec
//|| m_response.type == BreakpointAtVFork
|| m_parameters.type == BreakpointAtSysCall)
return typeToString(m_parameters.type);
if (m_parameters.type == WatchpointAtAddress)
return Tr::tr("Data at 0x%1").arg(m_parameters.address, 0, 16);
if (m_parameters.type == WatchpointAtExpression)
return Tr::tr("Data at %1").arg(m_parameters.expression);
return empty;
}
break;
case BreakpointFileColumn:
if (role == Qt::DisplayRole)
return markerFileName().toUserOutput();
break;
case BreakpointLineColumn:
if (role == Qt::DisplayRole) {
const int line = markerLineNumber();
if (line > 0)
return line;
return empty;
}
if (role == Qt::UserRole + 1)
return m_parameters.textPosition.line;
break;
case BreakpointAddressColumn:
if (role == Qt::DisplayRole) {
if (m_parameters.address)
return QString("0x%1").arg(m_parameters.address, 0, 16);
return QVariant();
}
break;
case BreakpointConditionColumn:
if (role == Qt::DisplayRole)
return m_parameters.condition;
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit if this condition is met.");
if (role == Qt::UserRole + 1)
return m_parameters.condition;
break;
case BreakpointIgnoreColumn:
if (role == Qt::DisplayRole) {
const int ignoreCount = m_parameters.ignoreCount;
return ignoreCount ? QVariant(ignoreCount) : QVariant(QString());
}
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit after being ignored so many times.");
if (role == Qt::UserRole + 1)
return m_parameters.ignoreCount;
break;
case BreakpointThreadsColumn:
if (role == Qt::DisplayRole)
return BreakHandler::displayFromThreadSpec(m_parameters.threadSpec);
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit in the specified thread(s).");
if (role == Qt::UserRole + 1)
return BreakHandler::displayFromThreadSpec(m_parameters.threadSpec);
break;
}
if (role == Qt::ToolTipRole && settings().useToolTipsInBreakpointsView())
return toolTip();
return {};
}
void BreakpointItem::addToCommand(DebuggerCommand *cmd, BreakpointPathUsage defaultPathUsage) const
{
QTC_ASSERT(m_globalBreakpoint, return);
const BreakpointParameters &requested = requestedParameters();
cmd->arg("modelid", modelId());
cmd->arg("id", m_responseId);
cmd->arg("type", requested.type);
cmd->arg("ignorecount", requested.ignoreCount);
cmd->arg("condition", toHex(requested.condition));
cmd->arg("command", toHex(requested.command));
cmd->arg("function", requested.functionName);
cmd->arg("oneshot", requested.oneShot);
cmd->arg("enabled", requested.enabled);
cmd->arg("line", requested.textPosition.line);
cmd->arg("address", requested.address);
cmd->arg("expression", requested.expression);
BreakpointPathUsage pathUsage = (requested.pathUsage
== BreakpointPathUsage::BreakpointPathUsageEngineDefault)
? defaultPathUsage
: requested.pathUsage;
cmd->arg("file",
pathUsage == BreakpointPathUsage::BreakpointUseFullPath
? requested.fileName.path()
: requested.fileName.fileName());
}
void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt, const FilePath &fileRoot)
{
m_parameters.updateFromGdbOutput(bkpt, fileRoot);
adjustMarker();
}
int BreakpointItem::modelId() const
{
return m_globalBreakpoint ? m_globalBreakpoint->modelId() : 0;
}
void BreakpointItem::setPending(bool pending)
{
m_parameters.pending = pending;
adjustMarker();
}
void BreakHandler::removeAlienBreakpoint(const QString &rid)
{
Breakpoint bp = findBreakpointByResponseId(rid);
destroyItem(bp);
}
void BreakHandler::requestBreakpointInsertion(const Breakpoint &bp)
{
bp->gotoState(BreakpointInsertionRequested, BreakpointNew);
m_engine->insertBreakpoint(bp);
}
void BreakHandler::requestBreakpointUpdate(const Breakpoint &bp)
{
bp->gotoState(BreakpointUpdateRequested, BreakpointInserted);
m_engine->updateBreakpoint(bp);
}
void BreakHandler::requestBreakpointRemoval(const Breakpoint &bp)
{
bp->gotoState(BreakpointRemoveRequested, BreakpointInserted);
m_engine->removeBreakpoint(bp);
}
void BreakHandler::requestBreakpointEnabling(const Breakpoint &bp, bool enabled)
{
if (bp->m_parameters.enabled != enabled) {
bp->update();
requestBreakpointUpdate(bp);
}
}
void BreakHandler::requestSubBreakpointEnabling(const SubBreakpoint &sbp, bool enabled)
{
if (sbp->params.enabled != enabled) {
sbp->params.enabled = enabled;
sbp->breakpoint()->update();
QTimer::singleShot(0, m_engine, [this, sbp, enabled] {
m_engine->enableSubBreakpoint(sbp, enabled);
});
}
}
void BreakpointItem::setMarkerFileAndPosition(const FilePath &fileName,
const Text::Position &textPosition)
{
if (m_parameters.fileName == fileName && m_parameters.textPosition == textPosition)
return;
m_parameters.fileName = fileName;
m_parameters.textPosition = textPosition;
destroyMarker();
updateMarker();
update();
}
static bool isAllowedTransition(BreakpointState from, BreakpointState to)
{
switch (from) {
case BreakpointNew:
return to == BreakpointInsertionRequested
|| to == BreakpointDead;
case BreakpointInsertionRequested:
return to == BreakpointInsertionProceeding;
case BreakpointInsertionProceeding:
return to == BreakpointInserted
|| to == BreakpointDead
|| to == BreakpointUpdateRequested
|| to == BreakpointRemoveRequested;
case BreakpointUpdateRequested:
return to == BreakpointUpdateProceeding;
case BreakpointUpdateProceeding:
return to == BreakpointInserted
|| to == BreakpointDead;
case BreakpointInserted:
return to == BreakpointUpdateRequested
|| to == BreakpointRemoveRequested;
case BreakpointRemoveRequested:
return to == BreakpointRemoveProceeding;
case BreakpointRemoveProceeding:
return to == BreakpointDead;
case BreakpointDead:
return false;
}
qDebug() << "UNKNOWN BREAKPOINT STATE:" << from;
return false;
}
void BreakpointItem::gotoState(BreakpointState target, BreakpointState assumedCurrent)
{
QTC_ASSERT(m_state == assumedCurrent, qDebug() << target << m_state);
setState(target);
}
void BreakpointItem::setNeedsLocationMarker(bool needsLocationMarker)
{
if (m_needsLocationMarker == needsLocationMarker)
return;
m_needsLocationMarker = needsLocationMarker;
update();
}
void BreakHandler::updateDisassemblerMarker(const Breakpoint &bp)
{
return m_engine->disassemblerAgent()->updateBreakpointMarker(bp);
}
void BreakHandler::removeDisassemblerMarker(const Breakpoint &bp)
{
m_engine->disassemblerAgent()->removeBreakpointMarker(bp);
bp->destroyMarker();
if (GlobalBreakpoint gbp = bp->globalBreakpoint())
gbp->updateMarker();
}
static bool matches(const Location &loc, const BreakpointParameters &bp)
{
if (loc.fileName() == bp.fileName && loc.textPosition() == bp.textPosition
&& bp.textPosition.line > 0)
return true;
if (loc.address() == bp.address && bp.address > 0)
return true;
return false;
}
void BreakHandler::setLocation(const Location &loc)
{
forItemsAtLevel<1>([loc](Breakpoint bp) {
bool needsMarker = matches(loc, bp->parameters());
if (GlobalBreakpoint gpb = bp->globalBreakpoint())
needsMarker = needsMarker || matches(loc, gpb->requestedParameters());
bp->setNeedsLocationMarker(needsMarker);
});
}
void BreakHandler::resetLocation()
{
forItemsAtLevel<1>([](Breakpoint bp) { bp->setNeedsLocationMarker(false); });
}
void BreakpointItem::setState(BreakpointState state)
{
//qDebug() << "BREAKPOINT STATE TRANSITION, ID: " << m_id
// << " FROM: " << state << " TO: " << state;
if (!isAllowedTransition(m_state, state)) {
qDebug() << "UNEXPECTED BREAKPOINT STATE TRANSITION" << m_state << state;
QTC_CHECK(false);
}
if (m_state == state) {
qDebug() << "STATE UNCHANGED: " << responseId() << m_state;
return;
}
m_state = state;
// FIXME: updateMarker() should recognize the need for icon changes.
if (state == BreakpointInserted || state == BreakpointInsertionRequested) {
destroyMarker();
updateMarker();
}
update();
}
const GlobalBreakpoint BreakpointItem::globalBreakpoint() const
{
return m_globalBreakpoint;
}
void BreakpointItem::setParameters(const BreakpointParameters &value)
{
m_parameters = value;
adjustMarker();
}
void BreakpointItem::setEnabled(bool on)
{
m_parameters.enabled = on;
adjustMarker();
}
void DebuggerEngine::notifyBreakpointInsertProceeding(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
bp->gotoState(BreakpointInsertionProceeding, BreakpointInsertionRequested);
}
void DebuggerEngine::notifyBreakpointInsertOk(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
bp->adjustMarker();
bp->gotoState(BreakpointInserted, BreakpointInsertionProceeding);
breakHandler()->updateDisassemblerMarker(bp);
bp->updateMarker();
}
void DebuggerEngine::notifyBreakpointInsertFailed(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
GlobalBreakpoint gbp = bp->globalBreakpoint();
bp->gotoState(BreakpointDead, BreakpointInsertionProceeding);
breakHandler()->removeDisassemblerMarker(bp);
breakHandler()->destroyItem(bp);
QTC_ASSERT(gbp, return);
gbp->updateMarker();
}
void DebuggerEngine::notifyBreakpointRemoveProceeding(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
bp->gotoState(BreakpointRemoveProceeding, BreakpointRemoveRequested);
}
void DebuggerEngine::notifyBreakpointRemoveOk(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
QTC_ASSERT(bp->state() == BreakpointRemoveProceeding, qDebug() << bp->state());
breakHandler()->removeDisassemblerMarker(bp);
breakHandler()->destroyItem(bp);
}
void DebuggerEngine::notifyBreakpointRemoveFailed(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
QTC_ASSERT(bp->m_state == BreakpointRemoveProceeding, qDebug() << bp->m_state);
breakHandler()->removeDisassemblerMarker(bp);
breakHandler()->destroyItem(bp);
}
void DebuggerEngine::notifyBreakpointChangeProceeding(const Breakpoint &bp)
{
bp->gotoState(BreakpointUpdateProceeding, BreakpointUpdateRequested);
}
void DebuggerEngine::notifyBreakpointChangeOk(const Breakpoint &bp)
{
bp->gotoState(BreakpointInserted, BreakpointUpdateProceeding);
}
void DebuggerEngine::notifyBreakpointChangeFailed(const Breakpoint &bp)
{
bp->gotoState(BreakpointDead, BreakpointUpdateProceeding);
}
void DebuggerEngine::notifyBreakpointNeedsReinsertion(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
QTC_ASSERT(bp->m_state == BreakpointUpdateProceeding, qDebug() << bp->m_state);
bp->m_state = BreakpointInsertionRequested;
}
void BreakHandler::handleAlienBreakpoint(const QString &responseId, const BreakpointParameters &params)
{
// Search a breakpoint we might refer to.
Breakpoint bp = findItemAtLevel<1>([params, responseId](const Breakpoint &bp) {
if (bp && !bp->responseId().isEmpty() && bp->responseId() == responseId)
return true;
return bp && isSimilarTo(bp->m_parameters, params);
});
if (bp) {
// FIXME: x.y looks rather gdb specific.
if (bp->responseId().contains('.')) {
SubBreakpoint loc = bp->findOrCreateSubBreakpoint(bp->responseId());
QTC_ASSERT(loc, return);
loc->setParameters(params);
} else {
bp->setParameters(params);
}
} else {
bp = new BreakpointItem(nullptr);
bp->m_responseId = responseId;
bp->m_parameters = params;
bp->m_state = BreakpointInserted;
bp->updateMarker();
rootItem()->appendChild(bp);
// This has no global breakpoint, so there's nothing to update here.
}
}
SubBreakpoint BreakpointItem::findOrCreateSubBreakpoint(const QString &responseId)
{
SubBreakpoint loc = findFirstLevelChild([&](const SubBreakpoint &l) {
return l->responseId == responseId;
});
if (loc) {
// This modifies an existing sub-breakpoint.
loc->update();
} else {
// This is a new sub-breakpoint.
loc = new SubBreakpointItem;
loc->responseId = responseId;
appendChild(loc);
expand();
}
return loc;
}
bool BreakHandler::tryClaimBreakpoint(const GlobalBreakpoint &gbp)
{
const Breakpoints bps = breakpoints();
if (Utils::anyOf(bps, [gbp](const Breakpoint &bp) { return bp->globalBreakpoint() == gbp; }))
return false;
if (!m_engine->acceptsBreakpoint(gbp->requestedParameters())) {
m_engine->showMessage(QString("BREAKPOINT %1 IS NOT ACCEPTED BY ENGINE %2")
.arg(gbp->displayName()).arg(objectName()));
return false;
}
m_engine->showMessage(QString("TAKING OWNERSHIP OF BREAKPOINT %1").arg(gbp->displayName()));
Breakpoint bp(new BreakpointItem(gbp));
rootItem()->appendChild(bp);
gbp->updateMarker();
requestBreakpointInsertion(bp);
return true;
}
void BreakHandler::gotoLocation(const Breakpoint &bp) const
{
QTC_ASSERT(bp, return);
QTC_ASSERT(m_engine, return);
if (bp->m_parameters.type == BreakpointByAddress) {
m_engine->gotoLocation(bp->m_parameters.address);
} else {
// Don't use gotoLocation unconditionally as this ends up in
// disassembly if OperateByInstruction is on. But fallback
// to disassembly if we can't open the file.
if (IEditor *editor = EditorManager::openEditor(bp->markerFileName()))
editor->gotoLine(bp->markerLineNumber(), 0);
else
m_engine->openDisassemblerView(Location(bp->m_parameters.address));
}
}
const Breakpoints BreakHandler::breakpoints() const
{
QList<Breakpoint> items;
forItemsAtLevel<1>([&items](Breakpoint bp) { if (bp) items.append(bp); });
return items;
}
void BreakpointItem::adjustMarker()
{
destroyMarker();
updateMarker();
}
void BreakpointItem::deleteBreakpoint()
{
QTC_ASSERT(!globalBreakpoint(), return); // Use deleteBreakpoint(GlobalBreakpoint gbp) instead.
for (QPointer<DebuggerEngine> engine : EngineManager::engines())
engine->breakHandler()->requestBreakpointRemoval(this);
}
void BreakpointItem::deleteGlobalOrThisBreakpoint()
{
if (GlobalBreakpoint gbp = globalBreakpoint()) {
gbp->deleteBreakpoint();
} else {
deleteBreakpoint();
}
}
bool BreakHandler::setData(const QModelIndex &idx, const QVariant &value, int role)
{
if (role == BaseTreeView::ItemActivatedRole) {
if (Breakpoint bp = findBreakpointByIndex(idx))
gotoLocation(bp);
return true;
}
if (role == BaseTreeView::ItemViewEventRole) {
ItemViewEvent ev = value.value<ItemViewEvent>();
if (ev.as<QContextMenuEvent>())
return contextMenuEvent(ev);
if (auto kev = ev.as<QKeyEvent>(QEvent::KeyPress)) {
if (kev->key() == Qt::Key_Delete || kev->key() == Qt::Key_Backspace) {
QModelIndexList si = ev.currentOrSelectedRows();
const Breakpoints bps = findBreakpointsByIndex(si);
for (Breakpoint bp : bps) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint())
gbp->deleteBreakpoint();
else
bp->deleteBreakpoint();
}
// int row = qMin(rowCount() - ids.size() - 1, idx.row());
// setCurrentIndex(index(row, 0)); FIXME
return true;
}
if (kev->key() == Qt::Key_Space) {
const QModelIndexList selectedIds = ev.selectedRows();
if (!selectedIds.isEmpty()) {
const Breakpoints bps = findBreakpointsByIndex(selectedIds);
const SubBreakpoints sbps = findSubBreakpointsByIndex(selectedIds);
const bool isEnabled = (bps.isEmpty() && sbps.isEmpty())
|| (!bps.isEmpty() && bps.at(0)->isEnabled())
|| (!sbps.isEmpty() && sbps.at(0)->params.enabled);
for (Breakpoint bp : bps) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint())
gbp->setEnabled(!isEnabled, false);
requestBreakpointEnabling(bp, !isEnabled);
}
for (SubBreakpoint sbp : sbps)
requestSubBreakpointEnabling(sbp, !isEnabled);
return true;
}
}
}
if (ev.as<QMouseEvent>(QEvent::MouseButtonDblClick)) {
if (Breakpoint bp = findBreakpointByIndex(idx)) {
if (idx.column() >= BreakpointAddressColumn)
editBreakpoints({bp}, ev.view());
else
gotoLocation(bp);
} else if (SubBreakpoint loc = itemForIndexAtLevel<2>(idx)) {
gotoLocation(loc->breakpoint());
} else {
BreakpointManager::executeAddBreakpointDialog();
}
return true;
}
}
return false;
}
bool BreakHandler::contextMenuEvent(const ItemViewEvent &ev)
{
const QModelIndexList selectedIndices = ev.selectedRows();
const Breakpoints selectedBreakpoints = findBreakpointsByIndex(selectedIndices);
const bool breakpointsEnabled = selectedBreakpoints.isEmpty() || selectedBreakpoints.at(0)->isEnabled();
QList<SubBreakpointItem *> selectedLocations;
bool handlesIndividualLocations = false;
for (const QModelIndex &index : selectedIndices) {
if (SubBreakpointItem *location = itemForIndexAtLevel<2>(index)) {
if (selectedLocations.contains(location))
continue;
selectedLocations.append(location);
if (m_engine->hasCapability(BreakIndividualLocationsCapability))
handlesIndividualLocations = true;
}
}
const bool locationsEnabled = selectedLocations.isEmpty() || selectedLocations.at(0)->params.enabled;
auto menu = new QMenu;
addAction(this, menu, Tr::tr("Add Breakpoint..."), true, &BreakpointManager::executeAddBreakpointDialog);
addAction(this, menu, Tr::tr("Delete Selected Breakpoints"),
!selectedBreakpoints.isEmpty(),
[selectedBreakpoints] {
for (Breakpoint bp : selectedBreakpoints) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint()) {
gbp->deleteBreakpoint();
} else {
bp->deleteBreakpoint();
}
}
});
addAction(this, menu, Tr::tr("Edit Selected Breakpoints..."),
!selectedBreakpoints.isEmpty(),
[this, selectedBreakpoints, ev] { editBreakpoints(selectedBreakpoints, ev.view()); });
// FIXME BP: m_engine->threadsHandler()->currentThreadId();
// int threadId = 0;
// addAction(menu,
// threadId == -1 ? Tr::tr("Associate Breakpoint with All Threads")
// : Tr::tr("Associate Breakpoint with Thread %1").arg(threadId),
// !selectedItems.isEmpty(),
// [this, selectedItems, threadId] {
// for (Breakpoint bp : selectedItems)
// bp.setThreadSpec(threadId);
// });
addAction(this, menu,
selectedBreakpoints.size() > 1
? breakpointsEnabled ? Tr::tr("Disable Selected Breakpoints") : Tr::tr("Enable Selected Breakpoints")
: breakpointsEnabled ? Tr::tr("Disable Breakpoint") : Tr::tr("Enable Breakpoint"),
!selectedBreakpoints.isEmpty(),
[this, selectedBreakpoints, breakpointsEnabled] {
for (Breakpoint bp : selectedBreakpoints) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint())
gbp->setEnabled(!breakpointsEnabled, false);
requestBreakpointEnabling(bp, !breakpointsEnabled);
}
}
);
addAction(this, menu,
selectedLocations.size() > 1
? locationsEnabled ? Tr::tr("Disable Selected Locations") : Tr::tr("Enable Selected Locations")
: locationsEnabled ? Tr::tr("Disable Location") : Tr::tr("Enable Location"),
!selectedLocations.isEmpty() && handlesIndividualLocations,
[this, selectedLocations, locationsEnabled] {
for (SubBreakpointItem * const sbp : selectedLocations)
requestSubBreakpointEnabling(QPointer(sbp), !locationsEnabled);
}
);
menu->addSeparator();
addAction(this, menu, Tr::tr("Delete All Breakpoints"),
rowCount() > 0,
&BreakpointManager::executeDeleteAllBreakpointsDialog);
// Delete by file: Find indices of breakpoints of the same file.
QList<Breakpoint> breakpointsInFile;
QString file;
if (Breakpoint bp = itemForIndexAtLevel<1>(ev.sourceModelIndex())) {
const QModelIndex index = ev.sourceModelIndex().sibling(ev.sourceModelIndex().row(), BreakpointFileColumn);
if (!file.isEmpty()) {
for (int i = 0; i != rowCount(); ++i)
if (index.data().toString() == file)
breakpointsInFile.append(findBreakpointByIndex(index));
}
}
addAction(this, menu, Tr::tr("Delete Breakpoints of \"%1\"").arg(file),
Tr::tr("Delete Breakpoints of File"),
breakpointsInFile.size() > 1,
[breakpointsInFile] {
for (Breakpoint bp : breakpointsInFile)
bp->deleteGlobalOrThisBreakpoint();
});
menu->addSeparator();
menu->addAction(settings().useToolTipsInBreakpointsView.action());
menu->addAction(settings().settingsDialog.action());
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(ev.globalPos());
return true;
}
void BreakHandler::removeBreakpoint(const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
switch (bp->m_state) {
case BreakpointRemoveRequested:
break;
case BreakpointInserted:
case BreakpointInsertionProceeding:
requestBreakpointRemoval(bp);
break;
case BreakpointNew:
bp->setState(BreakpointDead);
bp->destroyMarker();
destroyItem(bp);
break;
default:
qWarning("Warning: Cannot remove breakpoint %s in state '%s'.",
qPrintable(bp->responseId()), qPrintable(stateToString(bp->m_state)));
bp->m_state = BreakpointRemoveRequested;
break;
}
}
static unsigned int engineBreakpointCapabilities(DebuggerEngine *engine)
{
unsigned int enabledParts = ~0;
if (!engine->hasCapability(BreakConditionCapability))
enabledParts &= ~ConditionPart;
if (!engine->hasCapability(BreakModuleCapability))
enabledParts &= ~ModulePart;
if (!engine->hasCapability(TracePointCapability))
enabledParts &= ~TracePointPart;
return enabledParts;
}
void BreakHandler::editBreakpoint(const Breakpoint &bp, QWidget *parent)
{
QTC_ASSERT(bp, return);
BreakpointParameters params = bp->requestedParameters();
BreakpointParts parts = NoParts;
BreakpointDialog dialog(engineBreakpointCapabilities(m_engine), parent);
if (!dialog.showDialog(&params, &parts))
return;
if (params != bp->requestedParameters()) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint()) {
gbp->setParameters(params);
} else {
bp->setParameters(params);
}
updateDisassemblerMarker(bp);
bp->updateMarker();
bp->update();
if (bp->needsChange() && bp->m_state != BreakpointNew)
requestBreakpointUpdate(bp);
}
}
void BreakHandler::editBreakpoints(const Breakpoints &bps, QWidget *parent)
{
QTC_ASSERT(!bps.isEmpty(), return);
Breakpoint bp = bps.at(0);
if (bps.size() == 1) {
editBreakpoint(bp, parent);
return;
}
// This allows to change properties of multiple breakpoints at a time.
QTC_ASSERT(bp, return);
MultiBreakPointsDialog dialog(engineBreakpointCapabilities(m_engine), parent);
dialog.setCondition(bp->condition());
dialog.setIgnoreCount(bp->ignoreCount());
dialog.setThreadSpec(bp->threadSpec());
if (dialog.exec() == QDialog::Rejected)
return;
const QString newCondition = dialog.condition();
const int newIgnoreCount = dialog.ignoreCount();
const int newThreadSpec = dialog.threadSpec();
for (Breakpoint bp : bps) {
if (bp) {
if (GlobalBreakpoint gbp = bp->globalBreakpoint()) {
BreakpointParameters params = bp->requestedParameters();
params.condition = newCondition;
params.ignoreCount = newIgnoreCount;
params.threadSpec = newThreadSpec;
gbp->setParameters(params);
} else {
bp->m_parameters.condition = newCondition;
bp->m_parameters.ignoreCount = newIgnoreCount;
bp->m_parameters.threadSpec = newThreadSpec;
}
if (bp->m_state != BreakpointNew)
requestBreakpointUpdate(bp);
}
}
}
//////////////////////////////////////////////////////////////////
//
// Storage
//
//////////////////////////////////////////////////////////////////
BreakpointItem::BreakpointItem(const GlobalBreakpoint &gbp)
: m_globalBreakpoint(gbp)
{
}
BreakpointItem::~BreakpointItem()
{
delete m_marker;
}
void BreakpointItem::destroyMarker()
{
if (m_marker) {
BreakpointMarker *m = m_marker;
m_marker = nullptr;
delete m;
}
}
FilePath BreakpointItem::markerFileName() const
{
// Some heuristics to find a "good" file name.
if (m_parameters.fileName.exists())
return m_parameters.fileName.absoluteFilePath();
const FilePath origFileName = requestedParameters().fileName;
if (m_parameters.fileName.endsWith(origFileName.fileName()))
return m_parameters.fileName;
if (origFileName.endsWith(m_parameters.fileName.fileName()))
return origFileName;
return m_parameters.fileName.toString().size() > origFileName.toString().size()
? m_parameters.fileName
: origFileName;
}
int BreakpointItem::markerLineNumber() const
{
if (m_parameters.textPosition.line > 0)
return m_parameters.textPosition.line;
return requestedParameters().textPosition.line;
}
const BreakpointParameters &BreakpointItem::requestedParameters() const
{
return m_globalBreakpoint ? m_globalBreakpoint->requestedParameters() : m_alienParameters;
}
static void formatAddress(QTextStream &str, quint64 address)
{
if (address) {
str << "0x";
str.setIntegerBase(16);
str << address;
str.setIntegerBase(10);
}
}
bool BreakpointItem::needsChange() const
{
const BreakpointParameters &oparams = requestedParameters();
if (!oparams.conditionsMatch(m_parameters.condition))
return true;
if (oparams.ignoreCount != m_parameters.ignoreCount)
return true;
if (oparams.enabled != m_parameters.enabled)
return true;
if (oparams.threadSpec != m_parameters.threadSpec)
return true;
if (oparams.command != m_parameters.command)
return true;
if (oparams.type == BreakpointByFileAndLine && oparams.textPosition != m_parameters.textPosition)
return true;
if (oparams.pathUsage != m_parameters.pathUsage)
return true;
// FIXME: Too strict, functions may have parameter lists, or not.
// if (m_params.type == BreakpointByFunction && m_params.functionName != m_response.functionName)
// return true;
// if (m_params.type == BreakpointByAddress && m_params.address != m_response.address)
// return true;
return false;
}
void BreakpointItem::updateMarker()
{
const FilePath &file = markerFileName();
int line = markerLineNumber();
if (m_marker && (file != m_marker->filePath() || line != m_marker->lineNumber()))
destroyMarker();
if (!m_marker && !file.isEmpty() && line > 0)
m_marker = new BreakpointMarker(this, file, line);
}
QIcon BreakpointItem::icon(bool withLocationMarker) const
{
// FIXME: This seems to be called on each cursor blink as soon as the
// cursor is near a line with a breakpoint marker (+/- 2 lines or so).
if (m_parameters.isTracepoint())
return Icons::TRACEPOINT.icon();
if (m_parameters.type == WatchpointAtAddress)
return Icons::WATCHPOINT.icon();
if (m_parameters.type == WatchpointAtExpression)
return Icons::WATCHPOINT.icon();
if (!m_parameters.enabled)
return Icons::BREAKPOINT_DISABLED.icon();
if (m_state == BreakpointInserted && !m_parameters.pending)
return withLocationMarker ? Icons::BREAKPOINT_WITH_LOCATION.icon()
: Icons::BREAKPOINT.icon();
return Icons::BREAKPOINT_PENDING.icon();
}
QString BreakpointItem::toolTip() const
{
const BreakpointParameters &requested = requestedParameters();
QString rc;
QTextStream str(&rc);
str << "<html><body><b>" << Tr::tr("Breakpoint") << "</b>"
<< "<table>"
<< "<tr><td>" << Tr::tr("Internal ID:")
<< "</td><td>" << m_responseId << "</td></tr>"
<< "<tr><td>" << Tr::tr("State:")
<< "</td><td>" << (requestedParameters().enabled ? Tr::tr("Enabled") : Tr::tr("Disabled"));
if (m_parameters.pending)
str << ", " << Tr::tr("pending");
str << ", " << stateToString(m_state) << "</td></tr>";
str << "<tr><td>" << Tr::tr("Breakpoint Type:")
<< "</td><td>" << typeToString(requested.type) << "</td></tr>"
<< "<tr><td>" << Tr::tr("Marker File:")
<< "</td><td>" << markerFileName().toUserOutput() << "</td></tr>"
<< "<tr><td>" << Tr::tr("Marker Line:")
<< "</td><td>" << markerLineNumber() << "</td></tr>";
if (m_parameters.hitCount) {
str << "<tr><td>" << Tr::tr("Hit Count:")
<< "</td><td>" << *m_parameters.hitCount << "</td></tr>";
}
str << "</table><br><table>"
<< "<tr><th>" << Tr::tr("Property")
<< "</th><th>" << Tr::tr("Requested")
<< "</th><th>" << Tr::tr("Obtained") << "</th></tr>";
if (!m_displayName.isEmpty()) {
str << "<tr><td>" << Tr::tr("Display Name:")
<< "</td><td>&mdash;</td><td>" << m_displayName << "</td></tr>";
}
if (m_parameters.type == BreakpointByFunction) {
str << "<tr><td>" << Tr::tr("Function Name:")
<< "</td><td>" << requested.functionName
<< "</td><td>" << m_parameters.functionName
<< "</td></tr>";
}
if (m_parameters.type == BreakpointByFileAndLine) {
str << "<tr><td>" << Tr::tr("File Name:")
<< "</td><td>" << requested.fileName.toUserOutput()
<< "</td><td>" << m_parameters.fileName.toUserOutput()
<< "</td></tr>"
<< "<tr><td>" << Tr::tr("Line Number:")
<< "</td><td>" << requested.textPosition.line
<< "</td><td>" << m_parameters.textPosition.line << "</td></tr>";
}
if (requested.type == BreakpointByFunction || m_parameters.type == BreakpointByFileAndLine) {
str << "<tr><td>" << Tr::tr("Module:")
<< "</td><td>" << requested.module
<< "</td><td>" << m_parameters.module
<< "</td></tr>";
}
str << "<tr><td>" << Tr::tr("Breakpoint Address:")
<< "</td><td>";
formatAddress(str, requested.address);
str << "</td><td>";
formatAddress(str, m_parameters.address);
str << "</td></tr>";
if (!requested.command.isEmpty() || !m_parameters.command.isEmpty()) {
str << "<tr><td>" << Tr::tr("Command:")
<< "</td><td>" << requested.command
<< "</td><td>" << m_parameters.command
<< "</td></tr>";
}
if (!requested.message.isEmpty() || !m_parameters.message.isEmpty()) {
str << "<tr><td>" << Tr::tr("Message:")
<< "</td><td>" << requested.message
<< "</td><td>" << m_parameters.message
<< "</td></tr>";
}
if (!requested.condition.isEmpty() || !m_parameters.condition.isEmpty()) {
str << "<tr><td>" << Tr::tr("Condition:")
<< "</td><td>" << requested.condition
<< "</td><td>" << m_parameters.condition
<< "</td></tr>";
}
if (requested.ignoreCount || m_parameters.ignoreCount) {
str << "<tr><td>" << Tr::tr("Ignore Count:") << "</td><td>";
if (requested.ignoreCount)
str << m_parameters.ignoreCount;
str << "</td><td>";
if (m_parameters.ignoreCount)
str << m_parameters.ignoreCount;
str << "</td></tr>";
}
if (requested.threadSpec >= 0 || m_parameters.threadSpec >= 0) {
str << "<tr><td>" << Tr::tr("Thread Specification:")
<< "</td><td>";
if (requested.threadSpec >= 0)
str << requested.threadSpec;
str << "</td><td>";
if (m_parameters.threadSpec >= 0)
str << m_parameters.threadSpec;
str << "</td></tr>";
}
str << "</table></body></html>";
return rc;
}
void BreakHandler::setWatchpointAtAddress(quint64 address, unsigned size)
{
BreakpointParameters params(WatchpointAtAddress);
params.address = address;
params.size = size;
if (findWatchpoint(params)) {
qDebug() << "WATCHPOINT EXISTS";
// removeBreakpoint(index);
return;
}
BreakpointManager::createBreakpointForEngine(params, m_engine);
}
void BreakHandler::setWatchpointAtExpression(const QString &exp)
{
BreakpointParameters params(WatchpointAtExpression);
params.expression = exp;
if (findWatchpoint(params)) {
qDebug() << "WATCHPOINT EXISTS";
// removeBreakpoint(index);
return;
}
BreakpointManager::createBreakpointForEngine(params, m_engine);
}
void BreakHandler::releaseAllBreakpoints()
{
GlobalBreakpoints gbps;
for (Breakpoint bp : breakpoints()) {
bp->removeChildren();
bp->destroyMarker();
gbps.append(bp->globalBreakpoint());
}
clear();
// Make now-unclaimed breakpoints globally visible again.
for (GlobalBreakpoint gbp: std::as_const(gbps)) {
if (gbp)
gbp->updateMarker();
}
}
QString BreakpointItem::msgWatchpointByExpressionTriggered(const QString &expr) const
{
return Tr::tr("Internal data breakpoint %1 at %2 triggered.")
.arg(responseId()).arg(expr);
}
QString BreakpointItem::msgWatchpointByExpressionTriggered(const QString &expr,
const QString &threadId) const
{
return Tr::tr("Internal data breakpoint %1 at %2 in thread %3 triggered.")
.arg(responseId()).arg(expr).arg(threadId);
}
QString BreakpointItem::msgWatchpointByAddressTriggered(quint64 address) const
{
return Tr::tr("Internal data breakpoint %1 at 0x%2 triggered.")
.arg(responseId()).arg(address, 0, 16);
}
QString BreakpointItem::msgWatchpointByAddressTriggered(quint64 address,
const QString &threadId) const
{
return Tr::tr("Internal data breakpoint %1 at 0x%2 in thread %3 triggered.")
.arg(responseId()).arg(address, 0, 16).arg(threadId);
}
QString BreakpointItem::msgBreakpointTriggered(const QString &threadId) const
{
return Tr::tr("Stopped at breakpoint %1 in thread %2.")
.arg(responseId()).arg(threadId);
}
QVariant SubBreakpointItem::data(int column, int role) const
{
if (role == Qt::DecorationRole && column == 0) {
if (params.tracepoint)
return Icons::TRACEPOINT.icon();
return params.enabled ? Icons::BREAKPOINT.icon()
: Icons::BREAKPOINT_DISABLED.icon();
}
if (role == Qt::DisplayRole) {
switch (column) {
case BreakpointNumberColumn:
return displayName.isEmpty() ? responseId : displayName;
case BreakpointFunctionColumn:
return params.functionName;
case BreakpointAddressColumn:
if (params.address)
return QString::fromLatin1("0x%1").arg(params.address, 0, 16);
}
}
return QVariant();
}
//
// GlobalBreakpointItem
//
// Ok to be not thread-safe. The order does not matter and only the gui
// produces authoritative ids.
static int currentId = 0;
GlobalBreakpointItem::GlobalBreakpointItem()
: m_modelId(++currentId)
{
}
GlobalBreakpointItem::~GlobalBreakpointItem()
{
delete m_marker;
m_marker = nullptr;
}
QVariant GlobalBreakpointItem::data(int column, int role) const
{
switch (column) {
case BreakpointNumberColumn:
if (role == Qt::DisplayRole) {
if (auto engine = usingEngine())
return engine->runParameters().displayName;
return QString("-");
}
if (role == Qt::DecorationRole)
return icon();
break;
case BreakpointFunctionColumn:
if (role == Qt::DisplayRole) {
if (!m_params.functionName.isEmpty())
return m_params.functionName;
if (m_params.type == BreakpointAtMain
|| m_params.type == BreakpointAtThrow
|| m_params.type == BreakpointAtCatch
|| m_params.type == BreakpointAtFork
|| m_params.type == BreakpointAtExec
//|| m_params.type == BreakpointAtVFork
|| m_params.type == BreakpointAtSysCall)
return typeToString(m_params.type);
if (m_params.type == WatchpointAtAddress)
return Tr::tr("Data at 0x%1").arg(m_params.address, 0, 16);
if (m_params.type == WatchpointAtExpression)
return Tr::tr("Data at %1").arg(m_params.expression);
return empty;
}
break;
case BreakpointFileColumn:
if (role == Qt::DisplayRole)
return m_params.fileName.toUserOutput();
break;
case BreakpointLineColumn:
if (role == Qt::DisplayRole) {
if (m_params.textPosition.line > 0)
return m_params.textPosition.line;
return empty;
}
if (role == Qt::UserRole + 1)
return m_params.textPosition.line;
break;
case BreakpointAddressColumn:
if (role == Qt::DisplayRole) {
const quint64 address = m_params.address;
if (address)
return QString("0x%1").arg(address, 0, 16);
return QVariant();
}
break;
case BreakpointConditionColumn:
if (role == Qt::DisplayRole)
return m_params.condition;
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit if this condition is met.");
if (role == Qt::UserRole + 1)
return m_params.condition;
break;
case BreakpointIgnoreColumn:
if (role == Qt::DisplayRole) {
const int ignoreCount = m_params.ignoreCount;
return ignoreCount ? QVariant(ignoreCount) : QVariant(QString());
}
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit after being ignored so many times.");
if (role == Qt::UserRole + 1)
return m_params.ignoreCount;
break;
case BreakpointThreadsColumn:
if (role == Qt::DisplayRole)
return BreakHandler::displayFromThreadSpec(m_params.threadSpec);
if (role == Qt::ToolTipRole)
return Tr::tr("Breakpoint will only be hit in the specified thread(s).");
if (role == Qt::UserRole + 1)
return BreakHandler::displayFromThreadSpec(m_params.threadSpec);
break;
}
if (role == Qt::ToolTipRole && settings().useToolTipsInBreakpointsView())
return toolTip();
return {};
}
QIcon GlobalBreakpointItem::icon() const
{
// FIXME: This seems to be called on each cursor blink as soon as the
// cursor is near a line with a breakpoint marker (+/- 2 lines or so).
if (m_params.isTracepoint())
return Icons::TRACEPOINT.icon();
if (m_params.type == WatchpointAtAddress)
return Icons::WATCHPOINT.icon();
if (m_params.type == WatchpointAtExpression)
return Icons::WATCHPOINT.icon();
if (!m_params.enabled)
return Icons::BREAKPOINT_DISABLED.icon();
return Icons::BREAKPOINT_PENDING.icon();
}
QPointer<DebuggerEngine> GlobalBreakpointItem::usingEngine() const
{
for (QPointer<DebuggerEngine> engine : EngineManager::engines()) {
for (Breakpoint bp : engine->breakHandler()->breakpoints()) {
if (bp->globalBreakpoint() == this)
return engine;
}
}
return nullptr;
}
int GlobalBreakpointItem::modelId() const
{
return m_modelId;
}
QString GlobalBreakpointItem::displayName() const
{
return QString::number(m_modelId);
}
void GlobalBreakpointItem::deleteBreakpoint()
{
for (QPointer<DebuggerEngine> engine : EngineManager::engines()) {
BreakHandler *handler = engine->breakHandler();
for (Breakpoint bp : handler->breakpoints()) {
if (bp->globalBreakpoint() == this)
handler->removeBreakpoint(bp);
}
}
removeBreakpointFromModel();
}
void GlobalBreakpointItem::removeBreakpointFromModel()
{
delete m_marker;
m_marker = nullptr;
theBreakpointManager->destroyItem(this);
}
void GlobalBreakpointItem::updateLineNumber(int lineNumber)
{
if (m_params.textPosition.line == lineNumber)
return;
m_params.textPosition.line = lineNumber;
update();
}
void GlobalBreakpointItem::updateFileName(const FilePath &fileName)
{
if (m_params.fileName == fileName)
return;
m_params.fileName = fileName;
update();
}
FilePath GlobalBreakpointItem::markerFileName() const
{
// Some heuristics to find a "good" file name.
if (m_params.fileName.exists())
return m_params.fileName.absoluteFilePath();
return m_params.fileName;
}
int GlobalBreakpointItem::markerLineNumber() const
{
return m_params.textPosition.line;
}
void GlobalBreakpointItem::updateMarker()
{
if (usingEngine() != nullptr) {
// Don't show markers that are claimed by engines.
// FIXME: Apart, perhaps, when the engine's reported location does not match?
destroyMarker();
return;
}
const int line = m_params.textPosition.line;
if (m_marker) {
if (m_params.fileName != m_marker->filePath())
m_marker->updateFilePath(m_params.fileName);
if (line != m_marker->lineNumber())
m_marker->move(line);
} else if (!m_params.fileName.isEmpty() && line > 0) {
m_marker = new GlobalBreakpointMarker(this, m_params.fileName, line);
}
}
void GlobalBreakpointItem::setEnabled(bool enabled, bool descend)
{
if (m_params.enabled != enabled) {
m_params.enabled = enabled;
if (m_marker)
m_marker->updateMarker();
update();
}
if (descend) {
for (QPointer<DebuggerEngine> engine : EngineManager::engines()) {
BreakHandler *handler = engine->breakHandler();
for (Breakpoint bp : handler->breakpoints()) {
if (bp->globalBreakpoint() == this)
handler->requestBreakpointEnabling(bp, enabled);
}
}
}
}
void GlobalBreakpointItem::setParameters(const BreakpointParameters &params)
{
if (m_params != params) {
m_params = params;
if (m_marker)
m_marker->updateMarker();
update();
}
}
void GlobalBreakpointItem::destroyMarker()
{
delete m_marker;
m_marker = nullptr;
}
QString GlobalBreakpointItem::toolTip() const
{
QString rc;
QTextStream str(&rc);
str << "<html><body><b>" << Tr::tr("Unclaimed Breakpoint") << "</b>"
<< "<table>"
//<< "<tr><td>" << Tr::tr("ID:") << "</td><td>" << m_id << "</td></tr>"
<< "<tr><td>" << Tr::tr("State:") << "</td><td>"
<< (m_params.enabled ? Tr::tr("Enabled") : Tr::tr("Disabled")) << "<tr><td>"
<< Tr::tr("Breakpoint Type:") << "</td><td>" << typeToString(m_params.type) << "</td></tr>";
if (m_params.type == BreakpointByFunction) {
str << "<tr><td>" << Tr::tr("Function Name:")
<< "</td><td>" << m_params.functionName
<< "</td></tr>";
}
if (m_params.type == BreakpointByFileAndLine) {
str << "<tr><td>" << Tr::tr("File Name:")
<< "</td><td>" << m_params.fileName.toUserOutput()
<< "</td></tr>"
<< "<tr><td>" << Tr::tr("Line Number:")
<< "</td><td>" << m_params.textPosition.line;
}
if (m_params.type == BreakpointByFunction || m_params.type == BreakpointByFileAndLine) {
str << "<tr><td>" << Tr::tr("Module:")
<< "</td><td>" << m_params.module
<< "</td></tr>";
}
str << "<tr><td>" << Tr::tr("Breakpoint Address:") << "</td><td>";
formatAddress(str, m_params.address);
str << "</td></tr>";
if (!m_params.command.isEmpty())
str << "<tr><td>" << Tr::tr("Command:") << "</td><td>" << m_params.command << "</td></tr>";
if (!m_params.message.isEmpty())
str << "<tr><td>" << Tr::tr("Message:") << "</td><td>" << m_params.message << "</td></tr>";
if (!m_params.condition.isEmpty())
str << "<tr><td>" << Tr::tr("Condition:") << "</td><td>" << m_params.condition << "</td></tr>";
if (m_params.ignoreCount)
str << "<tr><td>" << Tr::tr("Ignore Count:") << "</td><td>" << m_params.ignoreCount << "</td></tr>";
if (m_params.threadSpec >= 0)
str << "<tr><td>" << Tr::tr("Thread Specification:") << "</td><td>" << m_params.threadSpec << "</td></tr>";
str << "</table></body></html><hr>";
return rc;
}
//
// BreakpointManager
//
BreakpointManager::BreakpointManager()
{
theBreakpointManager = this;
setHeader({Tr::tr("Debuggee"), Tr::tr("Function"), Tr::tr("File"), Tr::tr("Line"), Tr::tr("Address"),
Tr::tr("Condition"), Tr::tr("Ignore"), Tr::tr("Threads")});
connect(SessionManager::instance(), &SessionManager::sessionLoaded,
this, &BreakpointManager::loadSessionData);
connect(SessionManager::instance(), &SessionManager::aboutToSaveSession,
this, &BreakpointManager::saveSessionData);
}
QAbstractItemModel *BreakpointManager::model()
{
return theBreakpointManager;
}
const GlobalBreakpoints BreakpointManager::globalBreakpoints()
{
GlobalBreakpoints items;
theBreakpointManager->forItemsAtLevel<1>([&items](GlobalBreakpointItem *b) { items.append(b); });
return items;
}
void BreakpointManager::claimBreakpointsForEngine(DebuggerEngine *engine)
{
theBreakpointManager->forItemsAtLevel<1>([&](GlobalBreakpoint gbp) {
engine->breakHandler()->tryClaimBreakpoint(gbp);
gbp->updateMarker();
});
}
GlobalBreakpoint BreakpointManager::createBreakpointHelper(const BreakpointParameters &params)
{
GlobalBreakpoint gbp = new GlobalBreakpointItem;
gbp->m_params = params;
gbp->updateMarker();
theBreakpointManager->rootItem()->appendChild(gbp);
return gbp;
}
GlobalBreakpoint BreakpointManager::findBreakpointByIndex(const QModelIndex &index)
{
return theBreakpointManager->itemForIndexAtLevel<1>(index);
}
GlobalBreakpoints BreakpointManager::findBreakpointsByIndex(const QList<QModelIndex> &list)
{
QSet<GlobalBreakpoint> items;
for (const QModelIndex &index : list) {
if (GlobalBreakpoint gbp = findBreakpointByIndex(index))
items.insert(gbp);
}
return Utils::toList(items);
}
GlobalBreakpoint BreakpointManager::createBreakpoint(const BreakpointParameters &params)
{
GlobalBreakpoint gbp = createBreakpointHelper(params);
for (QPointer<DebuggerEngine> engine : EngineManager::engines())
engine->breakHandler()->tryClaimBreakpoint(gbp);
return gbp;
}
void BreakpointManager::createBreakpointForEngine(const BreakpointParameters &params, DebuggerEngine *engine)
{
GlobalBreakpoint gbp = createBreakpointHelper(params);
engine->breakHandler()->tryClaimBreakpoint(gbp);
}
void BreakpointManager::setOrRemoveBreakpoint(const ContextData &location, const QString &tracePointMessage)
{
QTC_ASSERT(location.isValid(), return);
GlobalBreakpoint gbp = findBreakpointFromContext(location);
if (gbp) {
gbp->deleteBreakpoint();
} else {
BreakpointParameters data;
if (location.type == LocationByFile) {
data.type = BreakpointByFileAndLine;
if (settings().breakpointsFullPathByDefault())
data.pathUsage = BreakpointUseFullPath;
data.tracepoint = !tracePointMessage.isEmpty();
data.message = tracePointMessage;
data.fileName = location.fileName;
data.textPosition = location.textPosition;
} else if (location.type == LocationByAddress) {
data.type = BreakpointByAddress;
data.tracepoint = !tracePointMessage.isEmpty();
data.message = tracePointMessage;
data.address = location.address;
}
BreakpointManager::createBreakpoint(data);
}
}
void BreakpointManager::enableOrDisableBreakpoint(const ContextData &location)
{
QTC_ASSERT(location.isValid(), return);
if (GlobalBreakpoint gbp = findBreakpointFromContext(location))
gbp->setEnabled(!gbp->isEnabled());
else
setOrRemoveBreakpoint(location);
}
GlobalBreakpoint BreakpointManager::findBreakpointFromContext(const ContextData &location)
{
int matchLevel = 0;
GlobalBreakpoint bestMatch;
theBreakpointManager->forItemsAtLevel<1>([&](const GlobalBreakpoint &gbp) {
if (location.type == LocationByFile) {
if (gbp->m_params.isLocatedAt(location.fileName, location.textPosition.line, FilePath())) {
matchLevel = 2;
bestMatch = gbp;
} else if (matchLevel < 2) {
for (const QPointer<DebuggerEngine> &engine : EngineManager::engines()) {
BreakHandler *handler = engine->breakHandler();
for (Breakpoint bp : handler->breakpoints()) {
if (bp->globalBreakpoint() == gbp) {
if (bp->fileName() == location.fileName
&& bp->textPosition() == location.textPosition) {
matchLevel = 1;
bestMatch = gbp;
}
}
}
}
}
} else if (location.type == LocationByAddress) {
if (gbp->m_params.address == location.address) {
matchLevel = 2;
bestMatch = gbp;
}
}
});
return bestMatch;
}
void BreakpointManager::executeAddBreakpointDialog()
{
BreakpointParameters data(BreakpointByFileAndLine);
BreakpointParts parts = NoParts;
BreakpointDialog dialog(~0, ICore::dialogParent());
dialog.setWindowTitle(Tr::tr("Add Breakpoint"));
if (dialog.showDialog(&data, &parts))
BreakpointManager::createBreakpoint(data);
}
QVariant BreakpointManager::data(const QModelIndex &idx, int role) const
{
if (role == BaseTreeView::ItemDelegateRole)
return QVariant::fromValue(new LeftElideDelegate);
return BreakpointManagerModel::data(idx, role);
}
bool BreakpointManager::setData(const QModelIndex &idx, const QVariant &value, int role)
{
if (role == BaseTreeView::ItemActivatedRole) {
if (GlobalBreakpoint bp = findBreakpointByIndex(idx))
gotoLocation(bp);
return true;
}
if (role == BaseTreeView::ItemViewEventRole) {
ItemViewEvent ev = value.value<ItemViewEvent>();
if (ev.as<QContextMenuEvent>())
return contextMenuEvent(ev);
if (auto kev = ev.as<QKeyEvent>(QEvent::KeyPress)) {
if (kev->key() == Qt::Key_Delete || kev->key() == Qt::Key_Backspace) {
QModelIndexList si = ev.currentOrSelectedRows();
const GlobalBreakpoints gbps = findBreakpointsByIndex(si);
for (GlobalBreakpoint gbp : gbps)
gbp->deleteBreakpoint();
// int row = qMin(rowCount() - ids.size() - 1, idx.row());
// setCurrentIndex(index(row, 0)); FIXME
return true;
}
if (kev->key() == Qt::Key_Space) {
const QModelIndexList selectedIds = ev.selectedRows();
if (!selectedIds.isEmpty()) {
const GlobalBreakpoints gbps = findBreakpointsByIndex(selectedIds);
const bool isEnabled = gbps.isEmpty() || gbps.at(0)->isEnabled();
for (GlobalBreakpoint gbp : gbps)
gbp->setEnabled(!isEnabled);
// scheduleSynchronization();
return true;
}
}
}
if (ev.as<QMouseEvent>(QEvent::MouseButtonDblClick)) {
if (GlobalBreakpoint gbp = findBreakpointByIndex(idx)) {
if (idx.column() >= BreakpointAddressColumn)
editBreakpoints({gbp}, ev.view());
else
gotoLocation(gbp);
} else {
BreakpointManager::executeAddBreakpointDialog();
}
return true;
}
}
return false;
}
bool BreakpointManager::contextMenuEvent(const ItemViewEvent &ev)
{
const QModelIndexList selectedIndices = ev.selectedRows();
const GlobalBreakpoints selectedBreakpoints = findBreakpointsByIndex(selectedIndices);
const bool breakpointsEnabled = selectedBreakpoints.isEmpty() || selectedBreakpoints.at(0)->isEnabled();
auto menu = new QMenu;
addAction(this, menu, Tr::tr("Add Breakpoint..."), true, &BreakpointManager::executeAddBreakpointDialog);
addAction(this, menu, Tr::tr("Delete Selected Breakpoints"),
!selectedBreakpoints.isEmpty(),
[selectedBreakpoints] {
for (GlobalBreakpoint gbp : selectedBreakpoints)
gbp->deleteBreakpoint();
});
addAction(this, menu, Tr::tr("Edit Selected Breakpoints..."),
!selectedBreakpoints.isEmpty(),
[this, selectedBreakpoints, ev] { editBreakpoints(selectedBreakpoints, ev.view()); });
addAction(this, menu,
selectedBreakpoints.size() > 1
? breakpointsEnabled ? Tr::tr("Disable Selected Breakpoints") : Tr::tr("Enable Selected Breakpoints")
: breakpointsEnabled ? Tr::tr("Disable Breakpoint") : Tr::tr("Enable Breakpoint"),
!selectedBreakpoints.isEmpty(),
[selectedBreakpoints, breakpointsEnabled] {
for (GlobalBreakpoint gbp : selectedBreakpoints)
gbp->setEnabled(!breakpointsEnabled);
}
);
menu->addSeparator();
addAction(this, menu, Tr::tr("Delete All Breakpoints"),
rowCount() > 0,
&BreakpointManager::executeDeleteAllBreakpointsDialog);
// Delete by file: Find breakpoints of the same file.
GlobalBreakpoints breakpointsInFile;
FilePath file;
if (GlobalBreakpoint gbp = itemForIndexAtLevel<1>(ev.sourceModelIndex())) {
file = gbp->markerFileName();
if (!file.isEmpty()) {
forItemsAtLevel<1>([file, &breakpointsInFile](const GlobalBreakpoint &gbp) {
if (gbp->markerFileName() == file)
breakpointsInFile.append(gbp);
});
}
}
addAction(this, menu, Tr::tr("Delete Breakpoints of \"%1\"").arg(file.toUserOutput()),
Tr::tr("Delete Breakpoints of File"),
breakpointsInFile.size() > 1,
[breakpointsInFile] {
for (GlobalBreakpoint gbp : breakpointsInFile)
gbp->deleteBreakpoint();
});
menu->addSeparator();
menu->addAction(settings().useToolTipsInBreakpointsView.action());
menu->addAction(settings().settingsDialog.action());
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(ev.globalPos());
return true;
}
void BreakpointManager::gotoLocation(const GlobalBreakpoint &gbp) const
{
QTC_ASSERT(gbp, return);
if (IEditor *editor = EditorManager::openEditor(gbp->markerFileName()))
editor->gotoLine(gbp->markerLineNumber(), 0);
}
void BreakpointManager::executeDeleteAllBreakpointsDialog()
{
QMessageBox::StandardButton pressed
= CheckableMessageBox::question(ICore::dialogParent(),
Tr::tr("Remove All Breakpoints"),
Tr::tr("Are you sure you want to remove all breakpoints "
"from all files in the current session?"),
Key("RemoveAllBreakpoints"));
if (pressed != QMessageBox::Yes)
return;
for (GlobalBreakpoint gbp : globalBreakpoints())
gbp->deleteBreakpoint();
}
void BreakpointManager::editBreakpoint(const GlobalBreakpoint &gbp, QWidget *parent)
{
QTC_ASSERT(gbp, return);
BreakpointParts parts = NoParts;
BreakpointParameters params = gbp->requestedParameters();
BreakpointDialog dialog(~0, parent);
if (!dialog.showDialog(&params, &parts))
return;
gbp->destroyMarker();
gbp->deleteBreakpoint();
BreakpointManager::createBreakpoint(params);
}
void BreakpointManager::editBreakpoints(const GlobalBreakpoints &gbps, QWidget *parent)
{
QTC_ASSERT(!gbps.isEmpty(), return);
GlobalBreakpoint gbp = gbps.at(0);
if (gbps.size() == 1) {
editBreakpoint(gbp, parent);
return;
}
// This allows to change properties of multiple breakpoints at a time.
QTC_ASSERT(gbp, return);
BreakpointParameters params = gbp->requestedParameters();
MultiBreakPointsDialog dialog(~0, parent);
dialog.setCondition(params.condition);
dialog.setIgnoreCount(params.ignoreCount);
dialog.setThreadSpec(params.threadSpec);
if (dialog.exec() == QDialog::Rejected)
return;
const QString newCondition = dialog.condition();
const int newIgnoreCount = dialog.ignoreCount();
const int newThreadSpec = dialog.threadSpec();
for (GlobalBreakpoint gbp : gbps) {
QTC_ASSERT(gbp, continue);
BreakpointParameters newParams = gbp->requestedParameters();
newParams.condition = newCondition;
newParams.ignoreCount = newIgnoreCount;
newParams.threadSpec = newThreadSpec;
gbp->destroyMarker();
gbp->deleteBreakpoint();
BreakpointManager::createBreakpoint(newParams);
}
}
void BreakpointManager::saveSessionData()
{
QList<QVariant> list;
theBreakpointManager->forItemsAtLevel<1>([&list](const GlobalBreakpoint &bp) {
const BreakpointParameters &params = bp->m_params;
QMap<QString, QVariant> map;
if (params.type != BreakpointByFileAndLine)
map.insert("type", params.type);
if (!params.fileName.isEmpty())
map.insert("filename", params.fileName.toSettings());
if (params.textPosition.line)
map.insert("linenumber", params.textPosition.line);
if (!params.functionName.isEmpty())
map.insert("funcname", params.functionName);
if (params.address)
map.insert("address", params.address);
if (!params.condition.isEmpty())
map.insert("condition", params.condition);
if (params.ignoreCount)
map.insert("ignorecount", params.ignoreCount);
if (params.threadSpec >= 0)
map.insert("threadspec", params.threadSpec);
if (!params.enabled)
map.insert("disabled", "1");
if (params.oneShot)
map.insert("oneshot", "1");
if (params.pathUsage != BreakpointPathUsageEngineDefault)
map.insert("usefullpath", QString::number(params.pathUsage));
if (params.tracepoint)
map.insert("tracepoint", "1");
if (!params.module.isEmpty())
map.insert("module", params.module);
if (!params.command.isEmpty())
map.insert("command", params.command);
if (!params.expression.isEmpty())
map.insert("expression", params.expression);
if (!params.message.isEmpty())
map.insert("message", params.message);
list.append(map);
});
SessionManager::setValue("Breakpoints", list);
}
void BreakpointManager::loadSessionData()
{
clear();
const QVariant value = SessionManager::value("Breakpoints");
const QList<QVariant> list = value.toList();
for (const QVariant &var : list) {
const QMap<QString, QVariant> map = var.toMap();
BreakpointParameters params(BreakpointByFileAndLine);
QVariant v = map.value("filename");
if (v.isValid())
params.fileName = FilePath::fromSettings(v);
v = map.value("linenumber");
if (v.isValid())
params.textPosition.line = v.toString().toInt();
v = map.value("condition");
if (v.isValid())
params.condition = v.toString();
v = map.value("address");
if (v.isValid())
params.address = v.toString().toULongLong();
v = map.value("ignorecount");
if (v.isValid())
params.ignoreCount = v.toString().toInt();
v = map.value("threadspec");
if (v.isValid())
params.threadSpec = v.toString().toInt();
v = map.value("funcname");
if (v.isValid())
params.functionName = v.toString();
v = map.value("disabled");
if (v.isValid())
params.enabled = !v.toInt();
v = map.value("oneshot");
if (v.isValid())
params.oneShot = v.toInt();
v = map.value("usefullpath");
if (v.isValid())
params.pathUsage = static_cast<BreakpointPathUsage>(v.toInt());
v = map.value("tracepoint");
if (v.isValid())
params.tracepoint = bool(v.toInt());
v = map.value("type");
if (v.isValid() && v.toInt() != UnknownBreakpointType)
params.type = BreakpointType(v.toInt());
v = map.value("module");
if (v.isValid())
params.module = v.toString();
v = map.value("command");
if (v.isValid())
params.command = v.toString();
v = map.value("expression");
if (v.isValid())
params.expression = v.toString();
v = map.value("message");
if (v.isValid())
params.message = v.toString();
if (params.isValid())
BreakpointManager::createBreakpoint(params);
else
qWarning("Not restoring invalid breakpoint: %s", qPrintable(params.toString()));
}
}
} // namespace Internal
} // namespace Debugger