cutter/src/menus/DecompilerContextMenu.cpp
2020-12-16 11:59:23 +01:00

601 lines
22 KiB
C++

#include "DecompilerContextMenu.h"
#include "dialogs/preferences/PreferencesDialog.h"
#include "MainWindow.h"
#include "dialogs/BreakpointsDialog.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/EditVariablesDialog.h"
#include "dialogs/XrefsDialog.h"
#include "common/Configuration.h"
#include <QtCore>
#include <QShortcut>
#include <QJsonArray>
#include <QClipboard>
#include <QApplication>
#include <QPushButton>
#include <QInputDialog>
DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWindow)
: QMenu(parent),
mainWindow(mainWindow),
curHighlightedWord(QString()),
offset(0),
decompiledFunctionAddress(RVA_INVALID),
isTogglingBreakpoints(false),
annotationHere(nullptr),
actionCopy(tr("Copy"), this),
actionCopyInstructionAddress(tr("Copy instruction address (<address>)"), this),
actionCopyReferenceAddress(tr("Copy address of [flag] (<address>)"), this),
actionShowInSubmenu(tr("Show in"), this),
actionAddComment(tr("Add Comment"), this),
actionDeleteComment(tr("Delete comment"), this),
actionRenameThingHere(tr("Rename function at cursor"), this),
actionDeleteName(tr("Delete <name>"), this),
actionEditFunctionVariables(tr("Edit variable <name of variable>"), this),
actionXRefs(tr("Show X-Refs"), this),
actionToggleBreakpoint(tr("Add/remove breakpoint"), this),
actionAdvancedBreakpoint(tr("Advanced breakpoint"), this),
breakpointsInLineMenu(new QMenu(this)),
actionContinueUntil(tr("Continue until line"), this),
actionSetPC(tr("Set PC"), this)
{
setActionCopy(); // Sets all three copy actions
addSeparator();
setActionShowInSubmenu();
copySeparator = addSeparator();
setActionAddComment();
setActionDeleteComment();
setActionRenameThingHere();
setActionDeleteName();
setActionXRefs();
setActionEditFunctionVariables();
addSeparator();
addBreakpointMenu();
addDebugMenu();
setShortcutContextInActions(this);
connect(this, &DecompilerContextMenu::aboutToShow,
this, &DecompilerContextMenu::aboutToShowSlot);
connect(this, &DecompilerContextMenu::aboutToHide,
this, &DecompilerContextMenu::aboutToHideSlot);
}
DecompilerContextMenu::~DecompilerContextMenu()
{
}
void DecompilerContextMenu::setAnnotationHere(RzCodeAnnotation *annotation)
{
annotationHere = annotation;
}
void DecompilerContextMenu::setCurHighlightedWord(QString word)
{
curHighlightedWord = word;
}
void DecompilerContextMenu::setOffset(RVA newOffset)
{
offset = newOffset;
}
void DecompilerContextMenu::setDecompiledFunctionAddress(RVA functionAddr)
{
decompiledFunctionAddress = functionAddr;
}
void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset)
{
firstOffsetInLine = firstOffset;
}
RVA DecompilerContextMenu::getFirstOffsetInLine()
{
return firstOffsetInLine;
}
void DecompilerContextMenu::setAvailableBreakpoints(QVector<RVA> offsetList)
{
availableBreakpoints = offsetList;
}
void DecompilerContextMenu::setupBreakpointsInLineMenu()
{
breakpointsInLineMenu->clear();
for (auto curOffset : this->availableBreakpoints) {
QAction *action = breakpointsInLineMenu->addAction(RAddressString(curOffset));
connect(action, &QAction::triggered, this, [this, curOffset] {
BreakpointsDialog::editBreakpoint(Core()->getBreakpointAt(curOffset),
this);
});
}
}
void DecompilerContextMenu::setShortcutContextInActions(QMenu *menu)
{
for (QAction *action : menu->actions()) {
if (action->isSeparator()) {
//Do nothing
} else if (action->menu()) {
setShortcutContextInActions(action->menu());
} else {
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
}
}
void DecompilerContextMenu::setIsTogglingBreakpoints(bool isToggling)
{
isTogglingBreakpoints = isToggling;
}
bool DecompilerContextMenu::getIsTogglingBreakpoints()
{
return isTogglingBreakpoints;
}
void DecompilerContextMenu::aboutToHideSlot()
{
actionAddComment.setVisible(true);
actionRenameThingHere.setVisible(true);
actionRenameThingHere.setEnabled(true);
actionDeleteName.setVisible(false);
actionEditFunctionVariables.setVisible(true);
actionEditFunctionVariables.setEnabled(true);
actionXRefs.setVisible(true);
setToolTipsVisible(false);
}
void DecompilerContextMenu::aboutToShowSlot()
{
if (this->firstOffsetInLine != RVA_MAX) {
actionShowInSubmenu.setVisible(true);
QString comment = Core()->cmdRawAt("CC.", this->firstOffsetInLine);
actionAddComment.setVisible(true);
if (comment.isEmpty()) {
actionDeleteComment.setVisible(false);
actionAddComment.setText(tr("Add Comment"));
} else {
actionDeleteComment.setVisible(true);
actionAddComment.setText(tr("Edit Comment"));
}
} else {
actionShowInSubmenu.setVisible(false);
actionAddComment.setVisible(false);
actionDeleteComment.setVisible(false);
}
setupBreakpointsInLineMenu();
// Only show debug options if we are currently debugging
debugMenu->menuAction()->setVisible(Core()->currentlyDebugging);
bool hasBreakpoint = !this->availableBreakpoints.isEmpty();
int numberOfBreakpoints = this->availableBreakpoints.size();
if (numberOfBreakpoints == 0) {
actionToggleBreakpoint.setText(tr("Add breakpoint"));
} else if (numberOfBreakpoints == 1) {
actionToggleBreakpoint.setText(tr("Remove breakpoint"));
} else {
actionToggleBreakpoint.setText(tr("Remove all breakpoints in line"));
}
if (numberOfBreakpoints > 1) {
actionAdvancedBreakpoint.setMenu(breakpointsInLineMenu);
} else {
actionAdvancedBreakpoint.setMenu(nullptr);
}
actionAdvancedBreakpoint.setText(hasBreakpoint ?
tr("Edit breakpoint") : tr("Advanced breakpoint"));
QString progCounterName = Core()->getRegisterName("PC").toUpper();
actionSetPC.setText(tr("Set %1 here").arg(progCounterName));
if (!annotationHere
|| annotationHere->type ==
RZ_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) { // If constant, don't show rename and targeted show-in
actionRenameThingHere.setVisible(false);
copySeparator->setVisible(false);
} else {
copySeparator->setVisible(true);
if (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_FUNCTION_NAME) {
actionRenameThingHere.setText(tr("Rename function %1").arg(QString(
annotationHere->reference.name)));
} else if (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) {
RzFlagItem *flagDetails = rz_flag_get_i(Core()->core()->flags, annotationHere->reference.offset);
if (flagDetails) {
actionRenameThingHere.setText(tr("Rename %1").arg(QString(flagDetails->name)));
actionDeleteName.setText(tr("Remove %1").arg(QString(flagDetails->name)));
actionDeleteName.setVisible(true);
} else {
actionRenameThingHere.setText(tr("Add name to %1").arg(curHighlightedWord));
}
}
}
actionCopyInstructionAddress.setText(tr("Copy instruction address (%1)").arg(RAddressString(
offset)));
if (isReference()) {
actionCopyReferenceAddress.setVisible(true);
RVA referenceAddr = annotationHere->reference.offset;
RzFlagItem *flagDetails = rz_flag_get_i(Core()->core()->flags, referenceAddr);
if (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_FUNCTION_NAME) {
actionCopyReferenceAddress.setText(tr("Copy address of %1 (%2)").arg
(QString(annotationHere->reference.name), RAddressString(referenceAddr)));
} else if (flagDetails) {
actionCopyReferenceAddress.setText(tr("Copy address of %1 (%2)").arg
(flagDetails->name, RAddressString(referenceAddr)));
} else {
actionCopyReferenceAddress.setText(tr("Copy address (%1)").arg(RAddressString(referenceAddr)));
}
} else {
actionXRefs.setVisible(false);
actionCopyReferenceAddress.setVisible(false);
}
if (actionShowInSubmenu.menu() != nullptr) {
actionShowInSubmenu.menu()->deleteLater();
}
actionShowInSubmenu.setMenu(mainWindow->createShowInMenu(this, offset));
updateTargetMenuActions();
if (!isFunctionVariable()) {
actionEditFunctionVariables.setVisible(false);
} else {
actionEditFunctionVariables.setText(tr("Edit variable %1").arg(QString(
annotationHere->variable.name)));
actionRenameThingHere.setText(tr("Rename variable %1").arg(QString(annotationHere->variable.name)));
if (!variablePresentInRizin()) {
actionEditFunctionVariables.setDisabled(true);
actionRenameThingHere.setDisabled(true);
setToolTipsVisible(true);
}
}
}
// Set up actions
void DecompilerContextMenu::setActionCopy() // Set all three copy actions
{
connect(&actionCopy, &QAction::triggered, this, &DecompilerContextMenu::actionCopyTriggered);
addAction(&actionCopy);
actionCopy.setShortcut(QKeySequence::Copy);
connect(&actionCopyInstructionAddress, &QAction::triggered, this,
&DecompilerContextMenu::actionCopyInstructionAddressTriggered);
addAction(&actionCopyInstructionAddress);
connect(&actionCopyReferenceAddress, &QAction::triggered, this,
&DecompilerContextMenu::actionCopyReferenceAddressTriggered);
addAction(&actionCopyReferenceAddress);
actionCopyReferenceAddress.setShortcut({Qt::CTRL + Qt::SHIFT + Qt::Key_C});
}
void DecompilerContextMenu::setActionShowInSubmenu()
{
addAction(&actionShowInSubmenu);
}
void DecompilerContextMenu::setActionAddComment()
{
connect(&actionAddComment, &QAction::triggered, this,
&DecompilerContextMenu::actionAddCommentTriggered);
addAction(&actionAddComment);
actionAddComment.setShortcut(Qt::Key_Semicolon);
}
void DecompilerContextMenu::setActionDeleteComment()
{
connect(&actionDeleteComment, &QAction::triggered, this,
&DecompilerContextMenu::actionDeleteCommentTriggered);
addAction(&actionDeleteComment);
}
void DecompilerContextMenu::setActionXRefs()
{
connect(&actionXRefs, &QAction::triggered, this, &DecompilerContextMenu::actionXRefsTriggered);
addAction(&actionXRefs);
actionXRefs.setShortcut(Qt::Key_X);
}
void DecompilerContextMenu::setActionRenameThingHere()
{
actionRenameThingHere.setShortcut({Qt::Key_N});
connect(&actionRenameThingHere, &QAction::triggered, this,
&DecompilerContextMenu::actionRenameThingHereTriggered);
addAction(&actionRenameThingHere);
actionRenameThingHere.setToolTip(tr("Can't rename this variable.<br>"
"Only local variables defined in disassembly can be renamed."));
}
void DecompilerContextMenu::setActionDeleteName()
{
connect(&actionDeleteName, &QAction::triggered, this,
&DecompilerContextMenu::actionDeleteNameTriggered);
addAction(&actionDeleteName);
actionDeleteName.setVisible(false);
}
void DecompilerContextMenu::setActionEditFunctionVariables()
{
connect(&actionEditFunctionVariables, &QAction::triggered, this,
&DecompilerContextMenu::actionEditFunctionVariablesTriggered);
addAction(&actionEditFunctionVariables);
actionEditFunctionVariables.setShortcut(Qt::Key_Y);
actionEditFunctionVariables.setToolTip(tr("Can't edit this variable.<br>"
"Only local variables defined in disassembly can be edited."));
}
void DecompilerContextMenu::setActionToggleBreakpoint()
{
connect(&actionToggleBreakpoint, &QAction::triggered, this,
&DecompilerContextMenu::actionToggleBreakpointTriggered);
actionToggleBreakpoint.setShortcuts({Qt::Key_F2, Qt::CTRL + Qt::Key_B});
}
void DecompilerContextMenu::setActionAdvancedBreakpoint()
{
connect(&actionAdvancedBreakpoint, &QAction::triggered, this,
&DecompilerContextMenu::actionAdvancedBreakpointTriggered);
actionAdvancedBreakpoint.setShortcut({Qt::CTRL + Qt::Key_F2});
}
void DecompilerContextMenu::setActionContinueUntil()
{
connect(&actionContinueUntil, &QAction::triggered, this,
&DecompilerContextMenu::actionContinueUntilTriggered);
}
void DecompilerContextMenu::setActionSetPC()
{
connect(&actionSetPC, &QAction::triggered, this, &DecompilerContextMenu::actionSetPCTriggered);
}
// Set up action responses
void DecompilerContextMenu::actionCopyTriggered()
{
emit copy();
}
void DecompilerContextMenu::actionCopyInstructionAddressTriggered()
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(RAddressString(offset));
}
void DecompilerContextMenu::actionCopyReferenceAddressTriggered()
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(RAddressString(annotationHere->reference.offset));
}
void DecompilerContextMenu::actionAddCommentTriggered()
{
CommentsDialog::addOrEditComment(this->firstOffsetInLine, this);
}
void DecompilerContextMenu::actionDeleteCommentTriggered()
{
Core()->delComment(this->firstOffsetInLine);
}
void DecompilerContextMenu::actionRenameThingHereTriggered()
{
if (!annotationHere || annotationHere->type == RZ_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) {
return;
}
RzCoreLocked core = Core()->core();
bool ok;
auto type = annotationHere->type;
if (type == RZ_CODE_ANNOTATION_TYPE_FUNCTION_NAME) {
QString currentName(annotationHere->reference.name);
RVA func_addr = annotationHere->reference.offset;
RzAnalysisFunction *func = Core()->functionAt(func_addr);
if (func == NULL) {
QString function_name = QInputDialog::getText(this,
tr("Define this function at %2").arg(RAddressString(func_addr)),
tr("Function name:"), QLineEdit::Normal, currentName, &ok);
if (ok && !function_name.isEmpty()) {
Core()->createFunctionAt(func_addr, function_name);
}
} else {
QString newName = QInputDialog::getText(this->mainWindow, tr("Rename function %2").arg(currentName),
tr("Function name:"), QLineEdit::Normal, currentName, &ok);
if (ok && !newName.isEmpty()) {
Core()->renameFunction(func_addr, newName);
}
}
} else if (type == RZ_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) {
RVA var_addr = annotationHere->reference.offset;
RzFlagItem *flagDetails = rz_flag_get_i(core->flags, var_addr);
if (flagDetails) {
QString newName = QInputDialog::getText(this, tr("Rename %2").arg(flagDetails->name),
tr("Enter name"), QLineEdit::Normal, flagDetails->name, &ok);
if (ok && !newName.isEmpty()) {
Core()->renameFlag(flagDetails->name, newName);
}
} else {
QString newName = QInputDialog::getText(this, tr("Add name to %2").arg(curHighlightedWord),
tr("Enter name"), QLineEdit::Normal, curHighlightedWord, &ok);
if (ok && !newName.isEmpty()) {
Core()->addFlag(var_addr, newName, 1);
}
}
} else if (isFunctionVariable()) {
if (!variablePresentInRizin()) {
// Show can't rename this variable dialog
QMessageBox::critical(this, tr("Rename local variable %1").arg(QString(
annotationHere->variable.name)),
tr("Can't rename this variable. "
"Only local variables defined in disassembly can be renamed."));
return;
}
QString oldName(annotationHere->variable.name);
QString newName = QInputDialog::getText(this, tr("Rename %2").arg(oldName),
tr("Enter name"), QLineEdit::Normal, oldName, &ok);
if (ok && !newName.isEmpty()) {
Core()->renameFunctionVariable(newName, oldName, decompiledFunctionAddress);
}
}
}
void DecompilerContextMenu::actionDeleteNameTriggered()
{
Core()->delFlag(annotationHere->reference.offset);
}
void DecompilerContextMenu::actionEditFunctionVariablesTriggered()
{
if (!isFunctionVariable()) {
return;
} else if (!variablePresentInRizin()) {
QMessageBox::critical(this, tr("Edit local variable %1").arg(QString(
annotationHere->variable.name)),
tr("Can't edit this variable. "
"Only local variables defined in disassembly can be edited."));
return;
}
EditVariablesDialog dialog(decompiledFunctionAddress, QString(annotationHere->variable.name), this);
dialog.exec();
}
void DecompilerContextMenu::actionXRefsTriggered()
{
if (!isReference()) {
return;
}
XrefsDialog dialog(mainWindow);
QString displayString = (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_FUNCTION_NAME) ? QString(
annotationHere->reference.name) : RAddressString(annotationHere->reference.offset);
dialog.fillRefsForAddress(annotationHere->reference.offset, displayString, false);
dialog.exec();
}
void DecompilerContextMenu::actionToggleBreakpointTriggered()
{
if (!this->availableBreakpoints.isEmpty()) {
setIsTogglingBreakpoints(true);
for (auto offsetToRemove : this->availableBreakpoints) {
Core()->toggleBreakpoint(offsetToRemove);
}
this->availableBreakpoints.clear();
setIsTogglingBreakpoints(false);
return;
}
if (this->firstOffsetInLine == RVA_MAX)
return;
Core()->toggleBreakpoint(this->firstOffsetInLine);
}
void DecompilerContextMenu::actionAdvancedBreakpointTriggered()
{
if (!availableBreakpoints.empty()) {
// Edit the earliest breakpoint in the line
BreakpointsDialog::editBreakpoint(Core()->getBreakpointAt(this->availableBreakpoints.first()),
this);
} else {
// Add a breakpoint to the earliest offset in the line
BreakpointsDialog::createNewBreakpoint(this->firstOffsetInLine, this);
}
}
void DecompilerContextMenu::actionContinueUntilTriggered()
{
Core()->continueUntilDebug(RAddressString(offset));
}
void DecompilerContextMenu::actionSetPCTriggered()
{
QString progCounterName = Core()->getRegisterName("PC");
Core()->setRegister(progCounterName, RAddressString(offset).toUpper());
}
// Set up menus
void DecompilerContextMenu::addBreakpointMenu()
{
breakpointMenu = addMenu(tr("Breakpoint"));
setActionToggleBreakpoint();
breakpointMenu->addAction(&actionToggleBreakpoint);
setActionAdvancedBreakpoint();
breakpointMenu->addAction(&actionAdvancedBreakpoint);
}
void DecompilerContextMenu::addDebugMenu()
{
debugMenu = addMenu(tr("Debug"));
setActionContinueUntil();
debugMenu->addAction(&actionContinueUntil);
setActionSetPC();
debugMenu->addAction(&actionSetPC);
}
void DecompilerContextMenu::updateTargetMenuActions()
{
for (auto action : showTargetMenuActions) {
removeAction(action);
auto menu = action->menu();
if (menu) {
menu->deleteLater();
}
action->deleteLater();
}
showTargetMenuActions.clear();
RzCoreLocked core = Core()->core();
if (isReference()) {
QString name;
QMenu *menu;
if (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE
|| annotationHere->type == RZ_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) {
menu = mainWindow->createShowInMenu(this, annotationHere->reference.offset,
MainWindow::AddressTypeHint::Data);
RVA var_addr = annotationHere->reference.offset;
RzFlagItem *flagDetails = rz_flag_get_i(core->flags, var_addr);
if (flagDetails) {
name = tr("Show %1 in").arg(flagDetails->name);
} else {
name = tr("Show %1 in").arg(RAddressString(annotationHere->reference.offset));
}
} else if (annotationHere->type == RZ_CODE_ANNOTATION_TYPE_FUNCTION_NAME) {
menu = mainWindow->createShowInMenu(this, annotationHere->reference.offset,
MainWindow::AddressTypeHint::Function);
name = tr("%1 (%2)").arg(QString(annotationHere->reference.name),
RAddressString(annotationHere->reference.offset));
}
auto action = new QAction(name, this);
showTargetMenuActions.append(action);
action->setMenu(menu);
insertActions(copySeparator, showTargetMenuActions);
}
}
bool DecompilerContextMenu::isReference()
{
return (annotationHere && rz_annotation_is_reference(annotationHere));
}
bool DecompilerContextMenu::isFunctionVariable()
{
return (annotationHere && rz_annotation_is_variable(annotationHere));
}
bool DecompilerContextMenu::variablePresentInRizin()
{
QString variableName(annotationHere->variable.name);
QList<VariableDescription> variables = Core()->getVariables(offset);
for (const VariableDescription &var : variables) {
if (var.name == variableName) {
return true;
}
}
return false;
}