Reworked renaming in disassembly widgets (#2468)

This commit is contained in:
xarkes 2020-12-04 18:08:56 +01:00 committed by GitHub
parent b07bffc5fe
commit b02100b66b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 230 deletions

View File

@ -47,7 +47,7 @@ Add Flag
Rename
----------------------------------------
**Description:** Rename the flag in the current location.
**Description:** Rename the flag, function or local variable at current location. If empty, remove the currently associated name.
**Steps:** Right-click an address or item and choose ``Rename``
@ -61,14 +61,6 @@ Edit Function
**Shortcut:** :kbd:`Shift` + :kbd:`P`
Rename Flag/Function/Variable
----------------------------------------
**Description:** Rename a specific flag, variable or function under the cursor.
**Steps:** -> Rename Flag/Fcn/Var Used Here
**Shortcut:** :kbd:`Shift` + :kbd:`N`
Re-type Local Variables
----------------------------------------
**Description:** Rename or set the types of the function's variables and arguments.

View File

@ -8,26 +8,65 @@
FlagDialog::FlagDialog(RVA offset, QWidget *parent) :
QDialog(parent),
ui(new Ui::FlagDialog),
offset(offset)
offset(offset),
flagName(""),
flagOffset(RVA_INVALID)
{
// Setup UI
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
RFlagItem* flag = r_flag_get_i(Core()->core()->flags, offset);
if (flag) {
flagName = QString(flag->name);
flagOffset = flag->offset;
}
auto size_validator = new QIntValidator(ui->sizeEdit);
size_validator->setBottom(1);
ui->sizeEdit->setValidator(size_validator);
if (flag) {
ui->nameEdit->setText(flag->name);
ui->labelAction->setText(tr("Edit flag at %1").arg(RAddressString(offset)));
} else {
ui->labelAction->setText(tr("Add flag at %1").arg(RAddressString(offset)));
}
// Connect slots
connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &FlagDialog::buttonBoxAccepted);
connect(ui->buttonBox, &QDialogButtonBox::rejected,
this, &FlagDialog::buttonBoxRejected);
}
FlagDialog::~FlagDialog() {}
FlagDialog::~FlagDialog() { }
void FlagDialog::on_buttonBox_accepted()
void FlagDialog::buttonBoxAccepted()
{
QString name = ui->nameEdit->text();
RVA size = ui->sizeEdit->text().toULongLong();
Core()->addFlag(offset, name, size);
QString name = ui->nameEdit->text();
if (name.isEmpty()) {
if (flagOffset != RVA_INVALID) {
// Empty name and flag exists -> delete the flag
Core()->delFlag(flagOffset);
} else {
// Flag was not existing and we gave an empty name, do nothing
}
} else {
if (flagOffset != RVA_INVALID) {
// Name provided and flag exists -> rename the flag
Core()->renameFlag(flagName, name);
} else {
// Name provided and flag does not exist -> create the flag
Core()->addFlag(offset, name, size);
}
}
close();
this->setResult(QDialog::Accepted);
}
void FlagDialog::on_buttonBox_rejected()
void FlagDialog::buttonBoxRejected()
{
close();
this->setResult(QDialog::Rejected);
}

View File

@ -19,12 +19,14 @@ public:
~FlagDialog();
private slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
void buttonBoxAccepted();
void buttonBoxRejected();
private:
std::unique_ptr<Ui::FlagDialog> ui;
RVA offset;
QString flagName;
ut64 flagOffset;
};
#endif // FLAGDIALOG_H

View File

@ -14,13 +14,20 @@
<string>Add Flag</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelAction">
<property name="text">
<string>Add flag at</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>12</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<widget class="QLabel" name="labelFlag">
<property name="font">
<font>
<weight>75</weight>
@ -62,7 +69,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="labelSize">
<property name="font">
<font>
<weight>75</weight>
@ -89,38 +96,5 @@
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>FlagDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>FlagDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

View File

@ -32,11 +32,9 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
actionCopy(this),
actionCopyAddr(this),
actionAddComment(this),
actionAddFlag(this),
actionAnalyzeFunction(this),
actionEditFunction(this),
actionRename(this),
actionRenameUsedHere(this),
actionSetFunctionVarTypes(this),
actionXRefs(this),
actionXRefsForVariables(this),
@ -87,26 +85,18 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
SLOT(on_actionAddComment_triggered()), getCommentSequence());
addAction(&actionAddComment);
initAction(&actionAddFlag, tr("Add Flag"),
SLOT(on_actionAddFlag_triggered()), getAddFlagSequence());
addAction(&actionAddFlag);
initAction(&actionRename, tr("Rename"),
initAction(&actionRename, tr("Rename or add flag"),
SLOT(on_actionRename_triggered()), getRenameSequence());
addAction(&actionRename);
initAction(&actionEditFunction, tr("Edit function"),
SLOT(on_actionEditFunction_triggered()), getEditFunctionSequence());
addAction(&actionEditFunction);
initAction(&actionRenameUsedHere, tr("Rename Flag/Fcn/Var Used Here"),
SLOT(on_actionRenameUsedHere_triggered()), getRenameUsedHereSequence());
addAction(&actionRenameUsedHere);
initAction(&actionSetFunctionVarTypes, tr("Re-type Local Variables"),
SLOT(on_actionSetFunctionVarTypes_triggered()), getRetypeSequence());
addAction(&actionSetFunctionVarTypes);
initAction(&actionEditFunction, tr("Edit function"),
SLOT(on_actionEditFunction_triggered()), getEditFunctionSequence());
addAction(&actionEditFunction);
initAction(&actionDeleteComment, tr("Delete comment"), SLOT(on_actionDeleteComment_triggered()));
addAction(&actionDeleteComment);
@ -361,44 +351,9 @@ QVector<DisassemblyContextMenu::ThingUsedHere> DisassemblyContextMenu::getThingU
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;
this->actionSetFunctionVarTypes.setVisible(true);
}
@ -410,6 +365,123 @@ void DisassemblyContextMenu::setCanCopy(bool enabled)
void DisassemblyContextMenu::setCurHighlightedWord(const QString &text)
{
this->curHighlightedWord = text;
// Update the renaming options only when a new word is selected
setupRenaming();
}
DisassemblyContextMenu::ThingUsedHere DisassemblyContextMenu::getThingAt(ut64 address)
{
ThingUsedHere tuh;
RAnalFunction *fcn = Core()->functionAt(address);
RFlagItem *flag = r_flag_get_i(Core()->core()->flags, address);
// We will lookup through existing r2 types to find something relevant
if (fcn != nullptr) {
// It is a function
tuh.type = ThingUsedHere::Type::Function;
tuh.name = fcn->name;
} else if (flag != nullptr) {
// It is a flag
tuh.type = ThingUsedHere::Type::Flag;
if (Config()->getConfigBool("asm.flags.real") && flag->realname) {
tuh.name = flag->realname;
} else {
tuh.name = flag->name;
}
} else {
// Consider it an address
tuh.type = ThingUsedHere::Type::Address;
}
tuh.offset = address;
return tuh;
}
void DisassemblyContextMenu::buildRenameMenu(ThingUsedHere* tuh)
{
if (!tuh) {
qWarning() << "Unexpected behavior null pointer passed to DisassemblyContextMenu::buildRenameMenu";
doRenameAction = RENAME_DO_NOTHING;
return;
}
actionDeleteFlag.setVisible(false);
if (tuh->type == ThingUsedHere::Type::Address) {
doRenameAction = RENAME_ADD_FLAG;
doRenameInfo.name = RAddressString(tuh->offset);
doRenameInfo.addr = tuh->offset;
actionRename.setText(tr("Add flag at %1 (used here)").arg(doRenameInfo.name));
} else if (tuh->type == ThingUsedHere::Type::Function) {
doRenameAction = RENAME_FUNCTION;
doRenameInfo.name = tuh->name;
doRenameInfo.addr = tuh->offset;
actionRename.setText(tr("Rename \"%1\"").arg(doRenameInfo.name));
} else if (tuh->type == ThingUsedHere::Type::Var) {
doRenameAction = RENAME_LOCAL;
doRenameInfo.name = tuh->name;
doRenameInfo.addr = tuh->offset;
actionRename.setText(tr("Rename local \"%1\"").arg(tuh->name));
} else if (tuh->type == ThingUsedHere::Type::Flag) {
doRenameAction = RENAME_FLAG;
doRenameInfo.name = tuh->name;
doRenameInfo.addr = tuh->offset;
actionRename.setText(tr("Rename flag \"%1\" (used here)").arg(doRenameInfo.name));
actionDeleteFlag.setVisible(true);
} else {
qWarning() << "Unexpected renaming type";
doRenameAction = RENAME_DO_NOTHING;
}
}
void DisassemblyContextMenu::setupRenaming()
{
// We parse our highlighted word as an address
ut64 selection = Core()->num(curHighlightedWord);
// First, let's try to see if current line (offset) contains a local variable or a function
ThingUsedHere *tuh = nullptr;
ThingUsedHere thingAt;
auto things = getThingUsedHere(offset);
for (auto& thing : things) {
if (thing.offset == selection || thing.name == curHighlightedWord) {
// We matched something on current line
tuh = &thing;
break;
}
}
if (!tuh) {
// Nothing matched on current line, is there anything valid coming from our selection?
thingAt = getThingAt(selection);
if (thingAt.offset == 0) {
// We parsed something which resolved to 0, it's very likely nothing interesting was selected
// So we fallback on current line offset
thingAt = getThingAt(offset);
}
// However, since for the moment selection selects *every* lines which match a specific offset,
// make sure we didn't want to select a local variable rather than the function itself
if (thingAt.type == ThingUsedHere::Type::Function) {
auto vars = Core()->getVariables(offset);
for (auto v : vars) {
if (v.name == curHighlightedWord) {
// This is a local variable
thingAt.type = ThingUsedHere::Type::Var;
thingAt.name = v.name;
break;
}
}
}
// In any case, thingAt will contain something we can rename
tuh = &thingAt;
}
// Now, build the renaming menu and show it
buildRenameMenu(tuh);
actionRename.setVisible(true);
}
void DisassemblyContextMenu::aboutToShowSlot()
@ -483,36 +555,12 @@ void DisassemblyContextMenu::aboutToShowSlot()
actionCopy.setVisible(canCopy);
copySeparator->setVisible(canCopy);
RCore *core = Core()->core();
RAnalFunction *fcn = Core()->functionAt(offset);
RAnalFunction *in_fcn = Core()->functionIn(offset);
RFlagItem *f = r_flag_get_i (core->flags, offset);
actionDeleteFlag.setVisible(f ? true : false);
actionDeleteFunction.setVisible(fcn ? true : false);
if (fcn) {
actionAnalyzeFunction.setVisible(false);
actionRename.setVisible(true);
actionRename.setText(tr("Rename function \"%1\"").arg(fcn->name));
} else if (f) {
QString name;
// Check if Realname is enabled. If yes, show it instead of the full flag-name.
if (Config()->getConfigBool("asm.flags.real") && f->realname) {
name = f->realname;
} else {
name = f->name;
}
actionRename.setVisible(true);
actionRename.setText(tr("Rename flag \"%1\"").arg(name));
} else {
actionRename.setVisible(false);
}
// Handle renaming of variable, function, flag, ...
// Note: This might be useless if we consider setCurrentHighlightedWord is always called before
setupRenaming();
// Only show retype for local vars if in a function
RAnalFunction *in_fcn = Core()->functionIn(offset);
if (in_fcn) {
auto vars = Core()->getVariables(offset);
actionSetFunctionVarTypes.setVisible(!vars.empty());
@ -523,25 +571,6 @@ void DisassemblyContextMenu::aboutToShowSlot()
actionEditFunction.setVisible(false);
}
// Only show "rename X used here" if there is something to rename
auto thingsUsedHere = getThingUsedHere(offset);
if (!thingsUsedHere.isEmpty()) {
actionRenameUsedHere.setVisible(true);
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 == 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();
@ -619,21 +648,11 @@ QKeySequence DisassemblyContextMenu::getSetToDataExSequence() const
return {Qt::Key_Asterisk};
}
QKeySequence DisassemblyContextMenu::getAddFlagSequence() const
{
return {}; //TODO insert correct sequence
}
QKeySequence DisassemblyContextMenu::getRenameSequence() const
{
return {Qt::Key_N};
}
QKeySequence DisassemblyContextMenu::getRenameUsedHereSequence() const
{
return {Qt::SHIFT | Qt::Key_N};
}
QKeySequence DisassemblyContextMenu::getRetypeSequence() const
{
return {Qt::Key_Y};
@ -803,77 +822,37 @@ void DisassemblyContextMenu::on_actionAnalyzeFunction_triggered()
}
}
void DisassemblyContextMenu::on_actionAddFlag_triggered()
{
FlagDialog dialog(offset, this->parentWidget());
dialog.exec();
}
void DisassemblyContextMenu::on_actionRename_triggered()
{
RCore *core = Core()->core();
bool ok;
RAnalFunction *fcn = Core()->functionIn(offset);
RFlagItem *f = r_flag_get_i(core->flags, offset);
if (fcn) {
// Renaming a function
QString newName = QInputDialog::getText(this->mainWindow, tr("Rename function %2").arg(fcn->name),
tr("Function name:"), QLineEdit::Normal, fcn->name, &ok);
bool ok = false;
if (doRenameAction == RENAME_FUNCTION) {
QString newName = QInputDialog::getText(this->mainWindow, tr("Rename function %2").arg(doRenameInfo.name),
tr("Function name:"), QLineEdit::Normal, doRenameInfo.name, &ok);
if (ok && !newName.isEmpty()) {
Core()->renameFunction(fcn->addr, newName);
Core()->renameFunction(doRenameInfo.addr, newName);
}
} else if (f) {
// Renaming flag
QString newName = QInputDialog::getText(this, tr("Rename flag %2").arg(f->name),
tr("Flag name:"), QLineEdit::Normal, f->name, &ok);
if (ok && !newName.isEmpty()) {
Core()->renameFlag(f->name, newName);
} else if (doRenameAction == RENAME_FLAG || doRenameAction == RENAME_ADD_FLAG) {
FlagDialog dialog(doRenameInfo.addr, this->mainWindow);
ok = dialog.exec();
} else if (doRenameAction == RENAME_LOCAL) {
RAnalFunction *fcn = Core()->functionIn(offset);
if (fcn) {
EditVariablesDialog dialog(fcn->addr, curHighlightedWord, this->mainWindow);
if (!dialog.empty()) {
// Don't show the dialog if there are no variables
ok = dialog.exec();
}
}
}
}
void DisassemblyContextMenu::on_actionRenameUsedHere_triggered()
{
QString title;
QString inputValue;
QString oldName;
auto array = getThingUsedHere(offset);
if (array.isEmpty()) {
return;
}
auto thingUsedHere = array.first();
auto type = thingUsedHere.type;
if (type == ThingUsedHere::Type::Address) {
RVA offset = thingUsedHere.offset;
title = tr("Add flag at %1").arg(RAddressString(offset));
inputValue = "label." + QString::number(offset, 16);
} else if (doRenameAction == RENAME_DO_NOTHING) {
// Do nothing
} else {
oldName = thingUsedHere.name;
title = tr("Rename %1").arg(oldName);
inputValue = oldName;
qWarning() << "Unhandled renaming action: " << doRenameAction;
assert(false);
}
bool ok;
// Create dialog
QString newName = QInputDialog::getText(this, title,
tr("Name:"), QLineEdit::Normal, inputValue, &ok);
// If user accepted
if (ok && !newName.isEmpty()) {
Core()->cmdRawAt(QString("an %1").arg(newName), offset);
if (type == ThingUsedHere::Type::Address || type == ThingUsedHere::Type::Flag) {
Core()->triggerFlagsChanged();
} else if (type == ThingUsedHere::Type::Var) {
Core()->triggerVarsChanged();
} else if (type == ThingUsedHere::Type::Function) {
Core()->triggerFunctionRenamed(thingUsedHere.offset, newName);
}
if (ok) {
// Rebuild menu in case the user presses the rename shortcut directly before clicking
setupRenaming();
}
}

View File

@ -44,9 +44,7 @@ private slots:
void on_actionCopyAddr_triggered();
void on_actionAddComment_triggered();
void on_actionAnalyzeFunction_triggered();
void on_actionAddFlag_triggered();
void on_actionRename_triggered();
void on_actionRenameUsedHere_triggered();
void on_actionSetFunctionVarTypes_triggered();
void on_actionXRefs_triggered();
void on_actionXRefsForVariables_triggered();
@ -92,9 +90,7 @@ private:
QKeySequence getSetAsStringAdvanced() const;
QKeySequence getSetToDataSequence() const;
QKeySequence getSetToDataExSequence() const;
QKeySequence getAddFlagSequence() const;
QKeySequence getRenameSequence() const;
QKeySequence getRenameUsedHereSequence() const;
QKeySequence getRetypeSequence() const;
QKeySequence getXRefSequence() const;
QKeySequence getDisplayOptionsSequence() const;
@ -129,11 +125,9 @@ private:
QAction actionAddComment;
QAction actionAddFlag;
QAction actionAnalyzeFunction;
QAction actionEditFunction;
QAction actionRename;
QAction actionRenameUsedHere;
QAction actionSetFunctionVarTypes;
QAction actionXRefs;
QAction actionXRefsForVariables;
@ -209,6 +203,29 @@ private:
void addBreakpointMenu();
void addDebugMenu();
enum DoRenameAction {
RENAME_FUNCTION,
RENAME_FLAG,
RENAME_ADD_FLAG,
RENAME_LOCAL,
RENAME_DO_NOTHING,
};
struct DoRenameInfo {
ut64 addr;
QString name;
};
DoRenameAction doRenameAction = RENAME_DO_NOTHING;
DoRenameInfo doRenameInfo = { };
/*
* @brief Setups up the "Rename" option in the context menu
*
* This function takes into account cursor location so it can choose between current address and pointed value
* i.e. `0x000040f3 lea rdi, [0x000199b1]` -> does the user want to add a flag at 0x40f3 or at 0x199b1?
* and for that we will rely on |curHighlightedWord| which is the currently selected word.
*/
void setupRenaming();
/**
* @brief Checks if the currently highlighted word in the disassembly widget
* is a local variable or function paramter.
@ -229,6 +246,20 @@ private:
};
QVector<ThingUsedHere> getThingUsedHere(RVA offset);
void updateTargetMenuActions(const QVector<ThingUsedHere> &targets);
/*
* @brief This function checks if the given address contains a function,
* a flag or if it is just an address.
*/
ThingUsedHere getThingAt(ut64 address);
/*
* @brief This function will set the text for the renaming menu given a ThingUsedHere
* and provide information on how to handle the renaming of this specific thing.
* Indeed, selected dialogs are different when it comes to adding a flag, renaming an existing function,
* renaming a local variable...
*
* This function handles every possible object.
*/
void buildRenameMenu(ThingUsedHere* tuh);
};
#endif // DISASSEMBLYCONTEXTMENU_H