Create SyntaxHighlighter based on RAnnotatedCode. (#2409)

This commit is contained in:
karliss 2020-08-31 09:00:55 +03:00 committed by GitHub
parent 6fc902894d
commit 503907909f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 189 additions and 17 deletions

View File

@ -440,7 +440,8 @@ SOURCES += \
widgets/R2GraphWidget.cpp \ widgets/R2GraphWidget.cpp \
widgets/CallGraph.cpp \ widgets/CallGraph.cpp \
widgets/AddressableDockWidget.cpp \ widgets/AddressableDockWidget.cpp \
dialogs/preferences/AnalOptionsWidget.cpp dialogs/preferences/AnalOptionsWidget.cpp \
common/DecompilerHighlighter.cpp
GRAPHVIZ_SOURCES = \ GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp widgets/GraphvizLayout.cpp
@ -599,7 +600,8 @@ HEADERS += \
widgets/R2GraphWidget.h \ widgets/R2GraphWidget.h \
widgets/CallGraph.h \ widgets/CallGraph.h \
widgets/AddressableDockWidget.h \ widgets/AddressableDockWidget.h \
dialogs/preferences/AnalOptionsWidget.h dialogs/preferences/AnalOptionsWidget.h \
common/DecompilerHighlighter.h
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h

View File

@ -724,6 +724,17 @@ void Configuration::setDecompilerAutoRefreshEnabled(bool enabled)
s.setValue("decompilerAutoRefresh", enabled); s.setValue("decompilerAutoRefresh", enabled);
} }
void Configuration::enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter)
{
s.setValue("decompilerAnnotationHighlighter", useDecompilerHighlighter);
emit colorsUpdated();
}
bool Configuration::isDecompilerAnnotationHighlighterEnabled()
{
return s.value("decompilerAnnotationHighlighter", true).value<bool>();
}
bool Configuration::getBitmapTransparentState() bool Configuration::getBitmapTransparentState()
{ {
return s.value("bitmapGraphExportTransparency", false).value<bool>(); return s.value("bitmapGraphExportTransparency", false).value<bool>();

View File

@ -163,6 +163,9 @@ public:
bool getDecompilerAutoRefreshEnabled(); bool getDecompilerAutoRefreshEnabled();
void setDecompilerAutoRefreshEnabled(bool enabled); void setDecompilerAutoRefreshEnabled(bool enabled);
void enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter);
bool isDecompilerAnnotationHighlighterEnabled();
// Graph // Graph
int getGraphBlockMaxChars() const int getGraphBlockMaxChars() const
{ {

View File

@ -0,0 +1,71 @@
#include "DecompilerHighlighter.h"
#include "common/Configuration.h"
DecompilerHighlighter::DecompilerHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
setupTheme();
connect(Config(), &Configuration::colorsUpdated, this, [this]() {
setupTheme();
rehighlight();
});
}
void DecompilerHighlighter::setAnnotations(RAnnotatedCode *code)
{
this->code = code;
}
void DecompilerHighlighter::setupTheme()
{
struct {
RSyntaxHighlightType type;
QString name;
} mapping[] = {
{R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD, "pop"},
{R_SYNTAX_HIGHLIGHT_TYPE_COMMENT, "comment"},
{R_SYNTAX_HIGHLIGHT_TYPE_DATATYPE, "func_var_type"},
{R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_NAME, "fname"},
{R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_PARAMETER, "args"},
{R_SYNTAX_HIGHLIGHT_TYPE_LOCAL_VARIABLE, "func_var"},
{R_SYNTAX_HIGHLIGHT_TYPE_CONSTANT_VARIABLE, "num"},
{R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE, "flag"},
};
for (const auto &pair : mapping) {
assert(pair.type < format.size());
format[pair.type].setForeground(Config()->getColor(pair.name));
}
}
void DecompilerHighlighter::highlightBlock(const QString &)
{
if (!code) {
return;
}
auto block = currentBlock();
size_t start = block.position();
size_t end = block.position() + block.length();
std::unique_ptr<RPVector, decltype(&r_pvector_free)> annotations(r_annotated_code_annotations_range(code, start, end), &r_pvector_free);
void **iter;
r_pvector_foreach(annotations.get(), iter) {
RCodeAnnotation *annotation = static_cast<RCodeAnnotation*>(*iter);
if (annotation->type != R_CODE_ANNOTATION_TYPE_SYNTAX_HIGHLIGHT) {
continue;
}
auto type = annotation->syntax_highlight.type;
if (size_t(type) >= HIGHLIGHT_COUNT) {
continue;
}
auto annotationStart = annotation->start;
if (annotationStart < start) {
annotationStart = 0;
} else {
annotationStart -= start;
}
auto annotationEnd = annotation->end - start;
setFormat(annotationStart, annotationEnd - annotationStart, format[type]);
}
}

View File

@ -0,0 +1,44 @@
#ifndef DECOMPILER_HIGHLIGHTER_H
#define DECOMPILER_HIGHLIGHTER_H
#include "CutterCommon.h"
#include <r_util/r_annotated_code.h>
#include <QSyntaxHighlighter>
#include <QTextDocument>
#include <QTextCharFormat>
#include <array>
/**
* \brief SyntaxHighlighter based on annotations from decompiled code.
* Can be only used in combination with DecompilerWidget.
*/
class CUTTER_EXPORT DecompilerHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
DecompilerHighlighter(QTextDocument *parent = nullptr);
virtual ~DecompilerHighlighter() = default;
/**
* @brief Set the code with annotations to be used for highlighting.
*
* It is callers responsibility to ensure that it is synchronized with currentTextDocument and
* has sufficiently long lifetime.
*
* @param code
*/
void setAnnotations(RAnnotatedCode *code);
protected:
void highlightBlock(const QString &text) override;
private:
void setupTheme();
static const int HIGHLIGHT_COUNT = R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE + 1;
std::array<QTextCharFormat, HIGHLIGHT_COUNT> format;
RAnnotatedCode *code = nullptr;
};
#endif

View File

@ -67,6 +67,10 @@ AppearanceOptionsWidget::AppearanceOptionsWidget(PreferencesDialog *dialog)
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, this,
&AppearanceOptionsWidget::onFontZoomBoxValueChanged); &AppearanceOptionsWidget::onFontZoomBoxValueChanged);
ui->useDecompilerHighlighter->setChecked(Config()->isDecompilerAnnotationHighlighterEnabled());
connect(ui->useDecompilerHighlighter, &QCheckBox::toggled,
this, [](bool checked){ Config()->enableDecompilerAnnotationHighlighter(checked); });
} }
AppearanceOptionsWidget::~AppearanceOptionsWidget() {} AppearanceOptionsWidget::~AppearanceOptionsWidget() {}

View File

@ -283,6 +283,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="useDecompilerHighlighter">
<property name="toolTip">
<string>Use information provided by decompiler when highlighting code.</string>
</property>
<property name="text">
<string>Decompiler based highlighting</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -9,6 +9,7 @@
#include "common/Decompiler.h" #include "common/Decompiler.h"
#include "common/CutterSeekable.h" #include "common/CutterSeekable.h"
#include "core/MainWindow.h" #include "core/MainWindow.h"
#include "common/DecompilerHighlighter.h"
#include <QTextEdit> #include <QTextEdit>
#include <QPlainTextEdit> #include <QPlainTextEdit>
@ -38,7 +39,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) :
: getWidgetType()); : getWidgetType());
updateWindowTitle(); updateWindowTitle();
syntaxHighlighter = Config()->createSyntaxHighlighter(ui->textEdit->document()); setHighlighter(Config()->isDecompilerAnnotationHighlighterEnabled());
// Event filter to intercept double click and right click in the textbox // Event filter to intercept double click and right click in the textbox
ui->textEdit->viewport()->installEventFilter(this); ui->textEdit->viewport()->installEventFilter(this);
@ -255,10 +256,9 @@ void DecompilerWidget::doRefresh()
// the decompiler selection combo box as we are not waiting for any decompilation to finish. // the decompiler selection combo box as we are not waiting for any decompilation to finish.
ui->progressLabel->setVisible(false); ui->progressLabel->setVisible(false);
ui->decompilerComboBox->setEnabled(true); ui->decompilerComboBox->setEnabled(true);
connectCursorPositionChanged(false); setCode(Decompiler::makeWarning(
ui->textEdit->setPlainText( tr("No function found at this offset. "
tr("No function found at this offset. Seek to a function or define one in order to decompile it.")); "Seek to a function or define one in order to decompile it.")));
connectCursorPositionChanged(true);
return; return;
} }
mCtxMenu->setDecompiledFunctionAddress(decompiledFunctionAddr); mCtxMenu->setDecompiledFunctionAddress(decompiledFunctionAddr);
@ -297,24 +297,18 @@ void DecompilerWidget::decompilationFinished(RAnnotatedCode *codeDecompiled)
ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled); ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled);
mCtxMenu->setAnnotationHere(nullptr); mCtxMenu->setAnnotationHere(nullptr);
this->code.reset(codeDecompiled); setCode(codeDecompiled);
Decompiler *dec = getCurrentDecompiler(); Decompiler *dec = getCurrentDecompiler();
QObject::disconnect(dec, &Decompiler::finished, this, &DecompilerWidget::decompilationFinished); QObject::disconnect(dec, &Decompiler::finished, this, &DecompilerWidget::decompilationFinished);
decompilerBusy = false; decompilerBusy = false;
QString codeString = QString::fromUtf8(this->code->code); if (ui->textEdit->toPlainText().isEmpty()) {
if (codeString.isEmpty()) { setCode(Decompiler::makeWarning(tr("Cannot decompile at this address (Not a function?)")));
connectCursorPositionChanged(false);
ui->textEdit->setPlainText(tr("Cannot decompile at this address (Not a function?)"));
connectCursorPositionChanged(true);
lowestOffsetInCode = RVA_MAX; lowestOffsetInCode = RVA_MAX;
highestOffsetInCode = 0; highestOffsetInCode = 0;
return; return;
} else { } else {
connectCursorPositionChanged(false);
ui->textEdit->setPlainText(codeString);
connectCursorPositionChanged(true);
updateCursorPosition(); updateCursorPosition();
highlightPC(); highlightPC();
highlightBreakpoints(); highlightBreakpoints();
@ -467,6 +461,10 @@ void DecompilerWidget::fontsUpdatedSlot()
void DecompilerWidget::colorsUpdatedSlot() void DecompilerWidget::colorsUpdatedSlot()
{ {
bool useAnotationHiglighter = Config()->isDecompilerAnnotationHighlighterEnabled();
if (useAnotationHiglighter != usingAnnotationBasedHighlighting) {
setHighlighter(useAnotationHiglighter);
}
} }
void DecompilerWidget::showDecompilerContextMenu(const QPoint &pt) void DecompilerWidget::showDecompilerContextMenu(const QPoint &pt)
@ -566,3 +564,27 @@ bool DecompilerWidget::addressInRange(RVA addr)
} }
return false; return false;
} }
void DecompilerWidget::setCode(RAnnotatedCode *code)
{
connectCursorPositionChanged(false);
if (auto highlighter = qobject_cast<DecompilerHighlighter*>(syntaxHighlighter.get())) {
highlighter->setAnnotations(code);
}
this->code.reset(code);
this->ui->textEdit->setPlainText(QString::fromUtf8(this->code->code));
connectCursorPositionChanged(true);
syntaxHighlighter->rehighlight();
}
void DecompilerWidget::setHighlighter(bool annotationBasedHighlighter)
{
usingAnnotationBasedHighlighting = annotationBasedHighlighter;
if (usingAnnotationBasedHighlighting) {
syntaxHighlighter.reset(new DecompilerHighlighter());
static_cast<DecompilerHighlighter*>(syntaxHighlighter.get())->setAnnotations(code.get());
} else {
syntaxHighlighter.reset(Config()->createSyntaxHighlighter(nullptr));
}
syntaxHighlighter->setDocument(ui->textEdit->document());
}

View File

@ -61,7 +61,8 @@ private:
RefreshDeferrer *refreshDeferrer; RefreshDeferrer *refreshDeferrer;
QSyntaxHighlighter *syntaxHighlighter; bool usingAnnotationBasedHighlighting = false;
std::unique_ptr<QSyntaxHighlighter> syntaxHighlighter;
bool decompilerSelectionEnabled; bool decompilerSelectionEnabled;
/** /**
@ -233,6 +234,10 @@ private:
* @return True if the specified is a part of the decompiled function, False otherwise. * @return True if the specified is a part of the decompiled function, False otherwise.
*/ */
bool addressInRange(RVA addr); bool addressInRange(RVA addr);
void setCode(RAnnotatedCode *code);
void setHighlighter(bool annotationBasedHighlighter);
}; };
#endif // DECOMPILERWIDGET_H #endif // DECOMPILERWIDGET_H