diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 2878be15..7b8c3a35 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -676,6 +676,17 @@ void CutterCore::renameFlag(QString old_name, QString new_name) 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) { cmdRawAt("f-", addr); diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 7ba5b2eb..413e9af4 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -146,6 +146,15 @@ public: void renameFunction(const RVA offset, const QString &newName); void delFunction(RVA addr); 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 diff --git a/src/dialogs/EditVariablesDialog.cpp b/src/dialogs/EditVariablesDialog.cpp index 1f31eb0b..ea8eb5c2 100644 --- a/src/dialogs/EditVariablesDialog.cpp +++ b/src/dialogs/EditVariablesDialog.cpp @@ -9,7 +9,8 @@ EditVariablesDialog::EditVariablesDialog(RVA offset, QString initialVar, QWidget *parent) : QDialog(parent), - ui(new Ui::EditVariablesDialog) + ui(new Ui::EditVariablesDialog), + functionAddress(RVA_INVALID) { ui->setupUi(this); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &EditVariablesDialog::applyFields); @@ -17,7 +18,8 @@ EditVariablesDialog::EditVariablesDialog(RVA offset, QString initialVar, QWidget this, &EditVariablesDialog::updateFields); 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); int currentItemIndex = -1; @@ -63,7 +65,7 @@ void EditVariablesDialog::applyFields() .replace(QLatin1Char('\\'), QLatin1Char('_')) .replace(QLatin1Char('/'), QLatin1Char('_')); 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 diff --git a/src/dialogs/EditVariablesDialog.h b/src/dialogs/EditVariablesDialog.h index 5a017ca7..f0d0c4c1 100644 --- a/src/dialogs/EditVariablesDialog.h +++ b/src/dialogs/EditVariablesDialog.h @@ -23,6 +23,7 @@ private slots: private: Ui::EditVariablesDialog *ui; + RVA functionAddress; QList variables; void populateTypesComboBox(); diff --git a/src/menus/DecompilerContextMenu.cpp b/src/menus/DecompilerContextMenu.cpp index 277835d5..fe18b75e 100644 --- a/src/menus/DecompilerContextMenu.cpp +++ b/src/menus/DecompilerContextMenu.cpp @@ -3,6 +3,7 @@ #include "MainWindow.h" #include "dialogs/BreakpointsDialog.h" #include "dialogs/CommentsDialog.h" +#include "dialogs/EditVariablesDialog.h" #include "dialogs/XrefsDialog.h" #include "common/Configuration.h" @@ -18,6 +19,7 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi : QMenu(parent), curHighlightedWord(QString()), offset(0), + decompiledFunctionAddress(RVA_INVALID), isTogglingBreakpoints(false), mainWindow(mainWindow), annotationHere(nullptr), @@ -29,6 +31,7 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi actionDeleteComment(tr("Delete comment"), this), actionRenameThingHere(tr("Rename function at cursor"), this), actionDeleteName(tr("Delete "), this), + actionEditFunctionVariables(tr("Edit variable "), this), actionXRefs(tr("Show X-Refs"), this), actionToggleBreakpoint(tr("Add/remove breakpoint"), this), actionAdvancedBreakpoint(tr("Advanced breakpoint"), this), @@ -50,6 +53,8 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi setActionRenameThingHere(); setActionDeleteName(); + setActionEditFunctionVariables(); + addSeparator(); addBreakpointMenu(); addDebugMenu(); @@ -83,6 +88,11 @@ void DecompilerContextMenu::setOffset(RVA offset) // this->actionSetFunctionVarTypes.setVisible(true); } +void DecompilerContextMenu::setDecompiledFunctionAddress(RVA functionAddr) +{ + this->decompiledFunctionAddress = functionAddr; +} + void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset) { this->firstOffsetInLine = firstOffset; @@ -132,8 +142,12 @@ 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() @@ -190,11 +204,9 @@ void DecompilerContextMenu::aboutToShowSlot() } else { copySeparator->setVisible(true); if (annotationHere->type == R_CODE_ANNOTATION_TYPE_FUNCTION_NAME) { - actionRenameThingHere.setVisible(true); actionRenameThingHere.setText(tr("Rename function %1").arg(QString( annotationHere->reference.name))); - } - if (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) { + } else if (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) { RFlagItem *flagDetails = r_flag_get_i(Core()->core()->flags, annotationHere->reference.offset); if (flagDetails) { actionRenameThingHere.setText(tr("Rename %1").arg(QString(flagDetails->name))); @@ -229,6 +241,19 @@ void DecompilerContextMenu::aboutToShowSlot() } 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 (!variablePresentInR2()) { + actionEditFunctionVariables.setDisabled(true); + actionRenameThingHere.setDisabled(true); + setToolTipsVisible(true); + } + } } // Set up actions @@ -283,6 +308,8 @@ void DecompilerContextMenu::setActionRenameThingHere() connect(&actionRenameThingHere, &QAction::triggered, this, &DecompilerContextMenu::actionRenameThingHereTriggered); addAction(&actionRenameThingHere); + actionRenameThingHere.setToolTip(tr("Can't rename this variable.
" + "Only local variables defined in disassembly can be renamed.")); } void DecompilerContextMenu::setActionDeleteName() @@ -293,6 +320,16 @@ void DecompilerContextMenu::setActionDeleteName() 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.
" + "Only local variables defined in disassembly can be edited.")); +} + void DecompilerContextMenu::setActionToggleBreakpoint() { connect(&actionToggleBreakpoint, &QAction::triggered, this, @@ -390,7 +427,21 @@ void DecompilerContextMenu::actionRenameThingHereTriggered() 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); } +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() { if (!annotationHere || !r_annotation_is_reference(annotationHere)) { @@ -513,3 +579,20 @@ void DecompilerContextMenu::updateTargetMenuActions() insertActions(copySeparator, showTargetMenuActions); } } + +bool DecompilerContextMenu::isFunctionVariable() +{ + return (annotationHere && r_annotation_is_variable(annotationHere)); +} + +bool DecompilerContextMenu::variablePresentInR2() +{ + QString variableName(annotationHere->variable.name); + QList variables = Core()->getVariables(offset); + for (const VariableDescription &var : variables) { + if (var.name == variableName) { + return true; + } + } + return false; +} diff --git a/src/menus/DecompilerContextMenu.h b/src/menus/DecompilerContextMenu.h index 9f896129..86402e40 100644 --- a/src/menus/DecompilerContextMenu.h +++ b/src/menus/DecompilerContextMenu.h @@ -24,6 +24,7 @@ signals: public slots: void setCurHighlightedWord(QString word); void setOffset(RVA offset); + void setDecompiledFunctionAddress(RVA functionAddr); void setFirstOffsetInLine(RVA firstOffset); void setAvailableBreakpoints(QVector offsetList); @@ -42,6 +43,8 @@ private slots: void actionRenameThingHereTriggered(); void actionDeleteNameTriggered(); + void actionEditFunctionVariablesTriggered(); + void actionXRefsTriggered(); void actionToggleBreakpointTriggered(); @@ -54,6 +57,7 @@ private: // Private variables QString curHighlightedWord; RVA offset; + RVA decompiledFunctionAddress; RVA firstOffsetInLine; bool isTogglingBreakpoints; QVector availableBreakpoints; @@ -75,6 +79,8 @@ private: QAction actionRenameThingHere; QAction actionDeleteName; + QAction actionEditFunctionVariables; + QAction actionXRefs; QMenu *breakpointMenu; @@ -105,6 +111,8 @@ private: void setActionRenameThingHere(); void setActionDeleteName(); + void setActionEditFunctionVariables(); + void setActionToggleBreakpoint(); void setActionAdvancedBreakpoint(); @@ -114,25 +122,11 @@ private: // Add Menus void addBreakpointMenu(); 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 getThingUsedHere(RVA offset); - // void updateTargetMenuActions(const QVector &targets); void updateTargetMenuActions(); + + bool isFunctionVariable(); + bool variablePresentInR2(); }; #endif // DECOMPILERCONTEXTMENU_H \ No newline at end of file diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index 4987c1a1..91b7541a 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -883,7 +883,7 @@ void DisassemblyContextMenu::on_actionSetFunctionVarTypes_triggered() 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 return; } diff --git a/src/widgets/DecompilerWidget.cpp b/src/widgets/DecompilerWidget.cpp index ba52385e..e55d58ce 100644 --- a/src/widgets/DecompilerWidget.cpp +++ b/src/widgets/DecompilerWidget.cpp @@ -269,6 +269,7 @@ void DecompilerWidget::doRefresh(RVA addr) ui->textEdit->setExtraSelections({}); previousFunctionAddr = decompiledFunctionAddr; decompiledFunctionAddr = Core()->getFunctionStart(addr); + mCtxMenu->setDecompiledFunctionAddress(decompiledFunctionAddr); dec->decompileAt(addr); if (dec->isRunning()) { ui->progressLabel->setVisible(true);