Added load, delete and export features in the Types Widget (#1158)

* Added export types feature in types widget

* Added load new types option

* Added delete types option

* Removed redundant files

* Fix for translation

* Added popups in TypesWidget and related dialogs

* Added addTypes() in CutterCore and changed code to use it.

* Update r2 submodule for r_parse_c_string()

* Added override keyword for overridden functions

* Added comments and removed popup question.

* Added comments and confirmation dialog for delete
This commit is contained in:
Gaurav Kumar Ghildiyal 2019-02-11 15:04:15 +05:30 committed by Itay Cohen
parent 75ce42f1ef
commit c9ce15f6dd
10 changed files with 403 additions and 13 deletions

@ -1 +1 @@
Subproject commit b4178702c88ed361fcb98e1b87cd74f0af4b2f44
Subproject commit e1f23fab43c665b29029cd65af208c1d5eff13b5

View File

@ -2208,7 +2208,7 @@ QList<TypeDescription> CutterCore::getAllUnions()
TypeDescription exp;
exp.type = value.toString();
exp.size = 0;
exp.category = tr("Union");
exp.category = "Union";
ret << exp;
}
@ -2225,7 +2225,7 @@ QList<TypeDescription> CutterCore::getAllStructs()
TypeDescription exp;
exp.type = value.toString();
exp.size = 0;
exp.category = tr("Struct");
exp.category = "Struct";
ret << exp;
}
@ -2242,7 +2242,7 @@ QList<TypeDescription> CutterCore::getAllEnums()
TypeDescription exp;
exp.type = key;
exp.size = 0;
exp.category = tr("Enum");
exp.category = "Enum";
ret << exp;
}
@ -2259,13 +2259,26 @@ QList<TypeDescription> CutterCore::getAllTypedefs()
TypeDescription exp;
exp.type = key;
exp.size = 0;
exp.category = tr("Typedef");
exp.category = "Typedef";
ret << exp;
}
return ret;
}
QString CutterCore::addTypes(const char *str)
{
char *error_msg = nullptr;
char *parsed = r_parse_c_string(core_->anal, str, &error_msg);
if (!parsed && error_msg) {
return QString(error_msg);
}
sdb_query_lines(core_->anal->sdb_types, parsed);
return QString();
}
QList<SearchDescription> CutterCore::getAllSearch(QString search_for, QString space)
{
CORE_LOCK();

View File

@ -685,6 +685,18 @@ public:
*/
QList<TypeDescription> getAllTypedefs();
/*!
* \brief Adds new types
* It first uses the r_parse_c_string() function from radare2 API to parse the
* supplied C file (in the form of a string). If there were errors, they are displayed.
* If there were no errors, it uses sdb_query_lines() function from radare2 API
* to save the parsed types returned by r_parse_c_string()
* \param str Contains the definition of the data types
* \return returns an empty QString if there was no error, else returns the error
*/
QString addTypes(const char *str);
QString addTypes(const QString &str) { return addTypes(str.toUtf8().constData()); }
QList<MemoryMapDescription> getMemoryMap();
QList<SearchDescription> getAllSearch(QString search_for, QString space);
BlockStatistics getBlockStatistics(unsigned int blocksCount);

View File

@ -227,7 +227,8 @@ SOURCES += \
common/RefreshDeferrer.cpp \
dialogs/WelcomeDialog.cpp \
RunScriptTask.cpp \
dialogs/EditMethodDialog.cpp
dialogs/EditMethodDialog.cpp \
dialogs/LoadNewTypesDialog.cpp
HEADERS += \
Cutter.h \
@ -337,7 +338,8 @@ HEADERS += \
dialogs/WelcomeDialog.h \
RunScriptTask.h \
common/Json.h \
dialogs/EditMethodDialog.h
dialogs/EditMethodDialog.h \
dialogs/LoadNewTypesDialog.h
FORMS += \
dialogs/AboutDialog.ui \
@ -397,7 +399,8 @@ FORMS += \
widgets/ComboQuickFilterView.ui \
dialogs/HexdumpRangeDialog.ui \
dialogs/WelcomeDialog.ui \
dialogs/EditMethodDialog.ui
dialogs/EditMethodDialog.ui \
dialogs/LoadNewTypesDialog.ui
RESOURCES += \
resources.qrc \

View File

@ -0,0 +1,71 @@
#include "dialogs/LoadNewTypesDialog.h"
#include "ui_LoadNewTypesDialog.h"
#include "Cutter.h"
#include "common/Configuration.h"
#include "widgets/TypesWidget.h"
#include <QFileDialog>
#include <QTemporaryFile>
LoadNewTypesDialog::LoadNewTypesDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::LoadNewTypesDialog)
{
ui->setupUi(this);
ui->plainTextEdit->setPlainText("");
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
LoadNewTypesDialog::~LoadNewTypesDialog() {}
void LoadNewTypesDialog::on_selectFileButton_clicked()
{
QString filename = QFileDialog::getOpenFileName(this, tr("Select file"), Config()->getRecentFolder(), "Header files (*.h *.hpp);;All files (*)");
if (filename.isEmpty()) {
return;
}
Config()->setRecentFolder(QFileInfo(filename).absolutePath());
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox popup(this);
popup.setWindowTitle(tr("Error"));
popup.setText(file.errorString());
popup.setStandardButtons(QMessageBox::Ok);
popup.exec();
on_selectFileButton_clicked();
return;
}
ui->filenameLineEdit->setText(filename);
ui->plainTextEdit->setPlainText(file.readAll());
}
void LoadNewTypesDialog::on_plainTextEdit_textChanged()
{
if (ui->plainTextEdit->toPlainText().isEmpty()) {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
} else {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
}
void LoadNewTypesDialog::done(int r)
{
if (r == QDialog::Accepted) {
QString error = Core()->addTypes(ui->plainTextEdit->toPlainText());
if (error.isEmpty()) {
emit newTypesLoaded();
QDialog::done(r);
return;
}
QMessageBox popup(this);
popup.setWindowTitle(tr("Error"));
popup.setText(tr("There was some error while loading new types"));
popup.setDetailedText(error);
popup.setStandardButtons(QMessageBox::Ok);
popup.exec();
} else {
QDialog::done(r);
}
}

View File

@ -0,0 +1,49 @@
#ifndef LOADNEWTYPESDIALOG_H
#define LOADNEWTYPESDIALOG_H
#include <QDialog>
#include <memory>
namespace Ui {
class LoadNewTypesDialog;
}
class LoadNewTypesDialog : public QDialog
{
Q_OBJECT
public:
explicit LoadNewTypesDialog(QWidget *parent = nullptr);
~LoadNewTypesDialog();
private slots:
/*!
* \brief Executed when the user clicks the selectFileButton
* Opens a File Dialog from where the user can select a file from where
* the types will be loaded.
*/
void on_selectFileButton_clicked();
/*!
* \brief Executed whenever the text inside the textbox changes
* When the text box is empty, the OK button is disabled.
*/
void on_plainTextEdit_textChanged();
/*!
* \brief done Closes the dialog and sets its result code to r
* \param r The value which will be returned by exec()
*/
void done(int r) override;
private:
std::unique_ptr<Ui::LoadNewTypesDialog> ui;
signals:
/*!
* \brief Emitted when new types are loaded
*/
void newTypesLoaded();
};
#endif // LOADNEWTYPESDIALOG_H

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoadNewTypesDialog</class>
<widget class="QDialog" name="LoadNewTypesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="filenameLabel">
<property name="text">
<string>Load From File:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filenameLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectFileButton">
<property name="text">
<string>Select File</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="plainText">
<string/>
</property>
<property name="placeholderText">
<string>Enter Types Manually</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LoadNewTypesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LoadNewTypesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -3,6 +3,11 @@
#include "MainWindow.h"
#include "common/Helpers.h"
#include "dialogs/LoadNewTypesDialog.h"
#include <QMenu>
#include <QFileDialog>
TypesModel::TypesModel(QList<TypeDescription> *types, QObject *parent)
: QAbstractListModel(parent),
types(types)
@ -68,6 +73,17 @@ QVariant TypesModel::headerData(int section, Qt::Orientation, int role) const
}
}
bool TypesModel::removeRows(int row, int count, const QModelIndex &parent)
{
Core()->cmdRaw("t-" + types->at(row).type);
beginRemoveRows(parent, row, row + count - 1);
while (count--) {
types->removeAt(row);
}
endRemoveRows();
return true;
}
TypesSortFilterProxyModel::TypesSortFilterProxyModel(TypesModel *source_model, QObject *parent)
: QSortFilterProxyModel(parent)
@ -120,6 +136,10 @@ TypesWidget::TypesWidget(MainWindow *main, QAction *action) :
// Add status bar which displays the count
tree->addStatusBar(ui->verticalLayout);
// Set single select mode
ui->typesTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
// Setup up the model and the proxy model
types_model = new TypesModel(&types, this);
types_proxy_model = new TypesSortFilterProxyModel(types_model, this);
ui->typesTreeView->setModel(types_proxy_model);
@ -127,6 +147,13 @@ TypesWidget::TypesWidget(MainWindow *main, QAction *action) :
setScrollMode();
// Setup custom context menu
connect(ui->typesTreeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showTypesContextMenu(const QPoint &)));
ui->typesTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->quickFilterView, SIGNAL(filterTextChanged(const QString &)), types_proxy_model,
SLOT(setFilterWildcard(const QString &)));
@ -169,7 +196,7 @@ void TypesWidget::refreshTypes()
categories.removeDuplicates();
refreshCategoryCombo(categories);
qhelpers::adjustColumns(ui->typesTreeView, 3, 0);
qhelpers::adjustColumns(ui->typesTreeView, 4, 0);
}
void TypesWidget::refreshCategoryCombo(const QStringList &categories)
@ -190,3 +217,67 @@ void TypesWidget::setScrollMode()
{
qhelpers::setVerticalScrollMode(ui->typesTreeView);
}
void TypesWidget::showTypesContextMenu(const QPoint &pt)
{
QMenu menu(ui->typesTreeView);
menu.addAction(ui->actionLoad_New_Types);
menu.addAction(ui->actionExport_Types);
QModelIndex index = ui->typesTreeView->indexAt(pt);
if (index.isValid()) {
TypeDescription t = index.data(TypesModel::TypeDescriptionRole).value<TypeDescription>();
if (t.category != "Typedef") {
menu.addSeparator();
menu.addAction(ui->actionDelete_Type);
}
}
menu.exec(ui->typesTreeView->mapToGlobal(pt));
}
void TypesWidget::on_actionExport_Types_triggered()
{
QString filename = QFileDialog::getSaveFileName(this, tr("Save File"), Config()->getRecentFolder());
if (filename.isEmpty()) {
return;
}
Config()->setRecentFolder(QFileInfo(filename).absolutePath());
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox popup(this);
popup.setWindowTitle(tr("Error"));
popup.setText(file.errorString());
popup.setStandardButtons(QMessageBox::Ok);
popup.exec();
on_actionExport_Types_triggered();
return;
}
QTextStream fileOut(&file);
fileOut << Core()->cmd("tc");
file.close();
}
void TypesWidget::on_actionLoad_New_Types_triggered()
{
LoadNewTypesDialog *dialog = new LoadNewTypesDialog(this);
connect(dialog, SIGNAL(newTypesLoaded()), this, SLOT(refreshTypes()));
dialog->setWindowTitle(tr("Load New Types"));
dialog->exec();
}
void TypesWidget::on_actionDelete_Type_triggered()
{
QModelIndex proxyIndex = ui->typesTreeView->currentIndex();
QModelIndex index = types_proxy_model->mapToSource(proxyIndex);
TypeDescription exp = index.data(TypesModel::TypeDescriptionRole).value<TypeDescription>();
QMessageBox popup(this);
popup.setIcon(QMessageBox::Question);
popup.setText(tr("Are you sure you want to delete \"%1\"?").arg(exp.type));
popup.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
popup.setDefaultButton(QMessageBox::Yes);
if (popup.exec() == QMessageBox::Yes) {
types_model->removeRow(index.row());
}
}

View File

@ -38,11 +38,13 @@ public:
TypesModel(QList<TypeDescription> *types, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
};
@ -76,6 +78,32 @@ public:
private slots:
void refreshTypes();
/*!
* \brief Show custom context menu
* \param pt Position of the place where the right mouse button was clicked
*/
void showTypesContextMenu(const QPoint &pt);
/*!
* \brief Executed on clicking the Export Types option in the context menu
* It shows the user a file dialog box to select a file where the types
* will be exported. It uses the "tc" command of radare2 to export the types.
*/
void on_actionExport_Types_triggered();
/*!
* \brief Executed on clicking the Load New types option in the context menu
* It will open the LoadNewTypesDialog where the user can either enter the
* types manually, or can select a file from where the types will be loaded
*/
void on_actionLoad_New_Types_triggered();
/*!
* \brief Executed on clicking the Delete Type option in the context menu
* Upon confirmation from the user, it will delete the selected type.
*/
void on_actionDelete_Type_triggered();
private:
std::unique_ptr<Ui::TypesWidget> ui;

View File

@ -71,6 +71,30 @@
</item>
</layout>
</widget>
<action name="actionExport_Types">
<property name="text">
<string>Export Types</string>
</property>
<property name="toolTip">
<string>Export Types</string>
</property>
</action>
<action name="actionLoad_New_Types">
<property name="text">
<string>Load New Types</string>
</property>
<property name="toolTip">
<string>Load New Types</string>
</property>
</action>
<action name="actionDelete_Type">
<property name="text">
<string>Delete Type</string>
</property>
<property name="toolTip">
<string>Delete Type</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>