cutter/src/widgets/FunctionsWidget.cpp

607 lines
20 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"
2018-06-25 19:28:34 +00:00
#include "utils/FunctionsTask.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 8; // 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"));
case 3:
return tr("Nargs: %1").arg(RSizeString(function.nargs));
case 4:
return tr("Nbbs: %1").arg(RSizeString(function.nbbs));
case 5:
return tr("Nlocals: %1").arg(RSizeString(function.nlocals));
case 6:
return tr("Cyclomatic complexity: %1").arg(RSizeString(function.cc));
case 7:
return tr("Call type: %1").arg(function.calltype);
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);
case NargsColumn:
return function.nargs;
case NbbsColumn:
return function.nbbs;
case NlocalsColumn:
return function.nlocals;
case CcColumn:
return function.cc;
case CalltypeColumn:
return function.calltype;
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: {
2018-04-12 06:33:30 +00:00
QList<QString> info = Core()->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 +
2018-04-12 06:33:30 +00:00
"\n\nDisasm preview:\n\n" + Core()->cmd("pdi 10 @ " + function.name) +
"\nStrings:\n\n" + Core()->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");
case NargsColumn:
return tr("Nargs");
case NbbsColumn:
return tr("Nbbs");
case NlocalsColumn:
return tr("Nlocals");
case CcColumn:
return tr("Cyclo. Comp.");
case CalltypeColumn:
return tr("Call type");
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)
{
int previousIndex = currentIndex;
2018-03-21 20:32:32 +00:00
if (updateCurrentIndex()) {
if (previousIndex >= 0) {
emit dataChanged(index(previousIndex, 0), index(previousIndex, columnCount() - 1));
}
if (currentIndex >= 0) {
emit dataChanged(index(currentIndex, 0), index(currentIndex, 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;
case FunctionModel::NargsColumn:
if (left_function.nargs != right_function.nargs)
return left_function.nargs < right_function.nargs;
break;
case FunctionModel::NbbsColumn:
if (left_function.nbbs != right_function.nbbs)
return left_function.nbbs < right_function.nbbs;
break;
case FunctionModel::NlocalsColumn:
if (left_function.nlocals != right_function.nlocals)
return left_function.nlocals < right_function.nlocals;
break;
case FunctionModel::CcColumn:
if (left_function.cc != right_function.cc)
return left_function.cc < right_function.cc;
break;
case FunctionModel::CalltypeColumn:
return left_function.calltype < right_function.calltype;
break;
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-06-25 19:28:34 +00:00
if (task) {
task->wait();
}
task = QSharedPointer<FunctionsTask>(new FunctionsTask());
connect(task.data(), &FunctionsTask::fetchFinished, this, [this] (const QList<FunctionDescription> &functions) {
functionModel->beginReloadFunctions();
2018-06-25 19:28:34 +00:00
this->functions = functions;
2018-06-25 19:28:34 +00:00
importAddresses.clear();
for (ImportDescription import : Core()->getAllImports()) {
importAddresses.insert(import.plt);
}
2018-06-25 19:28:34 +00:00
mainAdress = (ut64)Core()->cmdj("iMj").object()["vaddr"].toInt();
2018-06-25 19:28:34 +00:00
functionModel->endReloadFunctions();
2018-06-25 19:28:34 +00:00
// resize offset and size columns
qhelpers::adjustColumns(ui->functionsTreeView, 3, 0);
});
Core()->getAsyncTaskManager()->start(task);
}
void FunctionsWidget::onFunctionsDoubleClicked(const QModelIndex &index)
{
if (!index.isValid())
return;
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
2018-04-12 06:33:30 +00:00
Core()->setComment(function.offset, comment);
// Seek to new renamed function
2018-04-12 06:33:30 +00:00
Core()->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
2018-04-12 06:33:30 +00:00
Core()->renameFunction(function.name, new_name);
// Seek to new renamed function
2018-04-12 06:33:30 +00:00
Core()->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);
}