2017-12-06 23:19:14 +00:00
|
|
|
#include "PseudocodeWidget.h"
|
2017-12-21 14:23:44 +00:00
|
|
|
#include "ui_PseudocodeWidget.h"
|
2017-12-15 10:52:47 +00:00
|
|
|
|
2018-10-17 07:55:53 +00:00
|
|
|
#include "common/Configuration.h"
|
|
|
|
#include "common/Helpers.h"
|
|
|
|
#include "common/TempConfig.h"
|
2019-07-12 08:57:07 +00:00
|
|
|
#include "common/SelectionHighlight.h"
|
2019-07-15 12:08:44 +00:00
|
|
|
#include "common/Decompiler.h"
|
2017-12-06 23:19:14 +00:00
|
|
|
|
2019-07-11 13:21:54 +00:00
|
|
|
#include <QTextEdit>
|
2019-07-12 08:57:07 +00:00
|
|
|
#include <QPlainTextEdit>
|
|
|
|
#include <QTextBlock>
|
|
|
|
#include <QObject>
|
|
|
|
#include <QTextBlockUserData>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a single line of decompiled code as part of the displayed text,
|
|
|
|
* including the position inside the QTextDocument
|
|
|
|
*/
|
|
|
|
struct DecompiledCodeTextLine
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* position inside the QTextDocument
|
|
|
|
*/
|
|
|
|
int position;
|
|
|
|
|
|
|
|
DecompiledCode::Line line;
|
|
|
|
|
|
|
|
DecompiledCodeTextLine(int position, const DecompiledCode::Line &line)
|
|
|
|
{
|
|
|
|
this->position = position;
|
|
|
|
this->line = line;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-07-11 13:21:54 +00:00
|
|
|
|
2018-03-16 21:46:57 +00:00
|
|
|
PseudocodeWidget::PseudocodeWidget(MainWindow *main, QAction *action) :
|
2019-03-27 08:24:54 +00:00
|
|
|
MemoryDockWidget(CutterCore::MemoryWidgetType::Pseudocode, main, action),
|
2018-03-21 20:32:32 +00:00
|
|
|
ui(new Ui::PseudocodeWidget)
|
2017-12-06 23:19:14 +00:00
|
|
|
{
|
2017-12-21 14:23:44 +00:00
|
|
|
ui->setupUi(this);
|
|
|
|
|
2019-07-11 13:21:54 +00:00
|
|
|
syntaxHighlighter = Config()->createSyntaxHighlighter(ui->textEdit->document());
|
2017-12-16 13:22:56 +00:00
|
|
|
|
2017-12-06 23:19:14 +00:00
|
|
|
setupFonts();
|
|
|
|
colorsUpdatedSlot();
|
|
|
|
|
|
|
|
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated()));
|
|
|
|
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
|
|
|
|
|
|
|
|
connect(this, &QDockWidget::visibilityChanged, this, [](bool visibility) {
|
2018-03-21 20:32:32 +00:00
|
|
|
if (visibility) {
|
2017-12-06 23:19:14 +00:00
|
|
|
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Pseudocode);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-01-13 15:41:08 +00:00
|
|
|
// TODO Use RefreshDeferrer and remove the refresh button
|
2017-12-21 14:23:44 +00:00
|
|
|
connect(ui->refreshButton, &QAbstractButton::clicked, this, [this]() {
|
2019-01-12 17:02:51 +00:00
|
|
|
doRefresh(Core()->getOffset());
|
2017-12-06 23:19:14 +00:00
|
|
|
});
|
|
|
|
|
2019-07-15 12:08:44 +00:00
|
|
|
auto decompilers = Core()->getDecompilers();
|
|
|
|
for (auto dec : decompilers) {
|
|
|
|
ui->decompilerComboBox->addItem(dec->getName(), dec->getId());
|
|
|
|
}
|
|
|
|
|
|
|
|
if(decompilers.size() <= 1) {
|
2018-09-08 07:12:08 +00:00
|
|
|
ui->decompilerComboBox->setEnabled(false);
|
2019-07-15 12:08:44 +00:00
|
|
|
if (decompilers.isEmpty()) {
|
|
|
|
ui->textEdit->setPlainText(tr("No Decompiler available."));
|
|
|
|
}
|
2018-09-08 07:12:08 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 08:57:07 +00:00
|
|
|
connectCursorPositionChanged(false);
|
|
|
|
connect(Core(), &CutterCore::seekChanged, this, &PseudocodeWidget::seekChanged);
|
|
|
|
|
2019-01-12 17:02:51 +00:00
|
|
|
doRefresh(RVA_INVALID);
|
2017-12-06 23:19:14 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 15:41:08 +00:00
|
|
|
PseudocodeWidget::~PseudocodeWidget() = default;
|
2017-12-06 23:19:14 +00:00
|
|
|
|
|
|
|
|
2019-01-12 17:02:51 +00:00
|
|
|
void PseudocodeWidget::doRefresh(RVA addr)
|
2017-12-06 23:19:14 +00:00
|
|
|
{
|
2019-07-15 12:08:44 +00:00
|
|
|
if (ui->decompilerComboBox->currentIndex() < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decompiler *dec = Core()->getDecompilerById(ui->decompilerComboBox->currentData().toString());
|
|
|
|
if (!dec) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (addr == RVA_INVALID) {
|
2019-07-12 08:57:07 +00:00
|
|
|
ui->textEdit->setPlainText(tr("Click Refresh to generate Pseudocode from current offset."));
|
2017-12-21 14:23:44 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-15 12:08:44 +00:00
|
|
|
DecompiledCode decompiledCode = dec->decompileAt(addr);
|
2018-09-08 07:12:08 +00:00
|
|
|
|
2019-07-12 08:57:07 +00:00
|
|
|
textLines = {};
|
|
|
|
textLines.reserve(decompiledCode.lines.size());
|
|
|
|
|
|
|
|
if (decompiledCode.lines.isEmpty()) {
|
|
|
|
ui->textEdit->setPlainText(tr("Cannot decompile at") + " " + RAddressString(
|
2018-03-21 20:32:32 +00:00
|
|
|
addr) + " " + tr("(Not a function?)"));
|
2017-12-15 10:52:47 +00:00
|
|
|
return;
|
2019-07-12 08:57:07 +00:00
|
|
|
} else {
|
|
|
|
connectCursorPositionChanged(true);
|
|
|
|
ui->textEdit->document()->clear();
|
|
|
|
QTextCursor cursor(ui->textEdit->document());
|
|
|
|
for (const DecompiledCode::Line &line : decompiledCode.lines) {
|
|
|
|
textLines.append(DecompiledCodeTextLine(cursor.position(), line));
|
|
|
|
// Can't use cursor.block()->setUserData() here, because the Syntax Highlighter will mess it up.
|
|
|
|
cursor.insertText(line.str + "\n");
|
|
|
|
}
|
|
|
|
connectCursorPositionChanged(false);
|
|
|
|
seekChanged();
|
2017-12-06 23:19:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PseudocodeWidget::refreshPseudocode()
|
|
|
|
{
|
2019-01-12 17:02:51 +00:00
|
|
|
doRefresh(Core()->getOffset());
|
2017-12-06 23:19:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 08:57:07 +00:00
|
|
|
void PseudocodeWidget::connectCursorPositionChanged(bool disconnect)
|
|
|
|
{
|
|
|
|
if (disconnect) {
|
|
|
|
QObject::disconnect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, &PseudocodeWidget::cursorPositionChanged);
|
|
|
|
} else {
|
|
|
|
connect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, &PseudocodeWidget::cursorPositionChanged);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PseudocodeWidget::cursorPositionChanged()
|
|
|
|
{
|
|
|
|
RVA offset = getOffsetAtLine(ui->textEdit->textCursor());
|
|
|
|
if (offset != RVA_INVALID && offset != Core()->getOffset()) {
|
|
|
|
seekFromCursor = true;
|
|
|
|
Core()->seek(offset);
|
|
|
|
seekFromCursor = false;
|
|
|
|
}
|
|
|
|
updateSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PseudocodeWidget::seekChanged()
|
|
|
|
{
|
|
|
|
if (seekFromCursor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateCursorPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PseudocodeWidget::updateCursorPosition()
|
|
|
|
{
|
|
|
|
RVA offset = Core()->getOffset();
|
|
|
|
connectCursorPositionChanged(true);
|
|
|
|
|
|
|
|
auto it = findLineByOffset(offset);
|
|
|
|
if (it != textLines.end()) {
|
|
|
|
// move back if the offset is identical (so we don't land on closing braces for example)
|
|
|
|
while (it != textLines.begin()) {
|
|
|
|
auto prev = it - 1;
|
|
|
|
if (prev->line.addr != it->line.addr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
it = prev;
|
|
|
|
}
|
|
|
|
QTextCursor cursor = ui->textEdit->textCursor();
|
|
|
|
cursor.setPosition((*it).position);
|
|
|
|
ui->textEdit->setTextCursor(cursor);
|
|
|
|
updateSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
connectCursorPositionChanged(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<DecompiledCodeTextLine>::iterator PseudocodeWidget::findLine(int position)
|
|
|
|
{
|
|
|
|
return std::upper_bound(textLines.begin(), textLines.end(), position,
|
|
|
|
[](int pos, const DecompiledCodeTextLine &line) {
|
|
|
|
return pos < line.position;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<DecompiledCodeTextLine>::iterator PseudocodeWidget::findLineByOffset(RVA offset)
|
|
|
|
{
|
|
|
|
auto it = textLines.begin();
|
|
|
|
auto candidate = it;
|
|
|
|
for (; it != textLines.end(); it++) {
|
|
|
|
RVA lineOffset = it->line.addr;
|
|
|
|
if (lineOffset != RVA_INVALID && lineOffset > offset) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (candidate->line.addr == RVA_INVALID || (lineOffset != RVA_INVALID && lineOffset > candidate->line.addr)) {
|
|
|
|
candidate = it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return candidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
RVA PseudocodeWidget::getOffsetAtLine(const QTextCursor &tc)
|
|
|
|
{
|
|
|
|
auto it = findLine(tc.position());
|
|
|
|
if (it == textLines.begin()) {
|
|
|
|
return RVA_INVALID;
|
|
|
|
}
|
|
|
|
it--;
|
|
|
|
return (*it).line.addr;
|
|
|
|
}
|
|
|
|
|
2017-12-06 23:19:14 +00:00
|
|
|
void PseudocodeWidget::setupFonts()
|
|
|
|
{
|
|
|
|
QFont font = Config()->getFont();
|
2017-12-21 14:23:44 +00:00
|
|
|
ui->textEdit->setFont(font);
|
2017-12-06 23:19:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 08:57:07 +00:00
|
|
|
void PseudocodeWidget::updateSelection()
|
|
|
|
{
|
|
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
|
|
|
|
|
|
|
// Highlight the current line
|
|
|
|
auto cursor = ui->textEdit->textCursor();
|
|
|
|
|
|
|
|
RVA cursorOffset = getOffsetAtLine(cursor);
|
|
|
|
if (cursorOffset != RVA_INVALID) {
|
|
|
|
for (auto it = findLineByOffset(cursorOffset);
|
|
|
|
it != textLines.end() && it->line.addr != RVA_INVALID && it->line.addr <= cursorOffset;
|
|
|
|
it++) {
|
|
|
|
auto lineCursor = cursor;
|
|
|
|
lineCursor.setPosition(it->position);
|
|
|
|
extraSelections.append(createLineHighlightSelection(lineCursor));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// if the cursor position has no valid offset, just highlight the line
|
|
|
|
extraSelections.append(createLineHighlightSelection(cursor));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Highlight all the words in the document same as the current one
|
|
|
|
cursor.select(QTextCursor::WordUnderCursor);
|
|
|
|
QString searchString = cursor.selectedText();
|
|
|
|
extraSelections.append(createSameWordsSelections(ui->textEdit, searchString));
|
|
|
|
|
|
|
|
ui->textEdit->setExtraSelections(extraSelections);
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:02:41 +00:00
|
|
|
QString PseudocodeWidget::getWindowTitle() const
|
|
|
|
{
|
|
|
|
return tr("Pseudocode");
|
|
|
|
}
|
|
|
|
|
2017-12-06 23:19:14 +00:00
|
|
|
void PseudocodeWidget::fontsUpdated()
|
|
|
|
{
|
|
|
|
setupFonts();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PseudocodeWidget::colorsUpdatedSlot()
|
|
|
|
{
|
|
|
|
}
|