cutter/src/widgets/FunctionsWidget.cpp

541 lines
17 KiB
C++
Raw Normal View History

#include "FunctionsWidget.h"
#include "ui_FunctionsWidget.h"
2017-10-01 19:09:42 +00:00
#include "MainWindow.h"
#include "utils/Helpers.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/RenameDialog.h"
#include "dialogs/XrefsDialog.h"
#include <algorithm>
#include <QMenu>
#include <QDebug>
#include <QString>
#include <QResource>
#include <QShortcut>
#include <QJsonObject>
2018-03-21 20:32:32 +00:00
FunctionModel::FunctionModel(QList<FunctionDescription> *functions, QSet<RVA> *importAddresses,
ut64 *mainAdress, bool nested, QFont default_font, QFont highlight_font, QObject *parent)
: QAbstractItemModel(parent),
functions(functions),
2018-02-09 14:03:03 +00:00
importAddresses(importAddresses),
mainAdress(mainAdress),
2018-02-09 14:03:03 +00:00
highlightFont(highlight_font),
defaultFont(default_font),
nested(nested),
2018-02-09 14:03:03 +00:00
currentIndex(-1)
{
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(seekChanged(RVA)));
2018-03-21 20:32:32 +00:00
connect(Core(), SIGNAL(functionRenamed(const QString &, const QString &)), this,
SLOT(functionRenamed(QString, QString)));
}
QModelIndex FunctionModel::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid())
return createIndex(row, column, (quintptr)0); // root function nodes have id = 0
2018-03-21 20:32:32 +00:00
return createIndex(row, column,
(quintptr)(parent.row() + 1)); // sub-nodes have id = function index + 1
}
QModelIndex FunctionModel::parent(const QModelIndex &index) const
{
if (!index.isValid() || index.column() != 0)
return QModelIndex();
if (index.internalId() == 0) // root function node
return QModelIndex();
else // sub-node
return this->index((int)(index.internalId() - 1), 0);
}
int FunctionModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return functions->count();
2018-03-21 20:32:32 +00:00
if (nested) {
if (parent.internalId() == 0)
return 3; // sub-nodes for nested functions
return 0;
2018-03-21 20:32:32 +00:00
} else
return 0;
}
int FunctionModel::columnCount(const QModelIndex &/*parent*/) const
{
if (nested)
return 1;
else
2017-11-04 15:28:02 +00:00
return ColumnCount;
}
bool FunctionModel::functionIsImport(ut64 addr) const
{
2018-02-09 14:03:03 +00:00
return importAddresses->contains(addr);
}
bool FunctionModel::functionIsMain(ut64 addr) const
{
return *mainAdress == addr;
}
QVariant FunctionModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int function_index;
bool subnode;
2018-03-21 20:32:32 +00:00
if (index.internalId() != 0) { // sub-node
function_index = index.parent().row();
subnode = true;
2018-03-21 20:32:32 +00:00
} else { // root function node
function_index = index.row();
subnode = false;
}
const FunctionDescription &function = functions->at(function_index);
if (function_index >= functions->count())
return QVariant();
2018-03-21 20:32:32 +00:00
switch (role) {
case Qt::DisplayRole:
2018-03-21 20:32:32 +00:00
if (nested) {
if (subnode) {
switch (index.row()) {
case 0:
return tr("Offset: %1").arg(RAddressString(function.offset));
case 1:
return tr("Size: %1").arg(RSizeString(function.size));
case 2:
return tr("Import: %1").arg(functionIsImport(function.offset) ? tr("true") : tr("false"));
default:
return QVariant();
}
2018-03-21 20:32:32 +00:00
} else
return function.name;
2018-03-21 20:32:32 +00:00
} else {
switch (index.column()) {
2017-11-04 15:28:02 +00:00
case NameColumn:
2017-10-13 13:53:23 +00:00
return function.name;
2017-11-04 15:28:02 +00:00
case SizeColumn:
return function.size;
2017-11-04 15:28:02 +00:00
case OffsetColumn:
2017-10-13 13:53:23 +00:00
return RAddressString(function.offset);
default:
return QVariant();
}
}
case Qt::DecorationRole:
2018-02-09 14:03:03 +00:00
if (importAddresses->contains(function.offset) &&
2017-11-04 15:28:02 +00:00
(nested ? false : index.column() == ImportColumn))
return QIcon(":/img/icons/import_light.svg");
return QVariant();
case Qt::FontRole:
2018-02-09 14:03:03 +00:00
if (currentIndex == function_index)
return highlightFont;
return defaultFont;
case Qt::TextAlignmentRole:
if (index.column() == 1)
return static_cast<int>(Qt::AlignRight | Qt::AlignVCenter);
return static_cast<int>(Qt::AlignLeft | Qt::AlignVCenter);
2018-03-21 20:32:32 +00:00
case Qt::ToolTipRole: {
2017-10-09 18:08:35 +00:00
QList<QString> info = CutterCore::getInstance()->cmd("afi @ " + function.name).split("\n");
2018-03-21 20:32:32 +00:00
if (info.length() > 2) {
QString size = info[4].split(" ")[1];
QString complex = info[8].split(" ")[1];
QString bb = info[11].split(" ")[1];
return QString("Summary:\n\n Size: " + size +
"\n Cyclomatic complexity: " + complex +
"\n Basic blocks: " + bb +
2017-10-09 18:08:35 +00:00
"\n\nDisasm preview:\n\n" + CutterCore::getInstance()->cmd("pdi 10 @ " + function.name) +
"\nStrings:\n\n" + CutterCore::getInstance()->cmd("pdsf @ " + function.name));
}
return QVariant();
}
case Qt::ForegroundRole:
if (functionIsImport(function.offset))
return QVariant(ConfigColor("gui.imports"));
if (functionIsMain(function.offset))
return QVariant(ConfigColor("gui.main"));
2018-01-09 11:03:07 +00:00
return QVariant(this->property("color"));
case FunctionDescriptionRole:
return QVariant::fromValue(function);
case IsImportRole:
2018-02-09 14:03:03 +00:00
return importAddresses->contains(function.offset);
default:
return QVariant();
}
}
QVariant FunctionModel::headerData(int section, Qt::Orientation orientation, int role) const
{
2018-03-21 20:32:32 +00:00
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
if (nested) {
return tr("Name");
2018-03-21 20:32:32 +00:00
} else {
switch (section) {
2017-11-04 15:28:02 +00:00
case NameColumn:
2017-10-13 13:53:23 +00:00
return tr("Name");
2017-11-04 15:28:02 +00:00
case SizeColumn:
return tr("Size");
2017-11-04 15:28:02 +00:00
case ImportColumn:
return tr("Imp.");
2017-11-04 15:28:02 +00:00
case OffsetColumn:
2017-10-13 13:53:23 +00:00
return tr("Offset");
default:
return QVariant();
}
}
}
return QVariant();
}
void FunctionModel::beginReloadFunctions()
{
beginResetModel();
}
void FunctionModel::endReloadFunctions()
{
updateCurrentIndex();
endResetModel();
}
void FunctionModel::setNested(bool nested)
{
beginResetModel();
this->nested = nested;
updateCurrentIndex();
endResetModel();
}
void FunctionModel::seekChanged(RVA)
{
2018-03-21 20:32:32 +00:00
if (updateCurrentIndex()) {
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
}
}
bool FunctionModel::updateCurrentIndex()
{
int index = -1;
RVA offset = 0;
RVA seek = Core()->getOffset();
2018-03-21 20:32:32 +00:00
for (int i = 0; i < functions->count(); i++) {
const FunctionDescription &function = functions->at(i);
if (function.contains(seek)
2018-03-21 20:32:32 +00:00
&& function.offset >= offset) {
offset = function.offset;
index = i;
}
}
2018-02-09 14:03:03 +00:00
bool changed = currentIndex != index;
2018-02-09 14:03:03 +00:00
currentIndex = index;
return changed;
}
void FunctionModel::functionRenamed(const QString &prev_name, const QString &new_name)
{
2018-03-21 20:32:32 +00:00
for (int i = 0; i < functions->count(); i++) {
FunctionDescription &function = (*functions)[i];
2018-03-21 20:32:32 +00:00
if (function.name == prev_name) {
function.name = new_name;
emit dataChanged(index(i, 0), index(i, columnCount() - 1));
}
}
}
2018-03-21 20:32:32 +00:00
FunctionSortFilterProxyModel::FunctionSortFilterProxyModel(FunctionModel *source_model,
QObject *parent)
: QSortFilterProxyModel(parent)
{
setSourceModel(source_model);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setSortCaseSensitivity(Qt::CaseInsensitive);
}
bool FunctionSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
{
QModelIndex index = sourceModel()->index(row, 0, parent);
2018-03-21 20:32:32 +00:00
FunctionDescription function = index.data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
return function.name.contains(filterRegExp());
}
bool FunctionSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
if (!left.isValid() || !right.isValid())
return false;
if (left.parent().isValid() || right.parent().isValid())
return false;
2018-03-21 20:32:32 +00:00
FunctionDescription left_function = left.data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
FunctionDescription right_function = right.data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
2018-03-21 20:32:32 +00:00
if (static_cast<FunctionModel *>(sourceModel())->isNested()) {
return left_function.name < right_function.name;
2018-03-21 20:32:32 +00:00
} else {
switch (left.column()) {
2017-11-04 15:28:02 +00:00
case FunctionModel::OffsetColumn:
return left_function.offset < right_function.offset;
2017-11-04 15:28:02 +00:00
case FunctionModel::SizeColumn:
if (left_function.size != right_function.size)
return left_function.size < right_function.size;
break;
2018-03-21 20:32:32 +00:00
case FunctionModel::ImportColumn: {
bool left_is_import = left.data(FunctionModel::IsImportRole).toBool();
bool right_is_import = right.data(FunctionModel::IsImportRole).toBool();
if (!left_is_import && right_is_import)
return true;
break;
}
2017-11-04 15:28:02 +00:00
case FunctionModel::NameColumn:
return left_function.name < right_function.name;
default:
return false;
}
return left_function.offset < right_function.offset;
}
}
FunctionsWidget::FunctionsWidget(MainWindow *main, QAction *action) :
CutterDockWidget(main, action),
ui(new Ui::FunctionsWidget)
{
ui->setupUi(this);
// Radare core found in:
this->main = main;
// leave the filter visible by default so users know it exists
//ui->filterLineEdit->setVisible(false);
// Ctrl-F to show/hide the filter entry
2017-12-19 18:56:18 +00:00
QShortcut *search_shortcut = new QShortcut(QKeySequence::Find, this);
connect(search_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::showFilter);
search_shortcut->setContext(Qt::WidgetWithChildrenShortcut);
// Esc to clear the filter entry
2017-12-21 11:08:49 +00:00
QShortcut *clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(clear_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::clearFilter);
clear_shortcut->setContext(Qt::WidgetWithChildrenShortcut);
QFontInfo font_info = ui->functionsTreeView->fontInfo();
QFont default_font = QFont(font_info.family(), font_info.pointSize());
QFont highlight_font = QFont(font_info.family(), font_info.pointSize(), QFont::Bold);
2018-03-21 20:32:32 +00:00
functionModel = new FunctionModel(&functions, &importAddresses, &mainAdress, false, default_font,
highlight_font, this);
2018-02-09 14:03:03 +00:00
functionProxyModel = new FunctionSortFilterProxyModel(functionModel, this);
ui->functionsTreeView->setModel(functionProxyModel);
ui->functionsTreeView->sortByColumn(FunctionModel::NameColumn, Qt::AscendingOrder);
2018-03-21 20:32:32 +00:00
connect(ui->quickFilterView, SIGNAL(filterTextChanged(const QString &)), functionProxyModel,
SLOT(setFilterWildcard(const QString &)));
connect(ui->quickFilterView, SIGNAL(filterClosed()), ui->functionsTreeView, SLOT(setFocus()));
setScrollMode();
// Set Functions context menu
connect(ui->functionsTreeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showFunctionsContextMenu(const QPoint &)));
2018-03-21 20:32:32 +00:00
connect(ui->functionsTreeView, SIGNAL(doubleClicked(const QModelIndex &)), this,
SLOT(onFunctionsDoubleClicked(const QModelIndex &)));
// Use a custom context menu on the dock title bar
//this->title_bar = this->titleBarWidget();
ui->actionHorizontal->setChecked(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showTitleContextMenu(const QPoint &)));
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshTree()));
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshTree()));
}
FunctionsWidget::~FunctionsWidget() {}
void FunctionsWidget::refreshTree()
2017-04-09 19:55:06 +00:00
{
2018-02-09 14:03:03 +00:00
functionModel->beginReloadFunctions();
2017-10-09 18:08:35 +00:00
functions = CutterCore::getInstance()->getAllFunctions();
2018-02-09 14:03:03 +00:00
importAddresses.clear();
2017-10-09 18:08:35 +00:00
foreach (ImportDescription import, CutterCore::getInstance()->getAllImports())
2018-02-09 14:03:03 +00:00
importAddresses.insert(import.plt);
mainAdress = (ut64)CutterCore::getInstance()->cmdj("iMj").object()["vaddr"].toInt();
2018-02-09 14:03:03 +00:00
functionModel->endReloadFunctions();
// resize offset and size columns
ui->functionsTreeView->resizeColumnToContents(0);
ui->functionsTreeView->resizeColumnToContents(1);
ui->functionsTreeView->resizeColumnToContents(2);
}
void FunctionsWidget::onFunctionsDoubleClicked(const QModelIndex &index)
{
2018-03-21 20:32:32 +00:00
FunctionDescription function = index.data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
Core()->seek(function.offset);
}
void FunctionsWidget::showFunctionsContextMenu(const QPoint &pt)
{
// Set functions popup menu
QMenu *menu = new QMenu(ui->functionsTreeView);
menu->clear();
menu->addAction(ui->actionDisasAdd_comment);
menu->addAction(ui->actionFunctionsRename);
menu->addAction(ui->actionFunctionsUndefine);
menu->addSeparator();
menu->addAction(ui->action_References);
menu->exec(ui->functionsTreeView->mapToGlobal(pt));
delete menu;
}
void FunctionsWidget::on_actionDisasAdd_comment_triggered()
{
// Get selected item in functions tree view
2018-03-21 20:32:32 +00:00
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
// Create dialog
2017-04-09 19:55:06 +00:00
CommentsDialog *c = new CommentsDialog(this);
2018-03-21 20:32:32 +00:00
if (c->exec()) {
// Get new function name
QString comment = c->getComment();
// Rename function in r2 core
2017-10-09 18:08:35 +00:00
CutterCore::getInstance()->setComment(function.offset, comment);
// Seek to new renamed function
2017-10-09 18:08:35 +00:00
CutterCore::getInstance()->seek(function.offset);
// TODO: Refresh functions tree widget
}
}
void FunctionsWidget::on_actionFunctionsRename_triggered()
{
// Get selected item in functions tree view
2018-03-21 20:32:32 +00:00
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
// Create dialog
2017-04-09 19:55:06 +00:00
RenameDialog *r = new RenameDialog(this);
// Set function name in dialog
2017-11-26 16:53:05 +00:00
r->setName(function.name);
// If user accepted
2018-03-21 20:32:32 +00:00
if (r->exec()) {
// Get new function name
2017-11-26 16:53:05 +00:00
QString new_name = r->getName();
2018-02-09 14:03:03 +00:00
// Rename function in r2 core
2017-10-09 18:08:35 +00:00
CutterCore::getInstance()->renameFunction(function.name, new_name);
// Seek to new renamed function
2017-10-09 18:08:35 +00:00
CutterCore::getInstance()->seek(function.offset);
}
}
void FunctionsWidget::on_actionFunctionsUndefine_triggered()
{
2018-03-21 20:32:32 +00:00
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
Core()->delFunction(function.offset);
}
void FunctionsWidget::on_action_References_triggered()
{
// Get selected item in functions tree view
2018-03-21 20:32:32 +00:00
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
XrefsDialog *x = new XrefsDialog(this);
x->fillRefsForAddress(function.offset, function.name, true);
x->exec();
}
void FunctionsWidget::showTitleContextMenu(const QPoint &pt)
{
// Set functions popup menu
QMenu *menu = new QMenu(this);
menu->clear();
menu->addAction(ui->actionHorizontal);
menu->addAction(ui->actionVertical);
2018-03-21 20:32:32 +00:00
if (!functionModel->isNested()) {
ui->actionHorizontal->setChecked(true);
ui->actionVertical->setChecked(false);
2018-03-21 20:32:32 +00:00
} else {
ui->actionVertical->setChecked(true);
ui->actionHorizontal->setChecked(false);
}
this->setContextMenuPolicy(Qt::CustomContextMenu);
menu->exec(this->mapToGlobal(pt));
delete menu;
}
void FunctionsWidget::on_actionHorizontal_triggered()
{
2018-02-09 14:03:03 +00:00
functionModel->setNested(false);
ui->functionsTreeView->setIndentation(8);
}
void FunctionsWidget::on_actionVertical_triggered()
{
2018-02-09 14:03:03 +00:00
functionModel->setNested(true);
ui->functionsTreeView->setIndentation(20);
}
void FunctionsWidget::resizeEvent(QResizeEvent *event)
2017-04-09 19:55:06 +00:00
{
2018-03-21 20:32:32 +00:00
if (main->responsive && isVisible()) {
if (event->size().width() >= event->size().height()) {
// Set horizontal view (list)
on_actionHorizontal_triggered();
2018-03-21 20:32:32 +00:00
} else {
// Set vertical view (Tree)
on_actionVertical_triggered();
}
}
QDockWidget::resizeEvent(event);
}
void FunctionsWidget::setScrollMode()
{
qhelpers::setVerticalScrollMode(ui->functionsTreeView);
}