From 503907909f0f7f740f0c3fc2770e61dcbf14c78e Mon Sep 17 00:00:00 2001 From: karliss Date: Mon, 31 Aug 2020 09:00:55 +0300 Subject: [PATCH] Create SyntaxHighlighter based on RAnnotatedCode. (#2409) --- src/Cutter.pro | 6 +- src/common/Configuration.cpp | 11 +++ src/common/Configuration.h | 3 + src/common/DecompilerHighlighter.cpp | 71 +++++++++++++++++++ src/common/DecompilerHighlighter.h | 44 ++++++++++++ .../preferences/AppearanceOptionsWidget.cpp | 4 ++ .../preferences/AppearanceOptionsWidget.ui | 10 +++ src/widgets/DecompilerWidget.cpp | 50 +++++++++---- src/widgets/DecompilerWidget.h | 7 +- 9 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 src/common/DecompilerHighlighter.cpp create mode 100644 src/common/DecompilerHighlighter.h diff --git a/src/Cutter.pro b/src/Cutter.pro index 2ba47edf..36ea95a8 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -440,7 +440,8 @@ SOURCES += \ widgets/R2GraphWidget.cpp \ widgets/CallGraph.cpp \ widgets/AddressableDockWidget.cpp \ - dialogs/preferences/AnalOptionsWidget.cpp + dialogs/preferences/AnalOptionsWidget.cpp \ + common/DecompilerHighlighter.cpp GRAPHVIZ_SOURCES = \ widgets/GraphvizLayout.cpp @@ -599,7 +600,8 @@ HEADERS += \ widgets/R2GraphWidget.h \ widgets/CallGraph.h \ widgets/AddressableDockWidget.h \ - dialogs/preferences/AnalOptionsWidget.h + dialogs/preferences/AnalOptionsWidget.h \ + common/DecompilerHighlighter.h GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h diff --git a/src/common/Configuration.cpp b/src/common/Configuration.cpp index da0942d3..5badd797 100644 --- a/src/common/Configuration.cpp +++ b/src/common/Configuration.cpp @@ -724,6 +724,17 @@ void Configuration::setDecompilerAutoRefreshEnabled(bool 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 Configuration::getBitmapTransparentState() { return s.value("bitmapGraphExportTransparency", false).value(); diff --git a/src/common/Configuration.h b/src/common/Configuration.h index b6f04e06..0f8dae4e 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -163,6 +163,9 @@ public: bool getDecompilerAutoRefreshEnabled(); void setDecompilerAutoRefreshEnabled(bool enabled); + void enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter); + bool isDecompilerAnnotationHighlighterEnabled(); + // Graph int getGraphBlockMaxChars() const { diff --git a/src/common/DecompilerHighlighter.cpp b/src/common/DecompilerHighlighter.cpp new file mode 100644 index 00000000..1ae2f51d --- /dev/null +++ b/src/common/DecompilerHighlighter.cpp @@ -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 annotations(r_annotated_code_annotations_range(code, start, end), &r_pvector_free); + void **iter; + r_pvector_foreach(annotations.get(), iter) { + RCodeAnnotation *annotation = static_cast(*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]); + } +} diff --git a/src/common/DecompilerHighlighter.h b/src/common/DecompilerHighlighter.h new file mode 100644 index 00000000..eaa93a70 --- /dev/null +++ b/src/common/DecompilerHighlighter.h @@ -0,0 +1,44 @@ +#ifndef DECOMPILER_HIGHLIGHTER_H +#define DECOMPILER_HIGHLIGHTER_H + +#include "CutterCommon.h" +#include +#include +#include +#include +#include + +/** + * \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 format; + RAnnotatedCode *code = nullptr; +}; + +#endif diff --git a/src/dialogs/preferences/AppearanceOptionsWidget.cpp b/src/dialogs/preferences/AppearanceOptionsWidget.cpp index 6bcaaf28..1843573a 100644 --- a/src/dialogs/preferences/AppearanceOptionsWidget.cpp +++ b/src/dialogs/preferences/AppearanceOptionsWidget.cpp @@ -67,6 +67,10 @@ AppearanceOptionsWidget::AppearanceOptionsWidget(PreferencesDialog *dialog) static_cast(&QSpinBox::valueChanged), this, &AppearanceOptionsWidget::onFontZoomBoxValueChanged); + + ui->useDecompilerHighlighter->setChecked(Config()->isDecompilerAnnotationHighlighterEnabled()); + connect(ui->useDecompilerHighlighter, &QCheckBox::toggled, + this, [](bool checked){ Config()->enableDecompilerAnnotationHighlighter(checked); }); } AppearanceOptionsWidget::~AppearanceOptionsWidget() {} diff --git a/src/dialogs/preferences/AppearanceOptionsWidget.ui b/src/dialogs/preferences/AppearanceOptionsWidget.ui index ebc86ed0..0527949c 100644 --- a/src/dialogs/preferences/AppearanceOptionsWidget.ui +++ b/src/dialogs/preferences/AppearanceOptionsWidget.ui @@ -283,6 +283,16 @@ + + + + Use information provided by decompiler when highlighting code. + + + Decompiler based highlighting + + + diff --git a/src/widgets/DecompilerWidget.cpp b/src/widgets/DecompilerWidget.cpp index a40d4f5e..239a4477 100644 --- a/src/widgets/DecompilerWidget.cpp +++ b/src/widgets/DecompilerWidget.cpp @@ -9,6 +9,7 @@ #include "common/Decompiler.h" #include "common/CutterSeekable.h" #include "core/MainWindow.h" +#include "common/DecompilerHighlighter.h" #include #include @@ -38,7 +39,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : : getWidgetType()); updateWindowTitle(); - syntaxHighlighter = Config()->createSyntaxHighlighter(ui->textEdit->document()); + setHighlighter(Config()->isDecompilerAnnotationHighlighterEnabled()); // Event filter to intercept double click and right click in the textbox 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. ui->progressLabel->setVisible(false); ui->decompilerComboBox->setEnabled(true); - connectCursorPositionChanged(false); - ui->textEdit->setPlainText( - tr("No function found at this offset. Seek to a function or define one in order to decompile it.")); - connectCursorPositionChanged(true); + setCode(Decompiler::makeWarning( + tr("No function found at this offset. " + "Seek to a function or define one in order to decompile it."))); return; } mCtxMenu->setDecompiledFunctionAddress(decompiledFunctionAddr); @@ -297,24 +297,18 @@ void DecompilerWidget::decompilationFinished(RAnnotatedCode *codeDecompiled) ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled); mCtxMenu->setAnnotationHere(nullptr); - this->code.reset(codeDecompiled); + setCode(codeDecompiled); Decompiler *dec = getCurrentDecompiler(); QObject::disconnect(dec, &Decompiler::finished, this, &DecompilerWidget::decompilationFinished); decompilerBusy = false; - QString codeString = QString::fromUtf8(this->code->code); - if (codeString.isEmpty()) { - connectCursorPositionChanged(false); - ui->textEdit->setPlainText(tr("Cannot decompile at this address (Not a function?)")); - connectCursorPositionChanged(true); + if (ui->textEdit->toPlainText().isEmpty()) { + setCode(Decompiler::makeWarning(tr("Cannot decompile at this address (Not a function?)"))); lowestOffsetInCode = RVA_MAX; highestOffsetInCode = 0; return; } else { - connectCursorPositionChanged(false); - ui->textEdit->setPlainText(codeString); - connectCursorPositionChanged(true); updateCursorPosition(); highlightPC(); highlightBreakpoints(); @@ -467,6 +461,10 @@ void DecompilerWidget::fontsUpdatedSlot() void DecompilerWidget::colorsUpdatedSlot() { + bool useAnotationHiglighter = Config()->isDecompilerAnnotationHighlighterEnabled(); + if (useAnotationHiglighter != usingAnnotationBasedHighlighting) { + setHighlighter(useAnotationHiglighter); + } } void DecompilerWidget::showDecompilerContextMenu(const QPoint &pt) @@ -566,3 +564,27 @@ bool DecompilerWidget::addressInRange(RVA addr) } return false; } + +void DecompilerWidget::setCode(RAnnotatedCode *code) +{ + connectCursorPositionChanged(false); + if (auto highlighter = qobject_cast(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(syntaxHighlighter.get())->setAnnotations(code.get()); + } else { + syntaxHighlighter.reset(Config()->createSyntaxHighlighter(nullptr)); + } + syntaxHighlighter->setDocument(ui->textEdit->document()); +} diff --git a/src/widgets/DecompilerWidget.h b/src/widgets/DecompilerWidget.h index 6181dfd1..86d0b5dd 100644 --- a/src/widgets/DecompilerWidget.h +++ b/src/widgets/DecompilerWidget.h @@ -61,7 +61,8 @@ private: RefreshDeferrer *refreshDeferrer; - QSyntaxHighlighter *syntaxHighlighter; + bool usingAnnotationBasedHighlighting = false; + std::unique_ptr syntaxHighlighter; bool decompilerSelectionEnabled; /** @@ -233,6 +234,10 @@ private: * @return True if the specified is a part of the decompiled function, False otherwise. */ bool addressInRange(RVA addr); + + void setCode(RAnnotatedCode *code); + + void setHighlighter(bool annotationBasedHighlighter); }; #endif // DECOMPILERWIDGET_H