diff --git a/radare2 b/radare2 index b022c953..b4178702 160000 --- a/radare2 +++ b/radare2 @@ -1 +1 @@ -Subproject commit b022c9533482a8f1c9ae825ced5c1a3e251837d9 +Subproject commit b4178702c88ed361fcb98e1b87cd74f0af4b2f44 diff --git a/src/Cutter.cpp b/src/Cutter.cpp index 1f547883..80c6c2cf 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -131,11 +131,19 @@ RCoreLocked CutterCore::core() const #define CORE_LOCK() RCoreLocked core_lock__(this->core_) +static void cutterREventCallback(REvent *, int type, void *user, void *data) +{ + auto core = reinterpret_cast(user); + core->handleREvent(type, data); +} + CutterCore::CutterCore(QObject *parent) : QObject(parent) { r_cons_new(); // initialize console - this->core_ = r_core_new(); + core_ = r_core_new(); + + r_event_hook(core_->anal->ev, R_EVENT_ALL, cutterREventCallback, this); #if defined(APPIMAGE) || defined(MACOS_R2_BUNDLED) auto prefix = QDir(QCoreApplication::applicationDirPath()); @@ -1970,11 +1978,11 @@ QList CutterCore::getAllClassesFromFlags() return ret; } -QList CutterCore::getAllAnalClasses() +QList CutterCore::getAllAnalClasses(bool sorted) { QList ret; - SdbList *l = r_anal_class_get_all(core_->anal, true); + SdbList *l = r_anal_class_get_all(core_->anal, sorted); if (!l) { return ret; } @@ -2081,13 +2089,11 @@ void CutterCore::setAnalMethod(const QString &className, const AnalMethodDescrip analMeth.vtable_offset = meth.vtableOffset; r_anal_class_method_set(core_->anal, className.toUtf8().constData(), &analMeth); r_anal_class_method_fini(&analMeth); - emit classesChanged(); } void CutterCore::renameAnalMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName) { r_anal_class_method_rename(core_->anal, className.toUtf8().constData(), oldMethodName.toUtf8().constData(), newMethodName.toUtf8().constData()); - emit classesChanged(); } QList CutterCore::getAllResources() @@ -2397,6 +2403,44 @@ void CutterCore::addFlag(RVA offset, QString name, RVA size) emit flagsChanged(); } +void CutterCore::handleREvent(int type, void *data) +{ + switch (type) { + case R_EVENT_CLASS_NEW: { + auto ev = reinterpret_cast(data); + emit classNew(QString::fromUtf8(ev->name)); + break; + } + case R_EVENT_CLASS_DEL: { + auto ev = reinterpret_cast(data); + emit classDeleted(QString::fromUtf8(ev->name)); + break; + } + case R_EVENT_CLASS_RENAME: { + auto ev = reinterpret_cast(data); + emit classRenamed(QString::fromUtf8(ev->name_old), QString::fromUtf8(ev->name_new)); + break; + } + case R_EVENT_CLASS_ATTR_SET: { + auto ev = reinterpret_cast(data); + emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name)); + break; + } + case R_EVENT_CLASS_ATTR_DEL: { + auto ev = reinterpret_cast(data); + emit classAttrsChanged(QString::fromUtf8(ev->class_name)); + break; + } + case R_EVENT_CLASS_ATTR_RENAME: { + auto ev = reinterpret_cast(data); + emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name)); + break; + } + default: + break; + } +} + void CutterCore::triggerFlagsChanged() { emit flagsChanged(); diff --git a/src/Cutter.h b/src/Cutter.h index bd69a1fe..af44c7bf 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -480,7 +480,7 @@ public: void setCurrentBits(int bits, RVA offset = RVA_INVALID); /* Classes */ - QList getAllAnalClasses(); + QList getAllAnalClasses(bool sorted); QList getAnalClassMethods(const QString &cls); QList getAnalClassBaseClasses(const QString &cls); QList getAnalClassVTables(const QString &cls); @@ -697,6 +697,8 @@ public: QList parseStringsJson(const QJsonDocument &doc); QList parseFunctionsJson(const QJsonDocument &doc); + void handleREvent(int type, void *data); + /* Signals related */ void triggerVarsChanged(); void triggerFunctionRenamed(const QString &prevName, const QString &newName); @@ -721,13 +723,17 @@ signals: void functionsChanged(); void flagsChanged(); void commentsChanged(); - void classesChanged(); void registersChanged(); void instructionChanged(RVA offset); void breakpointsChanged(); void refreshCodeViews(); void stackChanged(); + void classNew(const QString &cls); + void classDeleted(const QString &cls); + void classRenamed(const QString &oldName, const QString &newName); + void classAttrsChanged(const QString &cls); + void projectSaved(bool successfully, const QString &name); /*! diff --git a/src/dialogs/EditMethodDialog.cpp b/src/dialogs/EditMethodDialog.cpp index 37828ae2..a6102bf3 100644 --- a/src/dialogs/EditMethodDialog.cpp +++ b/src/dialogs/EditMethodDialog.cpp @@ -14,7 +14,7 @@ EditMethodDialog::EditMethodDialog(bool classFixed, QWidget *parent) : } else { classComboBox = new QComboBox(this); ui->formLayout->setItem(0, QFormLayout::FieldRole, new QWidgetItem(classComboBox)); - for (auto &cls : Core()->getAllAnalClasses()) { + for (auto &cls : Core()->getAllAnalClasses(true)) { classComboBox->addItem(cls, cls); } } diff --git a/src/widgets/ClassesWidget.cpp b/src/widgets/ClassesWidget.cpp index db4d3ecf..a078b14e 100644 --- a/src/widgets/ClassesWidget.cpp +++ b/src/widgets/ClassesWidget.cpp @@ -210,19 +210,114 @@ QVariant BinClassesModel::data(const QModelIndex &index, int role) const } -AnalClassesModel::AnalClassesModel(QObject *parent) +AnalClassesModel::AnalClassesModel(CutterDockWidget *parent) : ClassesModel(parent), attrs(new QMap>) { + // Just use a simple refresh deferrer. If an event was triggered in the background, simply refresh everything later. + refreshDeferrer = parent->createRefreshDeferrer([this]() { + this->refreshAll(); + }); + + connect(Core(), &CutterCore::refreshAll, this, &AnalClassesModel::refreshAll); + connect(Core(), &CutterCore::classNew, this, &AnalClassesModel::classNew); + connect(Core(), &CutterCore::classDeleted, this, &AnalClassesModel::classDeleted); + connect(Core(), &CutterCore::classRenamed, this, &AnalClassesModel::classRenamed); + connect(Core(), &CutterCore::classAttrsChanged, this, &AnalClassesModel::classAttrsChanged); + + refreshAll(); } -void AnalClassesModel::refreshClasses() +void AnalClassesModel::refreshAll() { + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + beginResetModel(); attrs->clear(); - classes = Core()->getAllAnalClasses(); + classes = Core()->getAllAnalClasses(true); // must be sorted endResetModel(); } +void AnalClassesModel::classNew(const QString &cls) +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + + // find the destination position using binary search and add the row + auto it = std::lower_bound(classes.begin(), classes.end(), cls); + int index = it - classes.begin(); + beginInsertRows(QModelIndex(), index, index); + classes.insert(it, cls); + endInsertRows(); +} + +void AnalClassesModel::classDeleted(const QString &cls) +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + + // find the position using binary search and remove the row + auto it = std::lower_bound(classes.begin(), classes.end(), cls); + if(it == classes.end() || *it != cls) { + return; + } + int index = it - classes.begin(); + beginRemoveRows(QModelIndex(), index, index); + classes.erase(it); + endRemoveRows(); +} + +void AnalClassesModel::classRenamed(const QString &oldName, const QString &newName) +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + + auto oldIt = std::lower_bound(classes.begin(), classes.end(), oldName); + if (oldIt == classes.end() || *oldIt != oldName) { + return; + } + auto newIt = std::lower_bound(classes.begin(), classes.end(), newName); + int oldRow = oldIt - classes.begin(); + int newRow = newIt - classes.begin(); + // oldRow == newRow means the name stayed the same. + // oldRow == newRow - 1 means the name changed, but the row stays the same. + if (oldRow != newRow && oldRow != newRow - 1) { + beginMoveRows(QModelIndex(), oldRow, oldRow, QModelIndex(), newRow); + classes.erase(oldIt); + // iterators are invalid now, so we calculate the new position from the rows. + if (oldRow < newRow) { + // if we move down, we need to account for the removed old element above. + newRow--; + } + classes.insert(newRow, newName); + endMoveRows(); + } else if (oldRow == newRow - 1) { // class name changed, but not the row + newRow--; + classes[newRow] = newName; + } + emit dataChanged(index(newRow, 0), index(newRow, 0)); +} + +void AnalClassesModel::classAttrsChanged(const QString &cls) +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + + auto it = std::lower_bound(classes.begin(), classes.end(), cls); + if(it == classes.end() || *it != cls) { + return; + } + QPersistentModelIndex persistentIndex = QPersistentModelIndex(index(it - classes.begin(), 0)); + layoutAboutToBeChanged({persistentIndex}); + attrs->remove(cls); + layoutChanged({persistentIndex}); +} + const QVector &AnalClassesModel::getAttrs(const QString &cls) const { auto it = attrs->find(cls); @@ -439,22 +534,24 @@ bool ClassesSortFilterProxyModel::lessThan(const QModelIndex &left, const QModel case ClassesModel::OFFSET: { RVA left_offset = left.data(ClassesModel::OffsetRole).toULongLong(); RVA right_offset = right.data(ClassesModel::OffsetRole).toULongLong(); - if (left_offset != right_offset) + if (left_offset != right_offset) { return left_offset < right_offset; + } } // fallthrough case ClassesModel::TYPE: { auto left_type = left.data(ClassesModel::TypeRole).value(); auto right_type = right.data(ClassesModel::TypeRole).value(); - if (left_type != right_type) + 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; + return QString::compare(left_name, right_name, Qt::CaseInsensitive) < 0; } } @@ -480,14 +577,10 @@ ClassesWidget::ClassesWidget(MainWindow *main, QAction *action) : ui->classSourceCombo->setCurrentIndex(1); - connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshClasses())); - connect(Core(), &CutterCore::classesChanged, this, [this]() { - if (getSource() == Source::ANAL) { - refreshClasses(); - } - }); connect(ui->classSourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshClasses())); connect(ui->classesTreeView, &QTreeView::customContextMenuRequested, this, &ClassesWidget::showContextMenu); + + refreshClasses(); } ClassesWidget::~ClassesWidget() {} @@ -523,7 +616,6 @@ void ClassesWidget::refreshClasses() anal_model = new AnalClassesModel(this); proxy_model->setSourceModel(anal_model); } - anal_model->refreshClasses(); break; } diff --git a/src/widgets/ClassesWidget.h b/src/widgets/ClassesWidget.h index 6fb8c2ce..443c936c 100644 --- a/src/widgets/ClassesWidget.h +++ b/src/widgets/ClassesWidget.h @@ -104,8 +104,13 @@ private: Attribute(Type type, const QVariant &data) : type(type), data(data) {} }; + /*! + * This must always stay sorted alphabetically. + */ QList classes; + RefreshDeferrer *refreshDeferrer; + /*! * \brief Cache for class attributes * @@ -130,9 +135,14 @@ private: QVariant data(const QModelIndex &index, int role) const override; public: - explicit AnalClassesModel(QObject *parent = nullptr); + explicit AnalClassesModel(CutterDockWidget *parent); - void refreshClasses(); +public slots: + void refreshAll(); + void classNew(const QString &cls); + void classDeleted(const QString &cls); + void classRenamed(const QString &oldName, const QString &newName); + void classAttrsChanged(const QString &cls); }; diff --git a/src/widgets/CutterDockWidget.h b/src/widgets/CutterDockWidget.h index 320b6e5d..9bd7c3e7 100644 --- a/src/widgets/CutterDockWidget.h +++ b/src/widgets/CutterDockWidget.h @@ -17,21 +17,6 @@ public: bool eventFilter(QObject *object, QEvent *event) override; bool isVisibleToUser() { return isVisibleToUserCurrent; } -public slots: - void toggleDockWidget(bool show); - -signals: - void becameVisibleToUser(); - -private: - QAction *action; - - bool isVisibleToUserCurrent = false; - void updateIsVisibleToUser(); - -protected: - void closeEvent(QCloseEvent *event) override; - /*! * \brief Convenience method for creating and registering a RefreshDeferrer without any parameters * \param refreshNowFunc lambda taking no parameters, called when a refresh should occur @@ -63,6 +48,21 @@ protected: }); return deferrer; } + +public slots: + void toggleDockWidget(bool show); + +signals: + void becameVisibleToUser(); + +private: + QAction *action; + + bool isVisibleToUserCurrent = false; + void updateIsVisibleToUser(); + +protected: + void closeEvent(QCloseEvent *event) override; }; #endif // CUTTERWIDGET_H