Edit/Rename Variables Actions for function variables (#2357)

* Edit Function Variables Action

* Rename Function Variables Action

* CutterCore::renameFunctionVariable
This commit is contained in:
NIRMAL MANOJ C 2020-08-06 14:02:46 +05:30
parent 6ed32d5d1d
commit 1c86f54c95
8 changed files with 126 additions and 25 deletions

View File

@ -676,6 +676,17 @@ void CutterCore::renameFlag(QString old_name, QString new_name)
emit flagsChanged(); emit flagsChanged();
} }
void CutterCore::renameFunctionVariable(QString newName, QString oldName, RVA functionAddress)
{
CORE_LOCK();
RAnalFunction *function = r_anal_get_function_at(core->anal, functionAddress);
RAnalVar *variable = r_anal_function_get_var_byname(function, oldName.toUtf8().constData());
if (variable) {
r_anal_var_rename(variable, newName.toUtf8().constData(), true);
}
emit refreshCodeViews();
}
void CutterCore::delFlag(RVA addr) void CutterCore::delFlag(RVA addr)
{ {
cmdRawAt("f-", addr); cmdRawAt("f-", addr);

View File

@ -146,6 +146,15 @@ public:
void renameFunction(const RVA offset, const QString &newName); void renameFunction(const RVA offset, const QString &newName);
void delFunction(RVA addr); void delFunction(RVA addr);
void renameFlag(QString old_name, QString new_name); void renameFlag(QString old_name, QString new_name);
/**
* @brief Renames the specified local variable in the function specified by the
* address given.
* @param newName Specifies the name to which the current name of the variable
* should be renamed.
* @param oldName Specifies the current name of the function variable.
* @param functionAddress Specifies the exact address of the function.
*/
void renameFunctionVariable(QString newName, QString oldName, RVA functionAddress);
/** /**
* @param addr * @param addr

View File

@ -9,7 +9,8 @@
EditVariablesDialog::EditVariablesDialog(RVA offset, QString initialVar, QWidget *parent) : EditVariablesDialog::EditVariablesDialog(RVA offset, QString initialVar, QWidget *parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::EditVariablesDialog) ui(new Ui::EditVariablesDialog),
functionAddress(RVA_INVALID)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &EditVariablesDialog::applyFields); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &EditVariablesDialog::applyFields);
@ -17,7 +18,8 @@ EditVariablesDialog::EditVariablesDialog(RVA offset, QString initialVar, QWidget
this, &EditVariablesDialog::updateFields); this, &EditVariablesDialog::updateFields);
QString fcnName = Core()->cmdRawAt("afn.", offset).trimmed(); QString fcnName = Core()->cmdRawAt("afn.", offset).trimmed();
setWindowTitle(tr("Set Variable Types for Function: %1").arg(fcnName)); functionAddress = offset;
setWindowTitle(tr("Edit Variables in Function: %1").arg(fcnName));
variables = Core()->getVariables(offset); variables = Core()->getVariables(offset);
int currentItemIndex = -1; int currentItemIndex = -1;
@ -63,7 +65,7 @@ void EditVariablesDialog::applyFields()
.replace(QLatin1Char('\\'), QLatin1Char('_')) .replace(QLatin1Char('\\'), QLatin1Char('_'))
.replace(QLatin1Char('/'), QLatin1Char('_')); .replace(QLatin1Char('/'), QLatin1Char('_'));
if (newName != desc.name) { if (newName != desc.name) {
Core()->cmdRaw(QString("afvn %1 %2").arg(newName).arg(desc.name)); Core()->renameFunctionVariable(newName, desc.name, functionAddress);
} }
// Refresh the views to reflect the changes to vars // Refresh the views to reflect the changes to vars

View File

@ -23,6 +23,7 @@ private slots:
private: private:
Ui::EditVariablesDialog *ui; Ui::EditVariablesDialog *ui;
RVA functionAddress;
QList<VariableDescription> variables; QList<VariableDescription> variables;
void populateTypesComboBox(); void populateTypesComboBox();

View File

@ -3,6 +3,7 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "dialogs/BreakpointsDialog.h" #include "dialogs/BreakpointsDialog.h"
#include "dialogs/CommentsDialog.h" #include "dialogs/CommentsDialog.h"
#include "dialogs/EditVariablesDialog.h"
#include "dialogs/XrefsDialog.h" #include "dialogs/XrefsDialog.h"
#include "common/Configuration.h" #include "common/Configuration.h"
@ -18,6 +19,7 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi
: QMenu(parent), : QMenu(parent),
curHighlightedWord(QString()), curHighlightedWord(QString()),
offset(0), offset(0),
decompiledFunctionAddress(RVA_INVALID),
isTogglingBreakpoints(false), isTogglingBreakpoints(false),
mainWindow(mainWindow), mainWindow(mainWindow),
annotationHere(nullptr), annotationHere(nullptr),
@ -29,6 +31,7 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi
actionDeleteComment(tr("Delete comment"), this), actionDeleteComment(tr("Delete comment"), this),
actionRenameThingHere(tr("Rename function at cursor"), this), actionRenameThingHere(tr("Rename function at cursor"), this),
actionDeleteName(tr("Delete <name>"), this), actionDeleteName(tr("Delete <name>"), this),
actionEditFunctionVariables(tr("Edit variable <name of variable>"), this),
actionXRefs(tr("Show X-Refs"), this), actionXRefs(tr("Show X-Refs"), this),
actionToggleBreakpoint(tr("Add/remove breakpoint"), this), actionToggleBreakpoint(tr("Add/remove breakpoint"), this),
actionAdvancedBreakpoint(tr("Advanced breakpoint"), this), actionAdvancedBreakpoint(tr("Advanced breakpoint"), this),
@ -50,6 +53,8 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi
setActionRenameThingHere(); setActionRenameThingHere();
setActionDeleteName(); setActionDeleteName();
setActionEditFunctionVariables();
addSeparator(); addSeparator();
addBreakpointMenu(); addBreakpointMenu();
addDebugMenu(); addDebugMenu();
@ -83,6 +88,11 @@ void DecompilerContextMenu::setOffset(RVA offset)
// this->actionSetFunctionVarTypes.setVisible(true); // this->actionSetFunctionVarTypes.setVisible(true);
} }
void DecompilerContextMenu::setDecompiledFunctionAddress(RVA functionAddr)
{
this->decompiledFunctionAddress = functionAddr;
}
void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset) void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset)
{ {
this->firstOffsetInLine = firstOffset; this->firstOffsetInLine = firstOffset;
@ -132,8 +142,12 @@ void DecompilerContextMenu::aboutToHideSlot()
{ {
actionAddComment.setVisible(true); actionAddComment.setVisible(true);
actionRenameThingHere.setVisible(true); actionRenameThingHere.setVisible(true);
actionRenameThingHere.setEnabled(true);
actionDeleteName.setVisible(false); actionDeleteName.setVisible(false);
actionEditFunctionVariables.setVisible(true);
actionEditFunctionVariables.setEnabled(true);
actionXRefs.setVisible(true); actionXRefs.setVisible(true);
setToolTipsVisible(false);
} }
void DecompilerContextMenu::aboutToShowSlot() void DecompilerContextMenu::aboutToShowSlot()
@ -190,11 +204,9 @@ void DecompilerContextMenu::aboutToShowSlot()
} else { } else {
copySeparator->setVisible(true); copySeparator->setVisible(true);
if (annotationHere->type == R_CODE_ANNOTATION_TYPE_FUNCTION_NAME) { if (annotationHere->type == R_CODE_ANNOTATION_TYPE_FUNCTION_NAME) {
actionRenameThingHere.setVisible(true);
actionRenameThingHere.setText(tr("Rename function %1").arg(QString( actionRenameThingHere.setText(tr("Rename function %1").arg(QString(
annotationHere->reference.name))); annotationHere->reference.name)));
} } else if (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) {
if (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) {
RFlagItem *flagDetails = r_flag_get_i(Core()->core()->flags, annotationHere->reference.offset); RFlagItem *flagDetails = r_flag_get_i(Core()->core()->flags, annotationHere->reference.offset);
if (flagDetails) { if (flagDetails) {
actionRenameThingHere.setText(tr("Rename %1").arg(QString(flagDetails->name))); actionRenameThingHere.setText(tr("Rename %1").arg(QString(flagDetails->name)));
@ -229,6 +241,19 @@ void DecompilerContextMenu::aboutToShowSlot()
} }
actionShowInSubmenu.setMenu(mainWindow->createShowInMenu(this, offset)); actionShowInSubmenu.setMenu(mainWindow->createShowInMenu(this, offset));
updateTargetMenuActions(); 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 (!variablePresentInR2()) {
actionEditFunctionVariables.setDisabled(true);
actionRenameThingHere.setDisabled(true);
setToolTipsVisible(true);
}
}
} }
// Set up actions // Set up actions
@ -283,6 +308,8 @@ void DecompilerContextMenu::setActionRenameThingHere()
connect(&actionRenameThingHere, &QAction::triggered, this, connect(&actionRenameThingHere, &QAction::triggered, this,
&DecompilerContextMenu::actionRenameThingHereTriggered); &DecompilerContextMenu::actionRenameThingHereTriggered);
addAction(&actionRenameThingHere); addAction(&actionRenameThingHere);
actionRenameThingHere.setToolTip(tr("Can't rename this variable.<br>"
"Only local variables defined in disassembly can be renamed."));
} }
void DecompilerContextMenu::setActionDeleteName() void DecompilerContextMenu::setActionDeleteName()
@ -293,6 +320,16 @@ void DecompilerContextMenu::setActionDeleteName()
actionDeleteName.setVisible(false); 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() void DecompilerContextMenu::setActionToggleBreakpoint()
{ {
connect(&actionToggleBreakpoint, &QAction::triggered, this, connect(&actionToggleBreakpoint, &QAction::triggered, this,
@ -390,7 +427,21 @@ void DecompilerContextMenu::actionRenameThingHereTriggered()
Core()->addFlag(var_addr, newName, 1); Core()->addFlag(var_addr, newName, 1);
} }
} }
} else if (isFunctionVariable()) {
if (!variablePresentInR2()) {
// 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);
}
} }
} }
@ -399,6 +450,21 @@ void DecompilerContextMenu::actionDeleteNameTriggered()
Core()->delFlag(annotationHere->reference.offset); Core()->delFlag(annotationHere->reference.offset);
} }
void DecompilerContextMenu::actionEditFunctionVariablesTriggered()
{
if (!isFunctionVariable()) {
return;
} else if (!variablePresentInR2()) {
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() void DecompilerContextMenu::actionXRefsTriggered()
{ {
if (!annotationHere || !r_annotation_is_reference(annotationHere)) { if (!annotationHere || !r_annotation_is_reference(annotationHere)) {
@ -513,3 +579,20 @@ void DecompilerContextMenu::updateTargetMenuActions()
insertActions(copySeparator, showTargetMenuActions); insertActions(copySeparator, showTargetMenuActions);
} }
} }
bool DecompilerContextMenu::isFunctionVariable()
{
return (annotationHere && r_annotation_is_variable(annotationHere));
}
bool DecompilerContextMenu::variablePresentInR2()
{
QString variableName(annotationHere->variable.name);
QList<VariableDescription> variables = Core()->getVariables(offset);
for (const VariableDescription &var : variables) {
if (var.name == variableName) {
return true;
}
}
return false;
}

View File

@ -24,6 +24,7 @@ signals:
public slots: public slots:
void setCurHighlightedWord(QString word); void setCurHighlightedWord(QString word);
void setOffset(RVA offset); void setOffset(RVA offset);
void setDecompiledFunctionAddress(RVA functionAddr);
void setFirstOffsetInLine(RVA firstOffset); void setFirstOffsetInLine(RVA firstOffset);
void setAvailableBreakpoints(QVector<RVA> offsetList); void setAvailableBreakpoints(QVector<RVA> offsetList);
@ -42,6 +43,8 @@ private slots:
void actionRenameThingHereTriggered(); void actionRenameThingHereTriggered();
void actionDeleteNameTriggered(); void actionDeleteNameTriggered();
void actionEditFunctionVariablesTriggered();
void actionXRefsTriggered(); void actionXRefsTriggered();
void actionToggleBreakpointTriggered(); void actionToggleBreakpointTriggered();
@ -54,6 +57,7 @@ private:
// Private variables // Private variables
QString curHighlightedWord; QString curHighlightedWord;
RVA offset; RVA offset;
RVA decompiledFunctionAddress;
RVA firstOffsetInLine; RVA firstOffsetInLine;
bool isTogglingBreakpoints; bool isTogglingBreakpoints;
QVector<RVA> availableBreakpoints; QVector<RVA> availableBreakpoints;
@ -75,6 +79,8 @@ private:
QAction actionRenameThingHere; QAction actionRenameThingHere;
QAction actionDeleteName; QAction actionDeleteName;
QAction actionEditFunctionVariables;
QAction actionXRefs; QAction actionXRefs;
QMenu *breakpointMenu; QMenu *breakpointMenu;
@ -105,6 +111,8 @@ private:
void setActionRenameThingHere(); void setActionRenameThingHere();
void setActionDeleteName(); void setActionDeleteName();
void setActionEditFunctionVariables();
void setActionToggleBreakpoint(); void setActionToggleBreakpoint();
void setActionAdvancedBreakpoint(); void setActionAdvancedBreakpoint();
@ -114,25 +122,11 @@ private:
// Add Menus // Add Menus
void addBreakpointMenu(); void addBreakpointMenu();
void addDebugMenu(); void addDebugMenu();
// I left out the following part from RAnnotatedCode. Probably, we will be returning/passing annotations
// from/to the function getThingUsedHere() and updateTargetMenuActions(). This block of comment will get removed in
// future PRs.
//
// 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);
void updateTargetMenuActions(); void updateTargetMenuActions();
bool isFunctionVariable();
bool variablePresentInR2();
}; };
#endif // DECOMPILERCONTEXTMENU_H #endif // DECOMPILERCONTEXTMENU_H

View File

@ -883,7 +883,7 @@ void DisassemblyContextMenu::on_actionSetFunctionVarTypes_triggered()
return; return;
} }
EditVariablesDialog dialog(Core()->getOffset(), curHighlightedWord, this); EditVariablesDialog dialog(fcn->addr, curHighlightedWord, this);
if (dialog.empty()) { // don't show the dialog if there are no variables if (dialog.empty()) { // don't show the dialog if there are no variables
return; return;
} }

View File

@ -269,6 +269,7 @@ void DecompilerWidget::doRefresh(RVA addr)
ui->textEdit->setExtraSelections({}); ui->textEdit->setExtraSelections({});
previousFunctionAddr = decompiledFunctionAddr; previousFunctionAddr = decompiledFunctionAddr;
decompiledFunctionAddr = Core()->getFunctionStart(addr); decompiledFunctionAddr = Core()->getFunctionStart(addr);
mCtxMenu->setDecompiledFunctionAddress(decompiledFunctionAddr);
dec->decompileAt(addr); dec->decompileAt(addr);
if (dec->isRunning()) { if (dec->isRunning()) {
ui->progressLabel->setVisible(true); ui->progressLabel->setVisible(true);