Add ClassesWidget (Fix #193)

This commit is contained in:
Florian Märkl 2017-12-23 17:42:42 +01:00
parent 320a81ee14
commit a476dda5d0
15 changed files with 547 additions and 20 deletions

View File

@ -33,12 +33,14 @@
#include <QToolButton>
#include <QToolTip>
#include <QTreeWidgetItem>
#include <QSvgRenderer>
#include "utils/Highlighter.h"
#include "utils/HexAsciiHighlighter.h"
#include "utils/Helpers.h"
#include "dialogs/NewFileDialog.h"
#include "utils/SvgIconEngine.h"
#include "dialogs/NewFileDialog.h"
#include "widgets/DisassemblerGraphView.h"
#include "widgets/FunctionsWidget.h"
#include "widgets/SectionsWidget.h"
@ -60,6 +62,7 @@
#include "dialogs/OptionsDialog.h"
#include "widgets/EntrypointWidget.h"
#include "dialogs/SaveProjectDialog.h"
#include "widgets/ClassesWidget.h"
// graphics
#include <QGraphicsEllipseItem>
@ -116,9 +119,6 @@ MainWindow::~MainWindow()
{
}
#include <QSvgRenderer>
#include "utils/SvgIconEngine.h"
void MainWindow::initUI()
{
ui->setupUi(this);
@ -241,6 +241,8 @@ void MainWindow::initUI()
ADD_DOCK(Notepad, notepadDock, ui->actionNotepad);
ADD_DOCK(Dashboard, dashboardDock, ui->actionDashboard);
ADD_DOCK(SdbDock, sdbDock, ui->actionSDBBrowser);
ADD_DOCK(ClassesWidget, classesDock, ui->actionClasses);
#undef ADD_DOCK
// Set up dock widgets default layout
@ -622,6 +624,7 @@ void MainWindow::restoreDocks()
tabifyDockWidget(dashboardDock, exportsDock);
tabifyDockWidget(dashboardDock, symbolsDock);
tabifyDockWidget(dashboardDock, notepadDock);
tabifyDockWidget(dashboardDock, classesDock);
dashboardDock->raise();

View File

@ -36,6 +36,7 @@ class SectionsDock;
class ConsoleWidget;
class EntrypointWidget;
class DisassemblerGraphView;
class ClassesWidget;
class QDockWidget;
@ -169,7 +170,7 @@ private:
std::unique_ptr<Ui::MainWindow> ui;
Highlighter *highlighter;
AsciiHighlighter *hex_highlighter;
VisualNavbar *visualNavbar;
VisualNavbar *visualNavbar;
EntrypointWidget *entrypointDock;
FunctionsWidget *functionsDock;
ImportsWidget *importsDock;
@ -185,6 +186,7 @@ private:
//QAction *sidebar_action;
SectionsDock *sectionsDock;
ConsoleWidget *consoleDock;
ClassesWidget *classesDock;
QList<QDockWidget *> dockWidgets;
QMap<QAction *, QDockWidget *> dockWidgetActions;

View File

@ -241,6 +241,7 @@ border-top: 0px;
<addaction name="separator"/>
<addaction name="actionEntrypoints"/>
<addaction name="actionFunctions"/>
<addaction name="actionClasses"/>
<addaction name="actionImports"/>
<addaction name="actionExports"/>
<addaction name="actionSymbols"/>
@ -1019,6 +1020,14 @@ QToolButton:pressed {
<string>Console</string>
</property>
</action>
<action name="actionClasses">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Classes</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View File

@ -1175,6 +1175,46 @@ QList<EntrypointDescription> CutterCore::getAllEntrypoint()
return ret;
}
QList<ClassDescription> CutterCore::getAllClasses()
{
CORE_LOCK();
QList<ClassDescription> ret;
QJsonArray classesArray = cmdj("icj").array();
for (QJsonValueRef value : classesArray)
{
QJsonObject classObject = value.toObject();
ClassDescription cls;
cls.name = classObject["classname"].toString();
cls.addr = classObject["addr"].toVariant().toULongLong();
cls.index = classObject["index"].toVariant().toULongLong();
for(QJsonValueRef value2 : classObject["methods"].toArray())
{
QJsonObject methObject = value2.toObject();
ClassMethodDescription meth;
meth.name = methObject["name"].toString();
meth.addr = methObject["addr"].toVariant().toULongLong();
cls.methods << meth;
}
for(QJsonValueRef value2 : classObject["fields"].toArray())
{
QJsonObject fieldObject = value2.toObject();
ClassFieldDescription field;
field.name = fieldObject["name"].toString();
field.addr = fieldObject["addr"].toVariant().toULongLong();
cls.fields << field;
}
ret << cls;
}
return ret;
}
QList<XrefDescription> CutterCore::getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType)
{
QList<XrefDescription> ret = QList<XrefDescription>();

View File

@ -174,6 +174,27 @@ struct DisassemblyLine
QString text;
};
struct ClassMethodDescription
{
QString name;
RVA addr;
};
struct ClassFieldDescription
{
QString name;
RVA addr;
};
struct ClassDescription
{
QString name;
RVA addr;
ut64 index;
QList<ClassMethodDescription> methods;
QList<ClassFieldDescription> fields;
};
Q_DECLARE_METATYPE(FunctionDescription)
Q_DECLARE_METATYPE(ImportDescription)
Q_DECLARE_METATYPE(ExportDescription)
@ -186,6 +207,12 @@ Q_DECLARE_METATYPE(FlagDescription)
Q_DECLARE_METATYPE(XrefDescription)
Q_DECLARE_METATYPE(EntrypointDescription)
Q_DECLARE_METATYPE(RBinPluginDescription)
Q_DECLARE_METATYPE(ClassMethodDescription)
Q_DECLARE_METATYPE(ClassFieldDescription)
Q_DECLARE_METATYPE(ClassDescription)
Q_DECLARE_METATYPE(const ClassDescription *)
Q_DECLARE_METATYPE(const ClassMethodDescription *)
Q_DECLARE_METATYPE(const ClassFieldDescription *)
class CutterCore: public QObject
{
@ -313,6 +340,7 @@ public:
QList<FlagDescription> getAllFlags(QString flagspace = NULL);
QList<SectionDescription> getAllSections();
QList<EntrypointDescription> getAllEntrypoint();
QList<ClassDescription> getAllClasses();
QList<XrefDescription> getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType = QString::null);

View File

@ -85,7 +85,8 @@ SOURCES += \
dialogs/preferences/PreferencesDialog.cpp \
dialogs/preferences/GeneralOptionsWidget.cpp \
dialogs/preferences/GraphOptionsWidget.cpp \
widgets/QuickFilterView.cpp
widgets/QuickFilterView.cpp \
widgets/ClassesWidget.cpp
HEADERS += \
cutter.h \
@ -141,7 +142,8 @@ HEADERS += \
dialogs/preferences/PreferencesDialog.h \
dialogs/preferences/GeneralOptionsWidget.h \
dialogs/preferences/GraphOptionsWidget.h \
widgets/QuickFilterView.h
widgets/QuickFilterView.h \
widgets/ClassesWidget.h
FORMS += \
dialogs/AboutDialog.ui \
@ -175,7 +177,8 @@ FORMS += \
dialogs/preferences/GeneralOptionsWidget.ui \
dialogs/preferences/GraphOptionsWidget.ui \
widgets/QuickFilterView.ui \
widgets/PseudocodeWidget.ui
widgets/PseudocodeWidget.ui \
widgets/ClassesWidget.ui
RESOURCES += \
resources.qrc

View File

@ -20,20 +20,24 @@ static QAbstractItemView::ScrollMode scrollMode()
namespace qhelpers
{
void adjustColumns(QTreeWidget *tw, int columnCount, int padding)
void adjustColumns(QTreeView *tv, int columnCount, int padding)
{
const int count = columnCount == 0 ? tw->columnCount() : columnCount;
for (int i = 0; i != count; ++i)
for (int i = 0; i != columnCount; ++i)
{
tw->resizeColumnToContents(i);
tv->resizeColumnToContents(i);
if (padding > 0)
{
int width = tw->columnWidth(i);
tw->setColumnWidth(i, width + padding);
int width = tv->columnWidth(i);
tv->setColumnWidth(i, width + padding);
}
}
}
void adjustColumns(QTreeWidget *tw, int padding)
{
adjustColumns(tw, tw->columnCount(), padding);
}
QTreeWidgetItem *appendRow(QTreeWidget *tw, const QString &str, const QString &str2,
const QString &str3, const QString &str4, const QString &str5)
{

View File

@ -13,10 +13,12 @@ class QTreeWidgetItem;
class QAbstractItemView;
class QAbstractButton;
class QWidget;
class QTreeView;
namespace qhelpers
{
void adjustColumns(QTreeWidget *tw, int columnCount = 0, int padding = 0);
void adjustColumns(QTreeView *tv, int columnCount, int padding);
void adjustColumns(QTreeWidget *tw, int padding);
QTreeWidgetItem *appendRow(QTreeWidget *tw, const QString &str, const QString &str2 = QString(),
const QString &str3 = QString(), const QString &str4 = QString(), const QString &str5 = QString());

View File

@ -0,0 +1,283 @@
#include <QList>
#include "ClassesWidget.h"
#include "ui_ClassesWidget.h"
#include "MainWindow.h"
#include "utils/Helpers.h"
ClassesModel::ClassesModel(QList<ClassDescription> *classes, QObject *parent)
: QAbstractItemModel(parent),
classes(classes)
{
}
QModelIndex ClassesModel::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid())
return createIndex(row, column, (quintptr)0); // root function nodes have id = 0
return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1
}
QModelIndex ClassesModel::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 ClassesModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) // root
{
return classes->count();
}
if (parent.internalId() == 0) // methods/fields
{
const ClassDescription *cls = &classes->at(parent.row());
return cls->methods.length() + cls->fields.length();
}
return 0; // below methods/fields
}
int ClassesModel::columnCount(const QModelIndex &) const
{
return Columns::COUNT;
}
QVariant ClassesModel::data(const QModelIndex &index, int role) const
{
const ClassDescription *cls;
const ClassMethodDescription *meth = nullptr;
const ClassFieldDescription *field = nullptr;
if (index.internalId() == 0) // class row
{
if (index.row() >= classes->count())
{
return QVariant();
}
cls = &classes->at(index.row());
}
else // method/field row
{
cls = &classes->at(static_cast<int>(index.internalId() - 1));
if (index.row() >= cls->methods.length() + cls->fields.length())
{
return QVariant();
}
if (index.row() < cls->methods.length())
{
meth = &cls->methods[index.row()];
}
else
{
field = &cls->fields[index.row() - cls->methods.length()];
}
}
if (meth)
{
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NAME:
return meth->name;
case TYPE:
return tr("method");
case OFFSET:
return RAddressString(meth->addr);
default:
return QVariant();
}
case OffsetRole:
return QVariant::fromValue(meth->addr);
case NameRole:
return meth->name;
case TypeRole:
return QVariant::fromValue(METHOD);
default:
return QVariant();
}
}
else if (field)
{
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NAME:
return field->name;
case TYPE:
return tr("field");
case OFFSET:
return RAddressString(field->addr);
default:
return QVariant();
}
case OffsetRole:
return QVariant::fromValue(field->addr);
case NameRole:
return field->name;
case TypeRole:
return QVariant::fromValue(FIELD);
default:
return QVariant();
}
}
else
{
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NAME:
return cls->name;
case TYPE:
return tr("class");
case OFFSET:
return RAddressString(cls->addr);
default:
return QVariant();
}
case OffsetRole:
return QVariant::fromValue(cls->addr);
case NameRole:
return cls->name;
case TypeRole:
return QVariant::fromValue(CLASS);
default:
return QVariant();
}
}
}
QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case NAME:
return tr("Name");
case TYPE:
return tr("Type");
case OFFSET:
return tr("Offset");
default:
return QVariant();
}
default:
return QVariant();
}
}
void ClassesModel::beginReload()
{
beginResetModel();
}
void ClassesModel::endReload()
{
endResetModel();
}
ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(ClassesModel *source_model, QObject *parent)
: QSortFilterProxyModel(parent)
{
setSourceModel(source_model);
}
bool ClassesSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
{
QModelIndex index = sourceModel()->index(row, 0, parent);
return index.data(ClassesModel::NameRole).toString().contains(filterRegExp());
}
bool ClassesSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
switch (left.column())
{
case ClassesModel::OFFSET:
{
RVA left_offset = left.data(ClassesModel::OffsetRole).toULongLong();
RVA right_offset = right.data(ClassesModel::OffsetRole).toULongLong();
if (left_offset != right_offset)
return left_offset < right_offset;
}
// fallthrough
case ClassesModel::TYPE:
{
auto left_type = left.data(ClassesModel::TypeRole).value<ClassesModel::RowType>();
auto right_type = right.data(ClassesModel::TypeRole).value<ClassesModel::RowType>();
if (left_type != right_type)
return left_type < right_type;
}
// fallthrough
case ClassesModel::NAME:
default:
QString left_name = left.data(ClassesModel::NameRole).toString();
QString right_name = right.data(ClassesModel::NameRole).toString();
return left_name < right_name;
}
}
ClassesWidget::ClassesWidget(MainWindow *main, QWidget *parent) :
QDockWidget(parent),
ui(new Ui::ClassesWidget),
main(main)
{
ui->setupUi(this);
// Radare core found in:
this->main = main;
model = new ClassesModel(&classes, this);
proxy_model = new ClassesSortFilterProxyModel(model, this);
ui->classesTreeView->setModel(proxy_model);
ui->classesTreeView->sortByColumn(ClassesModel::TYPE, Qt::AscendingOrder);
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshClasses()));
}
ClassesWidget::~ClassesWidget() {}
void ClassesWidget::refreshClasses()
{
model->beginReload();
classes = CutterCore::getInstance()->getAllClasses();
model->endReload();
ui->classesTreeView->resizeColumnToContents(0);
ui->classesTreeView->resizeColumnToContents(1);
ui->classesTreeView->resizeColumnToContents(2);
ui->classesTreeView->setColumnWidth(0, 200);
}
void ClassesWidget::on_classesTreeView_doubleClicked(const QModelIndex &index)
{
RVA offset = index.data(ClassesModel::OffsetRole).value<RVA>();
CutterCore::getInstance()->seek(offset);
}

View File

@ -0,0 +1,95 @@
#ifndef CLASSWSWIDGET_H
#define CLASSWSWIDGET_H
#include <memory>
#include "cutter.h"
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QDockWidget>
class MainWindow;
class QTreeWidget;
namespace Ui
{
class ClassesWidget;
}
class MainWindow;
class QTreeWidgetItem;
class ClassesModel: public QAbstractItemModel
{
Q_OBJECT
private:
QList<ClassDescription> *classes;
public:
enum Columns { NAME = 0, TYPE, OFFSET, COUNT };
enum RowType { CLASS = 0, METHOD = 1, FIELD = 2 };
static const int OffsetRole = Qt::UserRole;
static const int NameRole = Qt::UserRole + 1;
static const int TypeRole = Qt::UserRole + 2;
explicit ClassesModel(QList<ClassDescription> *classes, QObject *parent = 0);
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void beginReload();
void endReload();
};
Q_DECLARE_METATYPE(ClassesModel::RowType);
class ClassesSortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit ClassesSortFilterProxyModel(ClassesModel *source_model, QObject *parent = 0);
protected:
bool filterAcceptsRow(int row, const QModelIndex &parent) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
};
class ClassesWidget : public QDockWidget
{
Q_OBJECT
public:
explicit ClassesWidget(MainWindow *main, QWidget *parent = 0);
~ClassesWidget();
private slots:
void on_classesTreeView_doubleClicked(const QModelIndex &index);
void refreshClasses();
private:
std::unique_ptr<Ui::ClassesWidget> ui;
MainWindow *main;
ClassesModel *model;
ClassesSortFilterProxyModel *proxy_model;
QList<ClassDescription> classes;
};
#endif // CLASSWSWIDGET_H

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ClassesWidget</class>
<widget class="QDockWidget" name="ClassesWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Classes</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="classesTreeView">
<property name="styleSheet">
<string notr="true">QTreeView::item
{
padding-top: 1px;
padding-bottom: 1px;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -162,7 +162,7 @@ void CommentsWidget::refreshTree()
nestedComments[fcn_name].append(comment);
}
qhelpers::adjustColumns(ui->commentsTreeWidget);
qhelpers::adjustColumns(ui->commentsTreeWidget, 0);
// Add nested comments
ui->nestedCmtsTreeWidget->clear();
@ -180,5 +180,5 @@ void CommentsWidget::refreshTree()
}
ui->nestedCmtsTreeWidget->addTopLevelItem(item);
}
qhelpers::adjustColumns(ui->nestedCmtsTreeWidget);
qhelpers::adjustColumns(ui->nestedCmtsTreeWidget, 0);
}

View File

@ -44,7 +44,7 @@ void RelocsWidget::fillTreeWidget()
ui->relocsTreeWidget->addTopLevelItem(item);
}
qhelpers::adjustColumns(ui->relocsTreeWidget);
qhelpers::adjustColumns(ui->relocsTreeWidget, 0);
}
void RelocsWidget::setScrollMode()

View File

@ -34,7 +34,7 @@ void SectionsWidget::refreshSections()
fillSections(row++, section);
}
qhelpers::adjustColumns(tree);
qhelpers::adjustColumns(tree, 0);
}
void SectionsWidget::setupViews()

View File

@ -44,7 +44,7 @@ void SymbolsWidget::fillSymbols()
item->setData(0, Qt::UserRole, QVariant::fromValue(symbol));
ui->symbolsTreeWidget->addTopLevelItem(item);
}
qhelpers::adjustColumns(ui->symbolsTreeWidget);
qhelpers::adjustColumns(ui->symbolsTreeWidget, 0);
}
void SymbolsWidget::setScrollMode()