diff options
Diffstat (limited to 'plugins/warp/ui/shared')
| -rw-r--r-- | plugins/warp/ui/shared/constraint.cpp | 118 | ||||
| -rw-r--r-- | plugins/warp/ui/shared/constraint.h | 78 | ||||
| -rw-r--r-- | plugins/warp/ui/shared/function.cpp | 365 | ||||
| -rw-r--r-- | plugins/warp/ui/shared/function.h | 168 | ||||
| -rw-r--r-- | plugins/warp/ui/shared/misc.cpp | 81 | ||||
| -rw-r--r-- | plugins/warp/ui/shared/misc.h | 84 |
6 files changed, 894 insertions, 0 deletions
diff --git a/plugins/warp/ui/shared/constraint.cpp b/plugins/warp/ui/shared/constraint.cpp new file mode 100644 index 00000000..1d369bcd --- /dev/null +++ b/plugins/warp/ui/shared/constraint.cpp @@ -0,0 +1,118 @@ +#include "constraint.h" + +#include <QGridLayout> +#include <QHeaderView> + +WarpConstraintItem::WarpConstraintItem(const Warp::Constraint &constraint) : m_constraint(constraint) +{ + QString guidStr = QString::fromStdString(constraint.guid.ToString()); + if (const auto offset = constraint.offset; offset) + guidStr += QString(" @ %1").arg(*offset, 0, 16); + setText(guidStr); +} + +WarpConstraintItemModel::WarpConstraintItemModel(const QStringList &labels, QObject *parent) +{ + this->setHorizontalHeaderLabels(labels); +} + +void WarpConstraintItemModel::AddItem(WarpConstraintItem *item) +{ + QList<QStandardItem *> row = {}; + row.insert(COL_CONSTRAINT_ITEM, item); + appendRow(row); +} + +WarpConstraintItem *WarpConstraintItemModel::GetItem(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + return dynamic_cast<WarpConstraintItem *>(item(index.row(), COL_CONSTRAINT_ITEM)); +} + +QVariant WarpConstraintItemModel::data(const QModelIndex &index, int role) const +{ + // Highlight constraints that are found in analysis. + if (role == Qt::BackgroundRole) + { + if (const auto item = GetItem(index); item) + { + auto itemConstraint = item->GetConstraint(); + // TODO: We really should store the guid in a hashmap or something instead of looping over it for every item. + // TODO: A less intense green? + // TODO: Take into account the constraint offset. + for (const auto &constraint: m_matchedConstraints) + if (constraint.guid == itemConstraint.guid) + return QBrush(Qt::green); + } + } + + return QStandardItemModel::data(index, role); +} + +WarpConstraintTableWidget::WarpConstraintTableWidget(QWidget *parent) +{ + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(2, 2, 2, 2); + layout->setVerticalSpacing(4); + + m_table = new QTableView(this); + m_model = new WarpConstraintItemModel({"Constraint"}, this); + m_proxyModel = new GenericTextFilterModel(this); + m_proxyModel->setSourceModel(m_model); + m_table->setModel(m_proxyModel); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_table, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search constraints"); + + layout->addWidget(m_filterEdit, 0, 0, 1, 5); + layout->addWidget(m_table, 1, 0, 1, 5); + + // Make the table look nice. + m_table->horizontalHeader()->setStretchLastSection(true); + m_table->verticalHeader()->hide(); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->setFocusPolicy(Qt::NoFocus); + m_table->setShowGrid(false); + m_table->setAlternatingRowColors(false); + m_table->setSortingEnabled(true); + // NOTE: We only have a single column right now, so disable the header. + m_table->horizontalHeader()->hide(); + // Decrease row height to make it look nice. + m_table->verticalHeader()->setDefaultSectionSize(30); +} + +void WarpConstraintTableWidget::SetConstraints(QVector<WarpConstraintItem *> constraints) +{ + // Clear matches as they are no longer valid. + m_model->clear(); + m_model->setRowCount(0); + + // Temporarily disable sorting so we can add rows faster + m_table->setModel(m_model); + m_table->setSortingEnabled(false); + m_table->setEnabled(false); + + for (const auto &constraint: constraints) + m_model->AddItem(constraint); + + // We are done, re-enable table. + m_table->setEnabled(true); + m_table->setModel(m_proxyModel); + m_table->setSortingEnabled(true); +} + +void WarpConstraintTableWidget::SetMatchedConstraints( + const std::vector<Warp::Constraint> &analysisConstraints) +{ + m_model->SetMatchedConstraints(analysisConstraints); +} + +void WarpConstraintTableWidget::setFilter(const std::string &filter) +{ + m_proxyModel->setFilterFixedString(QString::fromStdString(filter)); + m_filterView->showFilter(QString::fromStdString(filter)); +} diff --git a/plugins/warp/ui/shared/constraint.h b/plugins/warp/ui/shared/constraint.h new file mode 100644 index 00000000..2f064836 --- /dev/null +++ b/plugins/warp/ui/shared/constraint.h @@ -0,0 +1,78 @@ +#pragma once +#include <qstandarditemmodel.h> +#include <QTableView> +#include <QWidget> + +#include "filter.h" +#include "misc.h" +#include "warp.h" + +class WarpConstraintItem : public QStandardItem +{ + Warp::Constraint m_constraint; + +public: + WarpConstraintItem(const Warp::Constraint &constraint); + + Warp::Constraint GetConstraint() { return m_constraint; } +}; + +class WarpConstraintItemModel : public QStandardItemModel +{ + Q_OBJECT + + // The current analysis constraints used to highlight matching constraints. + std::vector<Warp::Constraint> m_matchedConstraints; + +public: + WarpConstraintItemModel(const QStringList &labels, QObject *parent); + + static constexpr int COL_CONSTRAINT_ITEM = 0; + + void AddItem(WarpConstraintItem *item); + + WarpConstraintItem *GetItem(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role) const override; + + void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints) + { + m_matchedConstraints = analysisConstraints; + } +}; + +class WarpConstraintTableWidget : public QWidget, public FilterTarget +{ + Q_OBJECT + + QTableView *m_table; + WarpConstraintItemModel *m_model; + GenericTextFilterModel *m_proxyModel; + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + +public: + explicit WarpConstraintTableWidget(QWidget *parent = nullptr); + + void SetConstraints(QVector<WarpConstraintItem *> constraints); + + void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints); + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override + { + } + + void scrollToCurrentItem() override + { + } + + void selectFirstItem() override + { + } + + void activateFirstItem() override + { + } +}; diff --git a/plugins/warp/ui/shared/function.cpp b/plugins/warp/ui/shared/function.cpp new file mode 100644 index 00000000..2202fb4e --- /dev/null +++ b/plugins/warp/ui/shared/function.cpp @@ -0,0 +1,365 @@ +#include "theme.h" + +#include "function.h" + +#include <QClipboard> +#include <QGridLayout> +#include <QHeaderView> + +#include "constraint.h" +#include "misc.h" + +WarpFunctionItem::WarpFunctionItem(Warp::Ref<Warp::Function> function, + BinaryNinja::Ref<BinaryNinja::Function> analysisFunction) +{ + m_function = function; + + // TODO: This needs to be better. Symbol can be nullptr. + BinaryNinja::Ref<BinaryNinja::Symbol> symbol = m_function->GetSymbol(*analysisFunction); + std::string symbolName = symbol->GetShortName(); + setText(QString::fromStdString(symbolName)); + BinaryNinja::InstructionTextToken nameToken = {255, TextToken, symbolName}; + + // Serialize the tokens to make it accessible via QModelIndex. + // We will take these tokens and then user them in our custom item delegate. + TokenData tokenData = {}; + + // TODO: Make this not look like garbage + BinaryNinja::Ref<BinaryNinja::Type> type = m_function->GetType(*analysisFunction); + if (type) + { + BinaryNinja::Ref<BinaryNinja::Platform> platform = analysisFunction->GetPlatform(); + std::vector<BinaryNinja::InstructionTextToken> beforeTokens = type->GetTokensBeforeName(platform); + std::vector<BinaryNinja::InstructionTextToken> afterTokens = type->GetTokensAfterName(platform); + + for (const auto &token: beforeTokens) + tokenData.tokens.emplace_back(token); + tokenData.tokens.emplace_back(255, TextToken, " "); + tokenData.tokens.emplace_back(nameToken); + for (const auto &token: afterTokens) + tokenData.tokens.emplace_back(token); + } else + { + tokenData.tokens.emplace_back(nameToken); + } + + setData(QVariant::fromValue(tokenData), Qt::UserRole); +} + +void WarpFunctionItem::SetContainer(const Warp::Ref<Warp::Container> &container) +{ + m_container = container; + + // Add the container string to data so the filter model picks it up. + auto containerName = m_container->GetName(); + setData(QString::fromStdString(containerName), Qt::UserRole + 2); +} + +void WarpFunctionItem::SetSource(Warp::Source source) +{ + m_source = source; + + // Add the source string to data so the filter model picks it up. + std::string sourceStr = m_source->ToString(); + setData(QString::fromStdString(sourceStr), Qt::UserRole + 1); +} + +WarpFunctionItemModel::WarpFunctionItemModel(const QStringList &labels, QObject *parent) +{ + this->setHorizontalHeaderLabels(labels); +} + +void WarpFunctionItemModel::AppendFunction(WarpFunctionItem *item) +{ + QList<QStandardItem *> row = {}; + row.insert(COL_FUNCTION_ITEM, item); + appendRow(row); +} + +void WarpFunctionItemModel::InsertFunction(uint64_t address, WarpFunctionItem *item) +{ + // Update item if already available, this lets us keep the model + const auto iter = m_insertableFunctionRows.find(address); + if (iter != m_insertableFunctionRows.end()) + { + setItem(iter->second, COL_FUNCTION_ITEM, item); + return; + } + + AppendFunction(item); + m_insertableFunctionRows[address] = rowCount() - 1; +} + +WarpFunctionItem *WarpFunctionItemModel::GetItem(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + return dynamic_cast<WarpFunctionItem *>(item(index.row(), COL_FUNCTION_ITEM)); +} + +std::optional<uint64_t> WarpFunctionItemModel::GetAddress(const QModelIndex &index) const +{ + if (!index.isValid()) + return std::nullopt; + // TODO: This is a hack, this means we must enumerate all rows to get the address. + for (const auto &[addr, row]: m_insertableFunctionRows) + if (row == index.row()) + return addr; + return std::nullopt; +} + +QVariant WarpFunctionItemModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::BackgroundRole) + { + auto itemFunction = GetItem(index); + // Check if we have a valid item and it's the matched function + if (m_matchedFunction && itemFunction) + { + // TODO: Why wont == go to the correct call??? + if (BNWARPFunctionsEqual(itemFunction->GetFunction()->m_object, m_matchedFunction->m_object)) + { + // TODO: Better color? + QColor matchedColor = getThemeColor(BlueStandardHighlightColor); + matchedColor.setAlpha(128); + return matchedColor; + } + } + } + + if (role == Qt::DisplayRole) + { + // We really only use this for searching as we have TokenData for our delegate. + WarpFunctionItem *item = GetItem(index); + if (!item) + return QVariant(); + TokenData tokenData = item->data(Qt::UserRole).value<TokenData>(); + // Add the function guid so we can filter by that. + QString text = tokenData.toString() + " " + QString::fromStdString(item->GetFunction()->GetGUID().ToString()); + if (auto source = item->GetSource(); source) + { + // Add the source guid so we can also filter by that. + std::string sourceStr = source->ToString(); + text = text + " " + QString::fromStdString(sourceStr); + } + return text; + } + + return QStandardItemModel::data(index, role); +} + +bool WarpFunctionFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QString filterString = filterRegularExpression().pattern(); + if (filterString.isEmpty()) + return true; + + // Filter on the first column only, this contains our actual function. + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + auto data = QRegularExpression::escape(index.data().toString()); + if (data.contains(filterString, Qt::CaseInsensitive)) + return true; + return false; +} + +bool WarpFunctionFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const +{ + // TODO: When we make the stuff _actually_ sortable. + return sourceLeft.row() < sourceRight.row(); +} + +WarpFunctionTableWidget::WarpFunctionTableWidget(QWidget *parent) : QWidget(parent) +{ + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(2); + + m_table = new QTableView(this); + m_model = new WarpFunctionItemModel({"Function"}, this); + m_proxyModel = new WarpFunctionFilterModel(this); + m_proxyModel->setSourceModel(m_model); + m_table->setModel(m_proxyModel); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_table, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search functions (By GUID, name or source)"); + + layout->addWidget(m_filterEdit, 0, 0, 1, 5); + layout->addWidget(m_table, 1, 0, 1, 5); + + // Make the table look nice. + m_table->horizontalHeader()->setStretchLastSection(true); + m_table->verticalHeader()->hide(); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->setFocusPolicy(Qt::NoFocus); + m_table->setShowGrid(false); + m_table->setAlternatingRowColors(false); + m_table->setSortingEnabled(true); + // NOTE: We only have a single column right now, so disable header. + m_table->horizontalHeader()->hide(); + // Decrease row height to make it look nice. + m_table->verticalHeader()->setDefaultSectionSize(30); + + TokenDataDelegate *tokenDelegate = new TokenDataDelegate(this); + // NOTE: Column 0 is assumed to be the function with the token data. + m_table->setItemDelegateForColumn(0, tokenDelegate); + + AddressColorDelegate *addressDelegate = new AddressColorDelegate(this); + // NOTE: Column 1 is assumed to be the function address. + m_table->setItemDelegateForColumn(1, addressDelegate); + + // Add a dynamic context menu to the table. + // NOTE: This is a bit stupid, I am sure there is a better way to do this in QT. + m_contextMenu = new QMenu(this); + RegisterContextMenuAction("Copy Name", [](WarpFunctionItem *item, std::optional<uint64_t>) { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(item->text()); + }); + RegisterContextMenuAction("Copy GUID", [](WarpFunctionItem *item, std::optional<uint64_t>) { + QClipboard *clipboard = QGuiApplication::clipboard(); + Warp::Ref<Warp::Function> function = item->GetFunction(); + std::string guidStr = function->GetGUID().ToString(); + clipboard->setText(QString::fromStdString(guidStr)); + }); + + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_table, &QTableView::customContextMenuRequested, this, [&](QPoint pos) { + const QModelIndex index = m_table->indexAt(pos); + if (!index.isValid()) + return; + const QModelIndex sourceIndex = m_proxyModel->mapToSource(index); + WarpFunctionItem *item = m_model->GetItem(sourceIndex); + if (!item || !item->GetFunction()) + return; + + // Execute the menu and get the selected action + const QAction *selectedAction = m_contextMenu->exec(m_table->viewport()->mapToGlobal(pos)); + if (!selectedAction) + return; + + const auto name = selectedAction->text(); + const auto iter = m_contextMenuActions.find(name); + if (iter != m_contextMenuActions.end()) + iter->second(item, m_model->GetAddress(sourceIndex)); + }); +} + +void WarpFunctionTableWidget::RegisterContextMenuAction(const QString &name, + const std::function<void( + WarpFunctionItem *, std::optional<uint64_t>)> &callback) +{ + m_contextMenu->addAction(name); + m_contextMenuActions[name] = callback; +} + +void WarpFunctionTableWidget::SetFunctions(QVector<WarpFunctionItem *> functions) +{ + // Clear matches as they are no longer valid. + m_model->clear(); + m_model->setRowCount(0); + + // Temporarily disable sorting so we can add rows faster + m_table->setModel(m_model); + m_table->setSortingEnabled(false); + m_table->setEnabled(false); + + for (const auto &function: functions) + m_model->AppendFunction(function); + + // We are done, re-enable table. + m_table->setEnabled(true); + m_table->setModel(m_proxyModel); + m_table->setSortingEnabled(true); + + // Update the filter text with the new count of functions. + m_filterView->setFilterPlaceholderText(QString("Search %1 functions").arg(m_model->rowCount())); +} + +void WarpFunctionTableWidget::InsertFunction(uint64_t address, WarpFunctionItem *function) +{ + m_model->InsertFunction(address, function); +} + +void WarpFunctionTableWidget::setFilter(const std::string &filter) +{ + m_proxyModel->setFilterFixedString(QString::fromStdString(filter)); + m_filterView->showFilter(QString::fromStdString(filter)); +} + +WarpFunctionInfoWidget::WarpFunctionInfoWidget(QWidget *parent) + : QWidget(parent) +{ + // Create a tab widget + QTabWidget *tabWidget = new QTabWidget(this); + tabWidget->setContentsMargins(0, 0, 0, 0); + + // Create tables for the "Constraints", "Comments", and "Variables" tabs + m_commentsTable = new QTableView(this); + // m_variablesTable = new QTableView(this); + + // TODO: On click navigate to where the constraint is located. + m_constraintsTable = new WarpConstraintTableWidget(this); + tabWidget->addTab(m_constraintsTable, "Constraints"); + + // Set up comments tab + m_commentsModel = new QStandardItemModel(this); + m_commentsModel->setHorizontalHeaderLabels({"Offset", "Text"}); + m_commentsModel->setColumnCount(2); + m_commentsTable->setModel(m_commentsModel); + m_commentsTable->horizontalHeader()->setStretchLastSection(true); + m_commentsTable->horizontalHeader()->setSelectionBehavior(QAbstractItemView::SelectRows); + m_commentsTable->horizontalHeader()->setSelectionMode(QAbstractItemView::SingleSelection); + m_commentsTable->horizontalHeader()->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_commentsTable->verticalHeader()->hide(); + m_commentsTable->horizontalHeader()->hide(); + tabWidget->addTab(m_commentsTable, "Comments"); + + // Set up variables tab + // m_variablesTable->setModel(new QStandardItemModel(this)); + // TODO: Add variables to data. + // tabWidget->addTab(m_variablesTable, "Variables"); + + // Add the tab widget to this widget's layout + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tabWidget); + + setLayout(layout); +} + +void WarpFunctionInfoWidget::UpdateInfo() +{ + m_commentsModel->clear(); + m_commentsModel->setRowCount(0); + m_constraintsTable->SetMatchedConstraints({}); + m_constraintsTable->SetConstraints({}); + + Warp::Ref<Warp::Function> function = GetFunction(); + if (!function) + return; + + // Set the analysis constraints if there is an analysis function. + if (const auto analysisFunction = GetAnalysisFunction()) + { + const auto analysisConstraints = Warp::Function::Get(*analysisFunction)->GetConstraints(); + m_constraintsTable->SetMatchedConstraints(analysisConstraints); + } + + // Add all the constraints for the current function to the model. + QVector<WarpConstraintItem *> constraints; + for (const auto &constraint: function->GetConstraints()) + constraints.push_back(new WarpConstraintItem(constraint)); + m_constraintsTable->SetConstraints(constraints); + + // Add all the comments to the model. + for (const auto &comment: function->GetComments()) + { + m_commentsModel->appendRow({ + new QStandardItem(QString("0x%1").arg(comment.offset, 0, 16)), + new QStandardItem(QString::fromStdString(comment.text)) + }); + } +} diff --git a/plugins/warp/ui/shared/function.h b/plugins/warp/ui/shared/function.h new file mode 100644 index 00000000..90d52092 --- /dev/null +++ b/plugins/warp/ui/shared/function.h @@ -0,0 +1,168 @@ +#pragma once +#include <QStandardItemModel> +#include <QTableView> + +#include "binaryninjaapi.h" +#include "constraint.h" +#include "filter.h" +#include "misc.h" +#include "warp.h" + +class WarpFunctionItem : public QStandardItem +{ + Warp::Ref<Warp::Function> m_function; + + // Optional attached data used to show/manage the function. + Warp::Ref<Warp::Container> m_container; + std::optional<Warp::Source> m_source; + +public: + WarpFunctionItem(Warp::Ref<Warp::Function> function, + BinaryNinja::Ref<BinaryNinja::Function> analysisFunction); + + void SetContainer(const Warp::Ref<Warp::Container> &container); + + void SetSource(Warp::Source source); + + Warp::Ref<Warp::Function> GetFunction() { return m_function; } + Warp::Ref<Warp::Container> GetContainer() { return m_container; } + std::optional<Warp::Source> GetSource() { return m_source; } +}; + +class WarpFunctionItemModel : public QStandardItemModel +{ + Q_OBJECT + + // The current matched function, used to highlight currently. + Warp::Ref<Warp::Function> m_matchedFunction; + + // Mapping of function start address to the row index. + // This is used to identify unique functions for updating instead of resetting the entire model. + std::unordered_map<uint64_t, int> m_insertableFunctionRows; + +public: + WarpFunctionItemModel(const QStringList &labels, QObject *parent); + + static constexpr int COL_FUNCTION_ITEM = 0; + static constexpr int COL_ADDRESS_ITEM = 1; + + void AppendFunction(WarpFunctionItem *item); + + void InsertFunction(uint64_t address, WarpFunctionItem *item); + + WarpFunctionItem *GetItem(const QModelIndex &index) const; + + std::optional<uint64_t> GetAddress(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role) const override; + + void SetMatchedFunction(const Warp::Ref<Warp::Function> &matchedFunction) + { + Warp::Ref<Warp::Function> previousMatchedFunction = m_matchedFunction; + m_matchedFunction = matchedFunction; + + // Make sure to refresh the highlights so we don't keep the highlights from the previous function. + if (previousMatchedFunction) + { + const QModelIndex topLeft = index(0, 0); + const QModelIndex bottomRight = index(rowCount() - 1, 0); + emit dataChanged(topLeft, bottomRight); + } + } +}; + +class WarpFunctionFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + WarpFunctionFilterModel(QObject *parent): QSortFilterProxyModel(parent) + { + } + + ~WarpFunctionFilterModel() override = default; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override; +}; + +class WarpFunctionTableWidget : public QWidget, public FilterTarget +{ + Q_OBJECT + + QTableView *m_table; + WarpFunctionItemModel *m_model; + WarpFunctionFilterModel *m_proxyModel; + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + QMenu *m_contextMenu; + std::map<QString, std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> > m_contextMenuActions; + +public: + explicit WarpFunctionTableWidget(QWidget *parent = nullptr); + + // TODO: Invert this and provide OnCallback functions that wrap the connect call. + QTableView *GetTableView() const { return m_table; } + WarpFunctionItemModel *GetModel() const { return m_model; } + WarpFunctionFilterModel *GetProxyModel() const { return m_proxyModel; } + + void RegisterContextMenuAction(const QString &name, + const std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> &callback); + + void SetFunctions(QVector<WarpFunctionItem *> functions); + + void InsertFunction(uint64_t address, WarpFunctionItem *function); + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override + { + } + + void scrollToCurrentItem() override + { + } + + void selectFirstItem() override + { + } + + void activateFirstItem() override + { + } +}; + +class WarpFunctionInfoWidget : public QWidget +{ + Q_OBJECT + + Warp::Ref<Warp::Function> m_function; + BinaryNinja::Ref<BinaryNinja::Function> m_analysisFunction; + + // Optionally provide this information to show the source information. + Warp::Ref<Warp::Container> m_container; + std::string source; + + WarpConstraintTableWidget *m_constraintsTable; + + QTableView *m_commentsTable; + QStandardItemModel *m_commentsModel; + + QTableView *m_variablesTable; + +public: + explicit WarpFunctionInfoWidget(QWidget *parent = nullptr); + + Warp::Ref<Warp::Function> GetFunction() { return m_function; } + void SetFunction(Warp::Ref<Warp::Function> function) { m_function = function; }; + + void SetAnalysisFunction(BinaryNinja::Ref<BinaryNinja::Function> analysisFunction) + { + m_analysisFunction = analysisFunction; + }; + BinaryNinja::Ref<BinaryNinja::Function> GetAnalysisFunction() { return m_analysisFunction; } + + // TODO: Make this private? + void UpdateInfo(); +}; diff --git a/plugins/warp/ui/shared/misc.cpp b/plugins/warp/ui/shared/misc.cpp new file mode 100644 index 00000000..71147079 --- /dev/null +++ b/plugins/warp/ui/shared/misc.cpp @@ -0,0 +1,81 @@ +#include "misc.h" + +#include <QGridLayout> +#include <QHeaderView> + +#include "action.h" +#include "fontsettings.h" +#include "render.h" +#include "theme.h" + +void TokenDataDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + painter->save(); + + auto tokenData = index.data(Qt::UserRole).value<TokenData>(); + + // Draw either the selected row or background color. + QVariant background = index.data(Qt::BackgroundRole); + if (background.canConvert<QBrush>()) + painter->fillRect(option.rect, background.value<QBrush>()); + else if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + painter->translate(option.rect.topLeft()); + + + auto renderContext = RenderContext((QWidget *) option.widget); + renderContext.init(*painter); + HighlightTokenState highlightState; + renderContext.drawDisassemblyLine(*painter, 5, 5, {tokenData.tokens.begin(), tokenData.tokens.end()}, + highlightState); + + painter->restore(); +} + +QSize TokenDataDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto tokenData = index.data(Qt::UserRole).value<TokenData>(); + auto renderContext = RenderContext((QWidget *) option.widget); + QFontMetrics fontMetrics = QFontMetrics(renderContext.getFont()); + QString line = ""; + for (const auto &token: tokenData.tokens) + line += token.text; + int width = qMax(0, fontMetrics.horizontalAdvance(line)); + return QSize(width, renderContext.getFontHeight()); +} + +void AddressColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + opt.font = getMonospaceFont(qobject_cast<QWidget *>(parent())); + opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); + opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; + + QStyledItemDelegate::paint(painter, opt, index); +} + +bool GenericTextFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + auto filterString = filterRegularExpression().pattern(); + if (filterString.isEmpty()) + return true; + + for (int i = 0; i < sourceModel()->columnCount(); i++) + { + auto index = sourceModel()->index(sourceRow, i, sourceParent); + auto data = QRegularExpression::escape(index.data().toString()); + if (data.contains(filterString, Qt::CaseInsensitive)) + return true; + } + + return false; +} + +bool GenericTextFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const +{ + auto leftData = sourceLeft.data().toString(); + auto rightData = sourceRight.data().toString(); + return QString::localeAwareCompare(leftData, rightData) < 0; +} diff --git a/plugins/warp/ui/shared/misc.h b/plugins/warp/ui/shared/misc.h new file mode 100644 index 00000000..d2e6a1a8 --- /dev/null +++ b/plugins/warp/ui/shared/misc.h @@ -0,0 +1,84 @@ +#pragma once +#include <qmetatype.h> +#include <QSortFilterProxyModel> +#include <qstandarditemmodel.h> +#include <QStyledItemDelegate> +#include <QTableView> +#include <QVector> + +#include "binaryninjaapi.h" +#include "filter.h" + +// Used to serialize into the item data for rendering with TokenDataDelegate. +struct TokenData +{ + QVector<BinaryNinja::InstructionTextToken> tokens{}; + + TokenData() = default; + + TokenData(const std::vector<BinaryNinja::InstructionTextToken> &tokens) + { + for (const auto &token: tokens) + this->tokens.push_back(token); + } + + TokenData(const BinaryNinja::InstructionTextToken &token) + { + this->tokens.push_back(token); + } + + QString toString() const + { + QStringList tokenStrings; + for (const auto &token: tokens) + { + tokenStrings.append(QString::fromStdString(token.text)); + } + return tokenStrings.join(""); + } +}; + +Q_DECLARE_METATYPE(TokenData) + +class TokenDataDelegate final : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit TokenDataDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +class AddressColorDelegate final : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit AddressColorDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + + +class GenericTextFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GenericTextFilterModel(QObject *parent): QSortFilterProxyModel(parent) + { + } + + ~GenericTextFilterModel() override = default; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override; +}; |
