diff options
Diffstat (limited to 'plugins/warp/ui/shared/function.cpp')
| -rw-r--r-- | plugins/warp/ui/shared/function.cpp | 365 |
1 files changed, 365 insertions, 0 deletions
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)) + }); + } +} |
