From f50fecc57b8dab9a064364a11db9606c0434aa10 Mon Sep 17 00:00:00 2001
From: karliss <karlis3p70l1ij@gmail.com>
Date: Wed, 14 Aug 2019 21:47:30 +0300
Subject: [PATCH] Add context menu entries for target address (#1712)

* Refactor "used here" logic.
* Add menu for showing instruction target.
---
 src/core/MainWindow.cpp              |   2 +-
 src/menus/DisassemblyContextMenu.cpp | 113 ++++++++++++++++++++-------
 src/menus/DisassemblyContextMenu.h   |  20 ++++-
 3 files changed, 104 insertions(+), 31 deletions(-)

diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp
index f2edeee1..9cb81988 100644
--- a/src/core/MainWindow.cpp
+++ b/src/core/MainWindow.cpp
@@ -915,7 +915,7 @@ QMenu *MainWindow::createShowInMenu(QWidget *parent, RVA address)
     auto createAddNewWidgetAction = [this, menu, address](QString label, MemoryWidgetType type) {
         QAction *action = new QAction(label, menu);
         connect(action, &QAction::triggered, this, [this, address, type](){
-            addNewMemoryWidget(type, address, true);
+            addNewMemoryWidget(type, address, false);
         });
         menu->addAction(action);
     };
diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp
index bb20c09f..02a842be 100644
--- a/src/menus/DisassemblyContextMenu.cpp
+++ b/src/menus/DisassemblyContextMenu.cpp
@@ -18,7 +18,7 @@
 #include <QApplication>
 #include <QPushButton>
 
-DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow* mainWindow)
+DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *mainWindow)
     :   QMenu(parent),
         offset(0),
         canCopy(false),
@@ -27,7 +27,8 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow* main
     initAction(&actionCopy, tr("Copy"), SLOT(on_actionCopy_triggered()), getCopySequence());
     addAction(&actionCopy);
 
-    initAction(&actionCopyAddr, tr("Copy address"), SLOT(on_actionCopyAddr_triggered()), getCopyAddressSequence());
+    initAction(&actionCopyAddr, tr("Copy address"), SLOT(on_actionCopyAddr_triggered()),
+               getCopyAddressSequence());
     addAction(&actionCopyAddr);
 
     initAction(&showInSubmenu, tr("Show in"), nullptr);
@@ -112,9 +113,6 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow* main
 
 DisassemblyContextMenu::~DisassemblyContextMenu()
 {
-    for (QAction *action : anonymousActions) {
-        delete action;
-    }
 }
 
 void DisassemblyContextMenu::addSetBaseMenu()
@@ -249,6 +247,65 @@ void DisassemblyContextMenu::addDebugMenu()
     debugMenu->addAction(&actionSetPC);
 }
 
+QVector<DisassemblyContextMenu::ThingUsedHere> DisassemblyContextMenu::getThingUsedHere(RVA offset)
+{
+    QVector<ThingUsedHere> result;
+    const QJsonArray array = Core()->cmdj("anj @ " + QString::number(offset)).array();
+    result.reserve(array.size());
+    for (const auto &thing : array) {
+        auto obj = thing.toObject();
+        RVA offset = obj["offset"].toVariant().toULongLong();
+        QString name = obj["name"].toString();
+        QString typeString = obj["type"].toString();
+        ThingUsedHere::Type type = ThingUsedHere::Type::Address;
+        if (typeString == "var") {
+            type = ThingUsedHere::Type::Var;
+        } else if (typeString == "flag") {
+            type = ThingUsedHere::Type::Flag;
+        } else if (typeString == "function") {
+            type = ThingUsedHere::Type::Function;
+        } else if (typeString == "address") {
+            type = ThingUsedHere::Type::Address;
+        }
+        result.push_back(ThingUsedHere{name, offset, type});
+    }
+    return result;
+}
+
+void DisassemblyContextMenu::updateTargetMenuActions(const QVector<ThingUsedHere> &targets)
+{
+    for (auto action : showTargetMenuActions) {
+        removeAction(action);
+        auto menu = action->menu();
+        if (menu) {
+            menu->deleteLater();
+        }
+        action->deleteLater();
+    }
+    showTargetMenuActions.clear();
+    for (auto &target : targets) {
+        QString name;
+        if (target.name.isEmpty()) {
+            name = tr("%1 (used here)").arg(RAddressString(target.offset));
+        } else {
+            name = tr("%1 (%2)").arg(target.name, RAddressString(target.offset));
+        }
+        auto action = new QAction(name, this);
+        showTargetMenuActions.append(action);
+        auto menu = mainWindow->createShowInMenu(this, target.offset);
+        action->setMenu(menu);
+        QAction *copyAddress = new QAction(tr("Copy address"), menu);
+        RVA offset = target.offset;
+        connect(copyAddress, &QAction::triggered, copyAddress, [offset]() {
+            QClipboard *clipboard = QApplication::clipboard();
+            clipboard->setText(RAddressString(offset));
+        });
+        menu->addSeparator();
+        menu->addAction(copyAddress);
+    }
+    insertActions(copySeparator, showTargetMenuActions);
+}
+
 void DisassemblyContextMenu::setOffset(RVA offset)
 {
     this->offset = offset;
@@ -362,24 +419,22 @@ void DisassemblyContextMenu::aboutToShowSlot()
 
 
     // Only show "rename X used here" if there is something to rename
-    QJsonArray thingUsedHereArray = Core()->cmdj("anj @ " + QString::number(offset)).array();
-    if (!thingUsedHereArray.isEmpty()) {
+    auto thingsUsedHere = getThingUsedHere(offset);
+    if (!thingsUsedHere.isEmpty()) {
         actionRenameUsedHere.setVisible(true);
-        QJsonObject thingUsedHere = thingUsedHereArray.first().toObject();
-        if (thingUsedHere["type"] == "address") {
-            RVA offset = thingUsedHere["offset"].toVariant().toULongLong();
+        auto &thingUsedHere = thingsUsedHere.first();
+        if (thingUsedHere.type == ThingUsedHere::Type::Address) {
+            RVA offset = thingUsedHere.offset;
             actionRenameUsedHere.setText(tr("Add flag at %1 (used here)").arg(RAddressString(offset)));
-        } else {
-            if (thingUsedHere["type"] == "function") {
-                actionRenameUsedHere.setText(tr("Rename \"%1\"").arg(thingUsedHere["name"].toString()));
-            }
-            else {
-                actionRenameUsedHere.setText(tr("Rename \"%1\" (used here)").arg(thingUsedHere["name"].toString()));
-            }
+        } else if (thingUsedHere.type == ThingUsedHere::Type::Function) {
+            actionRenameUsedHere.setText(tr("Rename \"%1\"").arg(thingUsedHere.name));
+        }  else {
+            actionRenameUsedHere.setText(tr("Rename \"%1\" (used here)").arg(thingUsedHere.name));
         }
     } else {
         actionRenameUsedHere.setVisible(false);
     }
+    updateTargetMenuActions(thingsUsedHere);
 
     // Decide to show Reverse jmp option
     showReverseJmpQuery();
@@ -686,38 +741,39 @@ void DisassemblyContextMenu::on_actionRename_triggered()
 
 void DisassemblyContextMenu::on_actionRenameUsedHere_triggered()
 {
-    QJsonArray array = Core()->cmdj("anj @ " + QString::number(offset)).array();
+    auto array = getThingUsedHere(offset);
     if (array.isEmpty()) {
         return;
     }
 
-    QJsonObject thingUsedHere = array.first().toObject();
-    QString type = thingUsedHere.value("type").toString();
+    auto thingUsedHere = array.first();
 
     RenameDialog dialog(this);
 
     QString oldName;
+    auto type = thingUsedHere.type;
 
-    if (type == "address") {
-        RVA offset = thingUsedHere["offset"].toVariant().toULongLong();
+    if (type == ThingUsedHere::Type::Address) {
+        RVA offset = thingUsedHere.offset;
         dialog.setWindowTitle(tr("Add flag at %1").arg(RAddressString(offset)));
         dialog.setName("label." + QString::number(offset, 16));
     } else {
-        oldName = thingUsedHere.value("name").toString();
+        oldName = thingUsedHere.name;
         dialog.setWindowTitle(tr("Rename %1").arg(oldName));
         dialog.setName(oldName);
     }
 
+
     if (dialog.exec()) {
         QString newName = dialog.getName().trimmed();
         if (!newName.isEmpty()) {
             Core()->cmd("an " + newName + " @ " + QString::number(offset));
 
-            if (type == "address" || type == "flag") {
+            if (type == ThingUsedHere::Type::Address || type == ThingUsedHere::Type::Address) {
                 Core()->triggerFlagsChanged();
-            } else if (type == "var") {
+            } else if (type == ThingUsedHere::Type::Var) {
                 Core()->triggerVarsChanged();
-            } else if (type == "function") {
+            } else if (type == ThingUsedHere::Type::Function) {
                 Core()->triggerFunctionRenamed(oldName, newName);
             }
         }
@@ -729,7 +785,8 @@ void DisassemblyContextMenu::on_actionSetFunctionVarTypes_triggered()
     RAnalFunction *fcn = Core()->functionAt(offset);
 
     if (!fcn) {
-        QMessageBox::critical(this, tr("Re-type function local vars"), tr("You must be in a function to define variable types."));
+        QMessageBox::critical(this, tr("Re-type function local vars"),
+                              tr("You must be in a function to define variable types."));
         return;
     }
 
@@ -873,7 +930,7 @@ void DisassemblyContextMenu::setToData(int size, int repeat)
 QAction *DisassemblyContextMenu::addAnonymousAction(QString name, const char *slot,
                                                     QKeySequence keySequence)
 {
-    auto action = new QAction();
+    auto action = new QAction(this);
     addAction(action);
     anonymousActions.append(action);
     initAction(action, name, slot, keySequence);
diff --git a/src/menus/DisassemblyContextMenu.h b/src/menus/DisassemblyContextMenu.h
index 3225ab7c..9a061824 100644
--- a/src/menus/DisassemblyContextMenu.h
+++ b/src/menus/DisassemblyContextMenu.h
@@ -10,7 +10,7 @@ class DisassemblyContextMenu : public QMenu
     Q_OBJECT
 
 public:
-    DisassemblyContextMenu(QWidget *parent, MainWindow* mainWindow);
+    DisassemblyContextMenu(QWidget *parent, MainWindow *mainWindow);
     ~DisassemblyContextMenu();
 
 signals:
@@ -101,7 +101,7 @@ private:
     RVA offset;
     bool canCopy;
     QString curHighlightedWord; // The current highlighted word
-    MainWindow* mainWindow;
+    MainWindow *mainWindow;
 
     QList<QAction *> anonymousActions;
 
@@ -166,6 +166,7 @@ private:
     QAction actionSetToDataQword;
 
     QAction showInSubmenu;
+    QList<QAction*> showTargetMenuActions;
 
     // For creating anonymous entries (that are always visible)
     QAction *addAnonymousAction(QString name, const char *slot, QKeySequence shortcut);
@@ -184,5 +185,20 @@ private:
     void addSetToDataMenu();
     void addEditMenu();
     void addDebugMenu();
+
+    struct ThingUsedHere {
+        QString name;
+        RVA offset;
+        enum class Type {
+            Var,
+            Function,
+            Flag,
+            Address
+        };
+        Type type;
+    };
+    QVector<ThingUsedHere> getThingUsedHere(RVA offset);
+
+    void updateTargetMenuActions(const QVector<ThingUsedHere> &targets);
 };
 #endif // DISASSEMBLYCONTEXTMENU_H