diff --git a/radare2 b/radare2 index b4178702c..e1f23fab4 160000 --- a/radare2 +++ b/radare2 @@ -1 +1 @@ -Subproject commit b4178702c88ed361fcb98e1b87cd74f0af4b2f44 +Subproject commit e1f23fab43c665b29029cd65af208c1d5eff13b5 diff --git a/src/Cutter.cpp b/src/Cutter.cpp index 0e2c63694..b2adcc2a2 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -2208,7 +2208,7 @@ QList 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 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 CutterCore::getAllEnums() TypeDescription exp; exp.type = key; exp.size = 0; - exp.category = tr("Enum"); + exp.category = "Enum"; ret << exp; } @@ -2259,13 +2259,26 @@ QList 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 CutterCore::getAllSearch(QString search_for, QString space) { CORE_LOCK(); diff --git a/src/Cutter.h b/src/Cutter.h index 6c64d44e5..1fbd666dd 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -685,6 +685,18 @@ class CutterCore: public QObject */ QList 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 getMemoryMap(); QList getAllSearch(QString search_for, QString space); BlockStatistics getBlockStatistics(unsigned int blocksCount); diff --git a/src/Cutter.pro b/src/Cutter.pro index 4ee6f90d6..afa9dc591 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -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 \ diff --git a/src/dialogs/LoadNewTypesDialog.cpp b/src/dialogs/LoadNewTypesDialog.cpp new file mode 100644 index 000000000..c68c94687 --- /dev/null +++ b/src/dialogs/LoadNewTypesDialog.cpp @@ -0,0 +1,71 @@ +#include "dialogs/LoadNewTypesDialog.h" +#include "ui_LoadNewTypesDialog.h" + +#include "Cutter.h" +#include "common/Configuration.h" +#include "widgets/TypesWidget.h" + +#include +#include + +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); + } +} diff --git a/src/dialogs/LoadNewTypesDialog.h b/src/dialogs/LoadNewTypesDialog.h new file mode 100644 index 000000000..97dfbf75b --- /dev/null +++ b/src/dialogs/LoadNewTypesDialog.h @@ -0,0 +1,49 @@ +#ifndef LOADNEWTYPESDIALOG_H +#define LOADNEWTYPESDIALOG_H + +#include +#include + +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; + +signals: + /*! + * \brief Emitted when new types are loaded + */ + void newTypesLoaded(); +}; + +#endif // LOADNEWTYPESDIALOG_H diff --git a/src/dialogs/LoadNewTypesDialog.ui b/src/dialogs/LoadNewTypesDialog.ui new file mode 100644 index 000000000..dab4e10cd --- /dev/null +++ b/src/dialogs/LoadNewTypesDialog.ui @@ -0,0 +1,99 @@ + + + LoadNewTypesDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + Load From File: + + + + + + + true + + + + + + + Select File + + + + + + + + + + + + Enter Types Manually + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + LoadNewTypesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LoadNewTypesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/widgets/TypesWidget.cpp b/src/widgets/TypesWidget.cpp index 7428e747e..ab6320b63 100644 --- a/src/widgets/TypesWidget.cpp +++ b/src/widgets/TypesWidget.cpp @@ -3,6 +3,11 @@ #include "MainWindow.h" #include "common/Helpers.h" +#include "dialogs/LoadNewTypesDialog.h" + +#include +#include + TypesModel::TypesModel(QList *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(); + 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(); + 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()); + } +} diff --git a/src/widgets/TypesWidget.h b/src/widgets/TypesWidget.h index b73bf06ef..0dfb6e3ea 100644 --- a/src/widgets/TypesWidget.h +++ b/src/widgets/TypesWidget.h @@ -38,11 +38,13 @@ class TypesModel: public QAbstractListModel TypesModel(QList *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 @@ class TypesWidget : public CutterDockWidget 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; diff --git a/src/widgets/TypesWidget.ui b/src/widgets/TypesWidget.ui index b7ce9b690..171b8f528 100644 --- a/src/widgets/TypesWidget.ui +++ b/src/widgets/TypesWidget.ui @@ -71,6 +71,30 @@ + + + Export Types + + + Export Types + + + + + Load New Types + + + Load New Types + + + + + Delete Type + + + Delete Type + +