Use REvent for classes (#1169)

* React to Anal Class REvents

* Correctly react to specific Class Events

* Adapt to Class REvent changes

* Update r2 submodule for Class REvents
This commit is contained in:
Florian Märkl 2019-02-07 11:42:53 +01:00 committed by GitHub
parent d876c9deee
commit bcb3a162f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 39 deletions

@ -1 +1 @@
Subproject commit b022c9533482a8f1c9ae825ced5c1a3e251837d9
Subproject commit b4178702c88ed361fcb98e1b87cd74f0af4b2f44

View File

@ -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<CutterCore *>(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<BinClassDescription> CutterCore::getAllClassesFromFlags()
return ret;
}
QList<QString> CutterCore::getAllAnalClasses()
QList<QString> CutterCore::getAllAnalClasses(bool sorted)
{
QList<QString> 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<ResourcesDescription> 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<REventClass *>(data);
emit classNew(QString::fromUtf8(ev->name));
break;
}
case R_EVENT_CLASS_DEL: {
auto ev = reinterpret_cast<REventClass *>(data);
emit classDeleted(QString::fromUtf8(ev->name));
break;
}
case R_EVENT_CLASS_RENAME: {
auto ev = reinterpret_cast<REventClassRename *>(data);
emit classRenamed(QString::fromUtf8(ev->name_old), QString::fromUtf8(ev->name_new));
break;
}
case R_EVENT_CLASS_ATTR_SET: {
auto ev = reinterpret_cast<REventClassAttrSet *>(data);
emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name));
break;
}
case R_EVENT_CLASS_ATTR_DEL: {
auto ev = reinterpret_cast<REventClassAttr *>(data);
emit classAttrsChanged(QString::fromUtf8(ev->class_name));
break;
}
case R_EVENT_CLASS_ATTR_RENAME: {
auto ev = reinterpret_cast<REventClassAttrRename *>(data);
emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name));
break;
}
default:
break;
}
}
void CutterCore::triggerFlagsChanged()
{
emit flagsChanged();

View File

@ -480,7 +480,7 @@ public:
void setCurrentBits(int bits, RVA offset = RVA_INVALID);
/* Classes */
QList<QString> getAllAnalClasses();
QList<QString> getAllAnalClasses(bool sorted);
QList<AnalMethodDescription> getAnalClassMethods(const QString &cls);
QList<AnalBaseClassDescription> getAnalClassBaseClasses(const QString &cls);
QList<AnalVTableDescription> getAnalClassVTables(const QString &cls);
@ -697,6 +697,8 @@ public:
QList<StringDescription> parseStringsJson(const QJsonDocument &doc);
QList<FunctionDescription> 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);
/*!

View File

@ -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);
}
}

View File

@ -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<QString, QVector<Attribute>>)
{
// 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::Attribute> &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<ClassesModel::RowType>();
auto right_type = right.data(ClassesModel::TypeRole).value<ClassesModel::RowType>();
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;
}

View File

@ -104,8 +104,13 @@ private:
Attribute(Type type, const QVariant &data) : type(type), data(data) {}
};
/*!
* This must always stay sorted alphabetically.
*/
QList<QString> 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);
};

View File

@ -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