diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fe31656..5d3c84ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -397,7 +397,7 @@ jobs: python-version: 3.12.x output-id: artifact_windows - name: macos-x86_64 - os: macos-15-intel # Must be an x86_64 image: https://github.com/actions/runner-images#available-images + os: macos-14 arch: x86_64 package: true - name: macos-arm64 diff --git a/CMakeLists.txt b/CMakeLists.txt index e785237f..d6289c88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,6 @@ set(CUTTER_EXTRA_PLUGIN_DIRS "" CACHE STRING "List of addition plugin locations" option(CUTTER_ENABLE_DEPENDENCY_DOWNLOADS "Enable downloading of dependencies. Setting to OFF doesn't affect any downloads done by rizin build." OFF) option(CUTTER_ENABLE_PACKAGING "Enable building platform-specific packages for distributing" OFF) option(CUTTER_ENABLE_SIGDB "Downloads and installs sigdb (only available when CUTTER_USE_BUNDLED_RIZIN=ON)." OFF) -option(CUTTER_ENABLE_TRANSLATIONS "Build and install translation files" ON) option(CUTTER_PACKAGE_DEPENDENCIES "During install step include the third party dependencies." OFF) option(CUTTER_PACKAGE_RZ_GHIDRA "Compile and install rz-ghidra during install step." OFF) option(CUTTER_PACKAGE_RZ_LIBSWIFT "Compile and install rz-libswift demangler during the install step." OFF) diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index dab0a44b..b89399b4 100644 --- a/cmake/BundledRizin.cmake +++ b/cmake/BundledRizin.cmake @@ -53,7 +53,7 @@ endif() # TODO: This version number should be fetched automatically # instead of being hardcoded. -set (Rizin_VERSION 0.10) +set (Rizin_VERSION 0.9) set (RZ_LIBS rz_core rz_config rz_cons rz_io rz_util rz_flag rz_mark rz_arch rz_debug rz_hash rz_bin rz_lang rz_il rz_egg rz_reg rz_search rz_syscall diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt index d3102b6f..093d6a3a 100644 --- a/dist/CMakeLists.txt +++ b/dist/CMakeLists.txt @@ -201,9 +201,9 @@ if(CUTTER_PACKAGE_RZ_GHIDRA) # installed Cutter. ExternalProject_Add(rz-ghidra GIT_REPOSITORY https://github.com/rizinorg/rz-ghidra - #GIT_TAG v0.3.0 + GIT_TAG v0.9.0 #GIT_TAG c7a50a2e7c0a95cd52b167c9ee0fa1805223f08e - GIT_TAG dev + #GIT_TAG dev #GIT_SHALLOW ON # disable this line when using commit hash CONFIGURE_COMMAND "" BUILD_COMMAND "" diff --git a/dist/bundle_jsdec.ps1 b/dist/bundle_jsdec.ps1 index 4eee6d98..e1b58bea 100644 --- a/dist/bundle_jsdec.ps1 +++ b/dist/bundle_jsdec.ps1 @@ -4,7 +4,7 @@ $plugin_path = "$dist\plugins\native\" $pathdll = "$plugin_path\jsdec_cutter.dll" if (-not (Test-Path -Path 'jsdec' -PathType Container)) { - git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "dev" + git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "v0.9.0" } cd jsdec $jsdecdir = (Get-Item .).FullName diff --git a/dist/bundle_rz_libswift.ps1 b/dist/bundle_rz_libswift.ps1 index 9c0b3c32..8b7b7406 100644 --- a/dist/bundle_rz_libswift.ps1 +++ b/dist/bundle_rz_libswift.ps1 @@ -2,7 +2,7 @@ $dist = $args[0] $python = Split-Path((Get-Command python.exe).Path) if (-not (Test-Path -Path 'libswift' -PathType Container)) { - git clone https://github.com/rizinorg/rz-libswift.git --depth 1 libswift + git clone https://github.com/rizinorg/rz-libswift.git --depth 1 --branch "v0.9.0" libswift } cd libswift & meson.exe --buildtype=release --prefix=$dist build diff --git a/dist/bundle_rz_libyara.ps1 b/dist/bundle_rz_libyara.ps1 index fe72a91b..27711049 100644 --- a/dist/bundle_rz_libyara.ps1 +++ b/dist/bundle_rz_libyara.ps1 @@ -3,7 +3,7 @@ $cmake_opts = $args[1] $python = Split-Path((Get-Command python.exe).Path) if (-not (Test-Path -Path 'rz_libyara' -PathType Container)) { - git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch main rz_libyara + git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch "v0.9.0" rz_libyara git -C rz_libyara submodule init git -C rz_libyara submodule update } diff --git a/dist/bundle_rz_silhouette.ps1 b/dist/bundle_rz_silhouette.ps1 index 4cf04668..77bbc9ef 100644 --- a/dist/bundle_rz_silhouette.ps1 +++ b/dist/bundle_rz_silhouette.ps1 @@ -3,7 +3,7 @@ $cmake_opts = $args[1] $python = Split-Path((Get-Command python.exe).Path) if (-not (Test-Path -Path 'rz-silhouette' -PathType Container)) { - git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 rz-silhouette + git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 --branch "v0.9.0-beta4" rz-silhouette } cd rz-silhouette & meson.exe --buildtype=release --prefix=$dist build diff --git a/rizin b/rizin index 136a337e..da228d11 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 136a337e1c40236723a58ccca6549aa577e8aaa5 +Subproject commit da228d11cfc865b06442bd66b192caeb6e5ff556 diff --git a/scripts/fetch_deps.sh b/scripts/fetch_deps.sh index 0753023f..a6281d34 100755 --- a/scripts/fetch_deps.sh +++ b/scripts/fetch_deps.sh @@ -5,7 +5,7 @@ set -e cd $(dirname "${BASH_SOURCE[0]}")/.. mkdir -p cutter-deps && cd cutter-deps -DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/v19 +DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/v18 if [ "$CUTTER_QT" == "5" ]; then DEPS_FILE_linux_x86_64=cutter-deps-q5-linux-x86_64.tar.gz @@ -13,18 +13,18 @@ if [ "$CUTTER_QT" == "5" ]; then DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/qt5-v17 else DEPS_FILE_linux_x86_64=cutter-deps-linux-x86_64.tar.gz - DEPS_SHA256_linux_x86_64=b4bebf95c69f6216456d31b345d660ae3347517a6c14a4a4e9d568b03289c3de + DEPS_SHA256_linux_x86_64=204683c44e4824df7cb9925f1939544f081e64b7a701fe95fd03f67125a165b4 fi echo $DEPS_SHA256_linux_x86_64 DEPS_FILE_macos_x86_64=cutter-deps-macos-x86_64.tar.gz -DEPS_SHA256_macos_x86_64=6f202bd2b18cae780d4fe4ddce662909eebb4f0cd3f1c685e5b2bb6cd46a3e7d +DEPS_SHA256_macos_x86_64=a46c2dee12d4160410237da848075fc87c41c3850021f583d3b469a59c386650 DEPS_FILE_macos_arm64=cutter-deps-macos-arm64.tar.gz -DEPS_SHA256_macos_arm64=fa4b3c58f02e7b57a18c2fe7b8c196dd008187deda72fa1ae685c8ed661e10c6 +DEPS_SHA256_macos_arm64=1ca381b562d632a370e05a85b0fe60a591a261bba0f48d97a0e40b4307c2577a DEPS_FILE_win_x86_64=cutter-deps-win-x86_64.tar.gz -DEPS_SHA256_win_x86_64=3d69280bfedfdd3363fac450f814fc2f09cdd6149f60541dcdaa81f782c3ac76 +DEPS_SHA256_win_x86_64=95a3cf676178b8c58442d22ee8b21ce2530bfdae86f514901310490a8f28867a ARCH=x86_64 diff --git a/scripts/jsdec.sh b/scripts/jsdec.sh index ebcd2c4c..3ee28cb0 100755 --- a/scripts/jsdec.sh +++ b/scripts/jsdec.sh @@ -9,7 +9,7 @@ SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") cd "$SCRIPTPATH/.." if [ ! -d jsdec ]; then - git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "dev" + git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "v0.9.0" fi cd jsdec diff --git a/scripts/macos_sign.sh b/scripts/macos_sign.sh index c9f23a40..9d0b0c0c 100755 --- a/scripts/macos_sign.sh +++ b/scripts/macos_sign.sh @@ -108,17 +108,9 @@ resign_dmg() { notarize_bundle Cutter-rw/Cutter.app unmount trap - EXIT - # Remove temporary signing space and restore HFS+ volume consistency. - ee hdiutil resize -size min Cutter-rw.dmg OUTPUT="${1%.*}-signed.dmg" echo_step "Creating final read-only ${OUTPUT}" ee hdiutil convert -format UDZO -o "${OUTPUT}" Cutter-rw.dmg - echo_step "Verifying filesystem in ${OUTPUT}" - ee hdiutil attach "${OUTPUT}" -readonly -nobrowse -noautofsck -mountpoint Cutter-rw - trap unmount EXIT - ee diskutil verifyVolume Cutter-rw - unmount - trap - EXIT } case "$1" in diff --git a/scripts/rz-libswift.sh b/scripts/rz-libswift.sh index 0772cb4c..c620ad74 100755 --- a/scripts/rz-libswift.sh +++ b/scripts/rz-libswift.sh @@ -7,7 +7,7 @@ SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") cd "$SCRIPTPATH/.." if [[ ! -d libswift ]]; then - git clone https://github.com/rizinorg/rz-libswift.git --depth 1 libswift + git clone https://github.com/rizinorg/rz-libswift.git --depth 1 --branch "v0.9.0" libswift fi cd libswift diff --git a/scripts/rz-libyara.sh b/scripts/rz-libyara.sh index 06fdecd7..2c25ce25 100755 --- a/scripts/rz-libyara.sh +++ b/scripts/rz-libyara.sh @@ -8,7 +8,7 @@ EXTRA_CMAKE_OPTS="$2" cd "$SCRIPTPATH/.." if [[ ! -d rz_libyara ]]; then - git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch main rz_libyara + git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch "v0.9.0" rz_libyara git -C rz_libyara submodule init git -C rz_libyara submodule update fi diff --git a/scripts/rz-silhouette.sh b/scripts/rz-silhouette.sh index f2f595e5..67430338 100755 --- a/scripts/rz-silhouette.sh +++ b/scripts/rz-silhouette.sh @@ -8,7 +8,7 @@ EXTRA_CMAKE_OPTS="$2" cd "$SCRIPTPATH/.." if [[ ! -d rz-silhouette ]]; then - git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 rz-silhouette + git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 --branch "v0.9.0-beta4" rz-silhouette fi cd rz-silhouette diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fba081a..58b2b3b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -613,9 +613,7 @@ if(CUTTER_ENABLE_PACKAGING) target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_PACKAGING) endif() -if(CUTTER_ENABLE_TRANSLATIONS) - include(Translations) -endif() +include(Translations) # Install files install(TARGETS Cutter diff --git a/src/common/Helpers.cpp b/src/common/Helpers.cpp index 0f94847b..d50bd5b4 100644 --- a/src/common/Helpers.cpp +++ b/src/common/Helpers.cpp @@ -4,55 +4,19 @@ #include #include -#include #include #include #include #include -#include #include #include #include -#include #include #include #include #include -namespace { -template -int calculateMaxDisplayedLines(TextEditType *textEdit) -{ - qreal lineHeight = 0; - const QTextBlock firstBlock = textEdit->document()->begin(); - if (firstBlock.isValid()) { - lineHeight = textEdit->document()->documentLayout()->blockBoundingRect(firstBlock).height(); - } - - if (lineHeight <= 0) { - const QFontMetricsF fm(textEdit->font()); - lineHeight = fm.lineSpacing(); - } - - qreal availableHeight = textEdit->viewport()->height(); - const qreal margin = textEdit->document()->documentMargin(); - availableHeight -= margin; - - const qreal exactLines = availableHeight / lineHeight; - const qreal floorValue = std::floor(exactLines); - const qreal fractionalPart = exactLines - floorValue; - - // If the last line is more than 15% visible then count it, otherwise leave it - // avoids the issue where there is empty space at the bottom of a textedit widget - if (fractionalPart >= 0.15) { - return std::ceil(availableHeight / lineHeight); - } - - return floorValue; -} -} - namespace qhelpers { QString formatByteCount(ut64 bytecount) @@ -210,12 +174,20 @@ void SizePolicyMinMax::restoreHeight(QWidget *widget) const int getMaxFullyDisplayedLines(QTextEdit *textEdit) { - return calculateMaxDisplayedLines(textEdit); + const QFontMetrics fontMetrics(textEdit->document()->defaultFont()); + return (textEdit->height() + - (textEdit->contentsMargins().top() + textEdit->contentsMargins().bottom() + + (int)(textEdit->document()->documentMargin() * 2))) + / fontMetrics.lineSpacing(); } int getMaxFullyDisplayedLines(QPlainTextEdit *plainTextEdit) { - return calculateMaxDisplayedLines(plainTextEdit); + const QFontMetrics fontMetrics(plainTextEdit->document()->defaultFont()); + return (plainTextEdit->height() + - (plainTextEdit->contentsMargins().top() + plainTextEdit->contentsMargins().bottom() + + (int)(plainTextEdit->document()->documentMargin() * 2))) + / fontMetrics.lineSpacing(); } QByteArray applyColorToSvg(const QByteArray &data, QColor color) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 425c3ac8..10178b86 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1187,15 +1187,19 @@ void CutterCore::setConfig(const char *k, const RzInterval &itv) void CutterCore::setConfig(const char *k, const QStringList &list) { CORE_LOCK(); - RzSetS *set = rz_set_s_new(HT_STR_DUP); - if (!set) { + RzList *rzList = rz_list_newf(free); + if (!rzList) { return; } + for (const QString &str : list) { - rz_set_s_add(set, str.toUtf8().constData()); + char *dupStr = strdup(str.toUtf8().constData()); + if (dupStr) { + rz_list_append(rzList, dupStr); + } } - rz_config_set_set(core->config, k, set); - rz_set_s_free(set); + + rz_config_set_list(core->config, k, rzList); } int CutterCore::getConfigi(const char *k) @@ -1222,12 +1226,16 @@ RzInterval CutterCore::getConfigItv(const char *k) return rz_config_get_interval(core->config, k); } -QSet CutterCore::getConfigSet(const char *k) +QStringList CutterCore::getConfigList(const char *k) { CORE_LOCK(); - RzSetS *set = rz_config_get_set(core->config, k); - QSet res = convertRzSetS(set); - rz_set_s_free(set); + + QStringList res; + RzList *list = rz_config_get_list(core->config, k); + for (const auto *s : CutterRzList(list)) { + res << QString::fromUtf8(s); + } + rz_list_free(list); return res; } @@ -1287,18 +1295,18 @@ QString CutterCore::getConfig(const char *k) return { rz_config_get(core->config, k) }; } -QSet CutterCore::getConfigOptions(const char *k) +QStringList CutterCore::getConfigOptions(const char *k) { CORE_LOCK(); const RzConfigNode *node = rz_config_node_get(core->config, k); if (!(node && node->options)) { return {}; } - QSet res; - for (auto it = CutterRzIter(rz_set_s_as_iter(node->options)); it; ++it) { - res << QString::fromUtf8(*it); + QStringList list; + for (const auto &s : CutterRzList(node->options)) { + list << s; } - return res; + return list; } void CutterCore::setConfig(const char *k, const QVariant &v) @@ -4146,11 +4154,13 @@ QList CutterCore::getAllEvaluableVars() var.type = EvaluableVarDescription::Interval; const RzInterval itv = rz_config_var_get_interval(v); value = QVariant::fromValue(itv); - } else if (RZ_CONFIG_VAR_IS_TYPE(flags, RZ_CONFIG_VAR_TYPE_SET)) { - var.type = EvaluableVarDescription::Set; - RzSetS *set = rz_config_var_get_set(v); - value = QVariant::fromValue(convertRzSetS(set)); - rz_set_s_free(set); + } else if (RZ_CONFIG_VAR_IS_TYPE(flags, RZ_CONFIG_VAR_TYPE_LIST)) { + var.type = EvaluableVarDescription::List; + const RzList *list = rz_config_var_get_list(v); + QStringList stringList; + for (const auto *c : CutterRzList(list)) { + stringList << QString(c); + } } if (value.isNull()) { @@ -4158,9 +4168,13 @@ QList CutterCore::getAllEvaluableVars() } var.value = value; - const RzSetS *optionsSet = rz_config_var_get_options(v); - if (optionsSet) { - var.options = convertRzSetS(optionsSet); + const RzList *optionsList = rz_config_var_get_options(v); + if (optionsList) { + RzListIter *iter; + char *option; + CutterRzListForeach (optionsList, iter, char, option) { + var.options << QString::fromUtf8(option); + } } } else { const RzConfigNode *node = &entry->node; @@ -4175,8 +4189,10 @@ QList CutterCore::getAllEvaluableVars() var.value = QString::fromUtf8(rz_config_entry_get_as_string(entry)); - if (node->options) { - var.options = convertRzSetS(node->options); + RzListIter *iter; + char *option; + CutterRzListForeach (node->options, iter, char, option) { + var.options << QString::fromUtf8(option); } } diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 423c6032..f57f2eab 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -495,12 +495,12 @@ public: bool getConfigb(const QString &k) { return getConfigb(k.toUtf8().constData()); } RzInterval getConfigItv(const char *k); RzInterval getConfigItv(const QString &k) { return getConfigItv(k.toUtf8().constData()); } - QSet getConfigSet(const char *k); - QSet getConfigSet(const QString &k) { return getConfigSet(k.toUtf8().constData()); } + QStringList getConfigList(const char *k); + QStringList getConfigList(const QString &k) { return getConfigList(k.toUtf8().constData()); } QString getConfig(const char *k); QString getConfig(const QString &k) { return getConfig(k.toUtf8().constData()); } QString getConfigDescription(const char *k); - QSet getConfigOptions(const char *k); + QStringList getConfigOptions(const char *k); QStringList getColorThemes(); QHash getTheme(); QStringList getThemeKeys(); diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 0eea0278..0f23398b 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #include struct FunctionDescription @@ -448,11 +448,11 @@ struct EvaluableVarDescription QString description; bool readOnly; - enum Type : ut8 { Bool = 0, Int, String, Interval, Set }; + enum Type : ut8 { Bool = 0, Int, String, Interval, List }; Type type; - QVariant value; ///< Can be either QString, QSet or RzInterval depending on type - QSet options; + QVariant value; ///< Can be either QString, QStringList or RzInterval depending on type + QList options; }; Q_DECLARE_METATYPE(FunctionDescription) diff --git a/src/core/RizinCpp.cpp b/src/core/RizinCpp.cpp index 559a7c8f..594dfff6 100644 --- a/src/core/RizinCpp.cpp +++ b/src/core/RizinCpp.cpp @@ -1,12 +1 @@ #include "RizinCpp.h" - -#include - -QSet convertRzSetS(const RzSetS *set) -{ - QSet res; - for (auto it = CutterRzIter(rz_set_s_as_iter(set)); it; ++it) { - res << QString::fromUtf8(*it); - } - return res; -} diff --git a/src/core/RizinCpp.h b/src/core/RizinCpp.h index 407ba221..b1a39dbd 100644 --- a/src/core/RizinCpp.h +++ b/src/core/RizinCpp.h @@ -187,9 +187,9 @@ public: return *this; } operator bool() { return rzIter && rzIter->cur; } - T &operator*() { return *reinterpret_cast(rzIter->cur); } - T *get() { return reinterpret_cast(rzIter->cur); } - T *operator->() { return reinterpret_cast(rzIter->cur); } + T &operator*() { return *reinterpret_cast(rzIter->cur); } + T *get() { return reinterpret_cast(rzIter->cur); } + T *operator->() { return reinterpret_cast(rzIter->cur); } }; #define CutterHtDef(xx, XX, K, VB) \ @@ -219,6 +219,4 @@ public: CutterHtDef(sp, SP, const char *, void *); -QSet convertRzSetS(const RzSetS *set); - #endif // RIZINCPP_H diff --git a/src/dialogs/preferences/RizinConfigOptionsWidget.cpp b/src/dialogs/preferences/RizinConfigOptionsWidget.cpp index 38db0430..0f625b55 100644 --- a/src/dialogs/preferences/RizinConfigOptionsWidget.cpp +++ b/src/dialogs/preferences/RizinConfigOptionsWidget.cpp @@ -125,7 +125,7 @@ QVariant RizinConfigOptionsModel::data(const QModelIndex &index, int role) const const auto itv = evalVar.value.value(); return QString("%1+%2").arg(rzAddressString(itv.addr), rzSizeString(itv.size)); } - if (evalVar.type == EvaluableVarDescription::Set) { + if (evalVar.type == EvaluableVarDescription::List) { const QStringList list = evalVar.value.toStringList(); if (list.isEmpty()) { return QString(); @@ -344,16 +344,14 @@ QWidget *RizinConfigOptionsDelegate::createEditor(QWidget *parent, if (evalVar.type == EvaluableVarDescription::Bool || evalVar.type == EvaluableVarDescription::Interval - || evalVar.type == EvaluableVarDescription::Set) { + || evalVar.type == EvaluableVarDescription::List) { // handled in lambda connected to ui->treeView::doubleClicked return nullptr; } if (!evalVar.options.isEmpty()) { auto *comboBox = new QComboBox(parent); - QStringList opts = evalVar.options.values(); - opts.sort(); - for (const auto &opt : opts) { + for (const auto &opt : evalVar.options) { comboBox->addItem(opt); } return comboBox; @@ -488,7 +486,7 @@ RizinConfigOptionsWidget::RizinConfigOptionsWidget(PreferencesDialog *parent) return; } - if (evalVar.type == EvaluableVarDescription::Set) { + if (evalVar.type == EvaluableVarDescription::List) { StringListDialog dialog(evalVar.value.toStringList(), this); dialog.setWindowTitle(tr("Edit List for %1").arg(evalVar.name)); @@ -598,8 +596,8 @@ void RizinConfigOptionsWidget::handleConfigOptionChanged(const QModelIndex &topL if (!originalValues.contains(evalVar.name)) { if (evalVar.type == EvaluableVarDescription::Interval) { originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigItv(evalVar.name)); - } else if (evalVar.type == EvaluableVarDescription::Set) { - originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigSet(evalVar.name)); + } else if (evalVar.type == EvaluableVarDescription::List) { + originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigList(evalVar.name)); } else { originalValues[evalVar.name] = Core()->getConfig(evalVar.name); } diff --git a/src/shortcuts/DefaultShortcuts.cpp b/src/shortcuts/DefaultShortcuts.cpp index 2ef4496b..f93d7c2b 100644 --- a/src/shortcuts/DefaultShortcuts.cpp +++ b/src/shortcuts/DefaultShortcuts.cpp @@ -215,10 +215,6 @@ const QHash &getDefaultShortcuts() { { Qt::Key_Space }, QT_TRANSLATE_NOOP("DisassemblyWidget", "Switch to Graph"), "DisassemblyWidget" } }, - { "Disassembly.moveLeft", - { QList { Qt::Key_H } - + QKeySequence::keyBindings(QKeySequence::MoveToPreviousChar), - QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Down"), "DisassemblyWidget" } }, { "Disassembly.moveDown", { QList { Qt::Key_J } + QKeySequence::keyBindings(QKeySequence::MoveToNextLine), @@ -227,10 +223,6 @@ const QHash &getDefaultShortcuts() { QList { Qt::Key_K } + QKeySequence::keyBindings(QKeySequence::MoveToPreviousLine), QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Up"), "DisassemblyWidget" } }, - { "Disassembly.moveRight", - { QList { Qt::Key_L } - + QKeySequence::keyBindings(QKeySequence::MoveToNextChar), - QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Up"), "DisassemblyWidget" } }, { "Disassembly.pageDown", { QKeySequence::keyBindings(QKeySequence::MoveToNextPage), QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Down By Page"), diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 87fbcb42..da0d49d1 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -13,7 +13,6 @@ #include "widgets/AddressRangeScrollBar.h" #include -#include #include #include #include @@ -22,7 +21,6 @@ #include #include #include -#include #include #include @@ -102,6 +100,8 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main) setupFonts(); setupColors(); + updateMaxLines(); + mDisasTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); mDisasTextEdit->setFont(Config()->getFont()); mDisasTextEdit->setReadOnly(true); @@ -140,27 +140,18 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main) connect(Core(), &CutterCore::functionRenamed, this, refresh); connect(Core(), &CutterCore::varsChanged, this, refresh); connect(Core(), &CutterCore::asmOptionsChanged, this, refresh); - connect(Core(), &CutterCore::refreshCodeViews, this, [this] { - breakpointsDirty = true; - refreshDisasm(); - }); + connect(Core(), &CutterCore::refreshCodeViews, this, refresh); connect(Core(), &CutterCore::instructionChanged, this, &DisassemblyWidget::instructionChanged); - connect(Core(), &CutterCore::breakpointsChanged, this, [this](RVA offset) { - breakpointsDirty = true; - refreshIfInRange(offset); - }); + connect(Core(), &CutterCore::breakpointsChanged, this, &DisassemblyWidget::refreshIfInRange); connect(Config(), &Configuration::fontsUpdated, this, &DisassemblyWidget::fontsUpdatedSlot); connect(Config(), &Configuration::colorsUpdated, this, &DisassemblyWidget::colorsUpdatedSlot); - connect(Core(), &CutterCore::refreshAll, this, [this]() { - // just in case if breakpoints were changed via rizin console - breakpointsDirty = true; - refreshDisasm(seekable->getOffset()); - }); + connect(Core(), &CutterCore::refreshAll, this, + [this]() { refreshDisasm(seekable->getOffset()); }); refreshDisasm(seekable->getOffset()); - connect(mCtxMenu, &DisassemblyContextMenu::copy, this, &DisassemblyWidget::copySelection); + connect(mCtxMenu, &DisassemblyContextMenu::copy, mDisasTextEdit, &QPlainTextEdit::copy); mCtxMenu->addSeparator(); mCtxMenu->addAction(&syncAction); @@ -183,16 +174,15 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main) ADD_ACTION("General.seekPrev", Qt::WidgetWithChildrenShortcut, &DisassemblyWidget::seekPrev) - ADD_ACTION("Disassembly.pageUp", Qt::WidgetWithChildrenShortcut, - [this] { moveCursorRelative(QTextCursor::Up, true); }); - + ADD_ACTION("Disassembly.moveDown", Qt::WidgetWithChildrenShortcut, + [this]() { moveCursorRelative(false, false); }) + ADD_ACTION("Disassembly.moveUp", Qt::WidgetWithChildrenShortcut, + [this]() { moveCursorRelative(true, false); }) ADD_ACTION("Disassembly.pageDown", Qt::WidgetWithChildrenShortcut, - [this] { moveCursorRelative(QTextCursor::Down, true); }); - + [this]() { moveCursorRelative(false, true); }) + ADD_ACTION("Disassembly.pageUp", Qt::WidgetWithChildrenShortcut, + [this]() { moveCursorRelative(true, true); }) #undef ADD_ACTION - - QTimer::singleShot(0, [this] { updateMaxLines(); }); - connect(this, &CutterDockWidget::becameVisibleToUser, this, [this] { updateMaxLines(); }); } void DisassemblyWidget::setPreviewMode(bool previewMode) @@ -250,7 +240,7 @@ void DisassemblyWidget::instructionChanged(RVA offset) refreshDisasm(); } -void DisassemblyWidget::refreshDisasm(RVA offset, RefreshMode mode) +void DisassemblyWidget::refreshDisasm(RVA offset) { if (!disasmRefresh->attemptRefresh(offset == RVA_INVALID ? nullptr : new RVA(offset))) { return; @@ -271,72 +261,30 @@ void DisassemblyWidget::refreshDisasm(RVA offset, RefreshMode mode) return; } - if (breakpointsDirty) { - breakpoints = Core()->getBreakpointsAddresses(); - breakpointsDirty = false; - } + breakpoints = Core()->getBreakpointsAddresses(); const int horizontalScrollValue = mDisasTextEdit->horizontalScrollBar()->value(); - mDisasTextEdit->setLockScroll(true); + mDisasTextEdit->setLockScroll(true); // avoid flicker // Retrieve disassembly lines { TempConfig tempConfig; tempConfig.set("scr.color", COLOR_MODE_16M).set("asm.lines", false); - - if (mode == RefreshMode::Reset || lines.isEmpty()) { - lines = Core()->disassembleLines(topOffset, maxLines); - startIndex = 0; - } else if (mode == RefreshMode::Append) { - const RVA newOffset = Core()->nextOpAddr(lines.last().offset, 1); - lines.append(Core()->disassembleLines(newOffset, maxLines)); - - } else if (mode == RefreshMode::Prepend) { - const RVA newOffset = Core()->prevOpAddr(lines.first().offset, maxLines); - auto newLines = Core()->disassembleLines(newOffset, maxLines); - - // remove duplicates if any - if (!newLines.isEmpty() && !lines.isEmpty()) { - int keepSize = newLines.size(); - const RVA firstOffset = lines.first().offset; - for (int i = newLines.size() - 1; i >= 0; --i) { - if (newLines[i].offset >= firstOffset) { - keepSize = i; - } else { - break; - } - } - if (keepSize < newLines.size()) { - newLines.erase(newLines.begin() + keepSize, newLines.end()); - } - } - - const int prependCount = newLines.size(); - newLines.append(lines); - lines = std::move(newLines); - - startIndex += prependCount; - } + lines = Core()->disassembleLines(topOffset, maxLines); } - startIndex = qMax(0, qMin(startIndex, static_cast(lines.size()) - 1)); - endIndex = qMin(lines.size(), startIndex + maxLines); - - // render lines connectCursorPositionChanged(true); + mDisasTextEdit->document()->clear(); QTextCursor cursor(mDisasTextEdit->document()); - cursor.beginEditBlock(); - const QTextBlockFormat regular = cursor.blockFormat(); - const QColor breakpointBg(ConfigColor("gui.breakpoint_background")); - - for (int i = startIndex; i < endIndex; ++i) { - const auto &line = lines[i]; - + for (const DisassemblyLine &line : lines) { + if (line.offset < topOffset) { // overflow + break; + } cursor.insertHtml(line.text); if (Core()->isBreakpoint(breakpoints, line.offset)) { QTextBlockFormat f; - f.setBackground(breakpointBg); + f.setBackground(ConfigColor("gui.breakpoint_background")); cursor.setBlockFormat(f); } auto a = new DisassemblyTextBlockUserData(line); @@ -345,10 +293,8 @@ void DisassemblyWidget::refreshDisasm(RVA offset, RefreshMode mode) cursor.setBlockFormat(regular); } - cursor.endEditBlock(); - if (!lines.isEmpty()) { - bottomOffset = lines[endIndex - 1].offset; + bottomOffset = lines[qMin(lines.size(), maxLines) - 1].offset; if (bottomOffset < topOffset) { bottomOffset = RVA_MAX; } @@ -356,54 +302,24 @@ void DisassemblyWidget::refreshDisasm(RVA offset, RefreshMode mode) bottomOffset = topOffset; } - // update cursor position connectCursorPositionChanged(false); + updateCursorPosition(); - connectCursorPositionChanged(true); - // update cursor selection - const bool hasSelection = (selectionAnchorRVA != RVA_INVALID && selectionPosRVA != RVA_INVALID - && (selectionAnchorRVA != selectionPosRVA - || selectionAnchorSubIndex != selectionPosSubIndex - || selectionAnchorChar != selectionPosChar)); - if (hasSelection) { - updateSelection(); - } - connectCursorPositionChanged(false); + // remove additional lines + QTextCursor tc = mDisasTextEdit->textCursor(); + tc.movePosition(QTextCursor::Start); + tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, maxLines - 1); + tc.movePosition(QTextCursor::EndOfLine); + tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + tc.removeSelectedText(); - // update ctx menu - updateContextMenuSelection(hasSelection); - - // update scrollbars mDisasTextEdit->setLockScroll(false); mDisasTextEdit->horizontalScrollBar()->setValue(horizontalScrollValue); - mDisasTextEdit->verticalScrollBar()->setValue(0); - mDisasScrollArea->verticalScrollBar()->setPosition(topOffset); - // update left panel (trigger paint event) + // Refresh the left panel (trigger paintEvent) leftPanel->update(); - - // update buffer by erasing extra lines - constexpr int multiplier = 5; - const int targetSize = maxLines * multiplier; - if (lines.size() <= targetSize) { - return; - } - - // TODO: its fine for now but maybe in future this could be made more efficie - // if the complexity trade-off is bearable - const int idealKeepStart = startIndex - (targetSize - maxLines) / 2; - const int keepStart = - qMax(0, qMin(idealKeepStart, static_cast(lines.size() - targetSize))); - const int removeBottom = lines.size() - (keepStart + targetSize); - if (removeBottom > 0) { - lines.erase(lines.begin() + keepStart + targetSize, lines.begin() + lines.size()); - } - if (keepStart > 0) { - lines.erase(lines.begin(), lines.begin() + keepStart); - startIndex -= keepStart; - } } void DisassemblyWidget::scrollInstructions(int count, bool clampToScrollBarRange) @@ -412,30 +328,16 @@ void DisassemblyWidget::scrollInstructions(int count, bool clampToScrollBarRange return; } - startIndex += count; - RefreshMode mode = RefreshMode::Reset; - if (startIndex < 0) { - mode = RefreshMode::Prepend; - } else if (startIndex + maxLines > lines.size()) { - mode = RefreshMode::Append; - } else if (startIndex >= 0 && startIndex < lines.size()) { - mode = RefreshMode::None; - } - - RVA offset = topOffset; - if (mode == RefreshMode::None) { - offset = lines[startIndex].offset; + RVA offset; + if (count > 0) { + offset = Core()->nextOpAddr(topOffset, count); + if (offset < topOffset) { + offset = RVA_MAX; + } } else { - if (count > 0) { - offset = Core()->nextOpAddr(topOffset, count); - if (offset < topOffset) { - offset = RVA_MAX; - } - } else { - offset = Core()->prevOpAddr(topOffset, -count); - if (offset > topOffset) { - offset = 0; - } + offset = Core()->prevOpAddr(topOffset, -count); + if (offset > topOffset) { + offset = 0; } } @@ -443,169 +345,10 @@ void DisassemblyWidget::scrollInstructions(int count, bool clampToScrollBarRange offset = mDisasScrollArea->verticalScrollBar()->clampAddressToRange(offset); } - refreshDisasm(offset, mode); + refreshDisasm(offset); topOffsetHistory[topOffsetHistoryPos] = offset; } -void DisassemblyWidget::updateSelection() -{ - int anchorRenderIndex = -1; - int posRenderIndex = -1; - - auto findRenderIndex = [&](RVA offset, int offsetBlockIndex) -> int { - // safely large number to clamp the highlight off-screen - // without causing integer overflow during UI math - constexpr int offscreenPadding = 100000; - - if (lines.isEmpty()) { - return 0; - } - if (offset < lines.first().offset) { - return -offscreenPadding; - } - if (offset > lines.last().offset) { - return lines.size() + offscreenPadding; - } - - const int exactIndex = getLineIndex(offset, offsetBlockIndex); - if (exactIndex != -1) { - return exactIndex; - } - - // fallback: if somehow between bounds but missing, return nearest insertion point - auto it = std::lower_bound( - lines.begin(), lines.end(), offset, - [](const DisassemblyLine &line, RVA target) { return line.offset < target; }); - return std::distance(lines.begin(), it); - }; - - anchorRenderIndex = findRenderIndex(selectionAnchorRVA, selectionAnchorSubIndex); - posRenderIndex = findRenderIndex(selectionPosRVA, selectionPosSubIndex); - - // If the entire selection is completely above or completely below the screen then do nothing - if ((anchorRenderIndex < startIndex && posRenderIndex < startIndex) - || (anchorRenderIndex >= endIndex && posRenderIndex >= endIndex)) { - mDisasTextEdit->textCursor().clearSelection(); - return; - } - - // clamp indices so they don't reach outside current viewport - const int clampedAnchorIndex = qBound(startIndex, anchorRenderIndex, endIndex - 1); - const int clampedPosIndex = qBound(startIndex, posRenderIndex, endIndex - 1); - - const QTextBlock anchorBlock = - mDisasTextEdit->document()->findBlockByNumber(clampedAnchorIndex - startIndex); - const QTextBlock posBlock = - mDisasTextEdit->document()->findBlockByNumber(clampedPosIndex - startIndex); - - if (anchorBlock.isValid() && posBlock.isValid()) { - int finalAnchorPos = anchorBlock.position(); - int finalPosPos = posBlock.position(); - - // if selection starts above visible lines/blocks then start the selection from first - // character of first block otherwise start from where the selection actually started - if (anchorRenderIndex >= startIndex) { - finalAnchorPos += qMin(selectionAnchorChar, qMax(0, anchorBlock.length() - 1)); - } - - // same as above but for selection end - if (posRenderIndex < endIndex) { - finalPosPos += qMin(selectionPosChar, qMax(0, posBlock.length() - 1)); - } else { - // The position is below the screen - select till end of the last visible block - finalPosPos += qMax(0, posBlock.length() - 1); - } - - QTextCursor restoreCursor(mDisasTextEdit->document()); - restoreCursor.setPosition(finalAnchorPos); - restoreCursor.setPosition(finalPosPos, QTextCursor::KeepAnchor); - mDisasTextEdit->setTextCursor(restoreCursor); - } -} - -void DisassemblyWidget::updateContextMenuSelection(bool hasSelection) -{ - mCtxMenu->setCanCopy(hasSelection); - if (hasSelection) { - mCtxMenu->setCurHighlightedWord(mDisasTextEdit->textCursor().selectedText()); - } else { - mCtxMenu->setCurHighlightedWord(curHighlightedWord); - } -} - -int DisassemblyWidget::getLineIndex(RVA offset, int offsetSubIndex) const -{ - if (lines.isEmpty()) { - return -1; - } - - auto it = std::lower_bound( - lines.begin(), lines.end(), offset, - [](const DisassemblyLine &line, RVA target) { return line.offset < target; }); - - if (it != lines.end() && it->offset == offset) { - const int baseIndex = std::distance(lines.begin(), it); - if (baseIndex + offsetSubIndex < lines.size() - && lines[baseIndex + offsetSubIndex].offset == offset) { - return baseIndex + offsetSubIndex; - } - return baseIndex; - } - return -1; -} - -void DisassemblyWidget::updateLineHighlights() -{ - QList combinedSelections; - combinedSelections.append(highlightCurrentLine()); - combinedSelections.append(highlightPCLine()); - mDisasTextEdit->setExtraSelections(combinedSelections); -} - -void DisassemblyWidget::invalidateCursorSelection() -{ - selectionAnchorRVA = RVA_INVALID; - selectionPosRVA = RVA_INVALID; - selectionAnchorSubIndex = 0; - selectionPosSubIndex = 0; - selectionAnchorChar = 0; - selectionPosChar = 0; -} - -int DisassemblyWidget::getIndexInOffsetGroup(const QTextCursor &cursor) const -{ - const int lineIndex = startIndex + cursor.blockNumber(); - if (lineIndex < 0 || lineIndex >= lines.size()) { - return 0; - } - - const RVA offset = lines[lineIndex].offset; - int subIndex = 0; - - for (int i = lineIndex - 1; i >= 0; --i) { - if (lines[i].offset == offset) { - subIndex++; - } else { - break; // offsets are contiguous - } - } - return subIndex; -} - -void DisassemblyWidget::updateSelectionPos(const QTextCursor &cursor) -{ - selectionPosRVA = DisHlp::readDisassemblyOffset(cursor); - selectionPosSubIndex = getIndexInOffsetGroup(cursor); - selectionPosChar = cursor.positionInBlock(); -} - -void DisassemblyWidget::updateSelectionAnchor(const QTextCursor &cursor) -{ - selectionAnchorRVA = DisHlp::readDisassemblyOffset(cursor); - selectionAnchorSubIndex = getIndexInOffsetGroup(cursor); - selectionAnchorChar = cursor.positionInBlock(); -} - bool DisassemblyWidget::updateMaxLines() { const int currentMaxLines = qhelpers::getMaxFullyDisplayedLines(mDisasTextEdit); @@ -619,61 +362,42 @@ bool DisassemblyWidget::updateMaxLines() return false; } -QList DisassemblyWidget::highlightCurrentLine() +void DisassemblyWidget::highlightCurrentLine() { QList extraSelections; const QColor highlightColor = ConfigColor("lineHighlight"); - const RVA currentOffset = seekable->getOffset(); - - bool isOffScreen = true; - if (currentOffset >= topOffset - && (currentOffset <= bottomOffset || bottomOffset == RVA_INVALID)) { - const int targetLineIndex = getLineIndex(currentOffset, cursorLineOffset); - if (targetLineIndex >= startIndex && targetLineIndex < endIndex) { - isOffScreen = false; - } - } - + // Highlight the current word QTextCursor cursor = mDisasTextEdit->textCursor(); - - if (!isOffScreen) { - if (cursor.hasSelection()) { - curHighlightedWord = cursor.selectedText(); - } else { - auto clickedCharPos = cursor.positionInBlock(); - // Select the line (BlockUnderCursor matches a line with current implementation) - cursor.select(QTextCursor::BlockUnderCursor); - // Remove any non-breakable space from the current line - const QString searchString = cursor.selectedText().replace("\xc2\xa0", " "); - // Cut the line in "tokens" that can be highlighted - static const QRegularExpression tokenRegExp(R"(\b(? clickedCharPos) { - curHighlightedWord = match.captured(); - break; - } - } + auto clickedCharPos = cursor.positionInBlock(); + // Select the line (BlockUnderCursor matches a line with current implementation) + cursor.select(QTextCursor::BlockUnderCursor); + // Remove any non-breakable space from the current line + const QString searchString = cursor.selectedText().replace("\xc2\xa0", " "); + // Cut the line in "tokens" that can be highlighted + static const QRegularExpression tokenRegExp(R"(\b(? clickedCharPos) { + curHighlightedWord = match.captured(); + break; } } // Highlight the current line QTextEdit::ExtraSelection highlightSelection; - highlightSelection.cursor = mDisasTextEdit->textCursor(); + highlightSelection.cursor = cursor; highlightSelection.cursor.movePosition(QTextCursor::Start); while (true) { const RVA lineOffset = DisHlp::readDisassemblyOffset(highlightSelection.cursor); - if (lineOffset == currentOffset) { + if (lineOffset == seekable->getOffset()) { highlightSelection.format.setBackground(highlightColor); highlightSelection.format.setProperty(QTextFormat::FullWidthSelection, true); highlightSelection.cursor.clearSelection(); extraSelections.append(highlightSelection); - } else if (lineOffset != RVA_INVALID && lineOffset > currentOffset) { + } else if (lineOffset != RVA_INVALID && lineOffset > seekable->getOffset()) { break; } highlightSelection.cursor.movePosition(QTextCursor::EndOfLine); @@ -685,14 +409,12 @@ QList DisassemblyWidget::highlightCurrentLine() } // Highlight all the words in the document same as the current one - if (!curHighlightedWord.isEmpty()) { - extraSelections.append(createSameWordsSelections(mDisasTextEdit, curHighlightedWord)); - } + extraSelections.append(createSameWordsSelections(mDisasTextEdit, curHighlightedWord)); - return extraSelections; + mDisasTextEdit->setExtraSelections(extraSelections); } -QList DisassemblyWidget::highlightPCLine() +void DisassemblyWidget::highlightPCLine() { const RVA pcAddr = Core()->getProgramCounterValue(); @@ -722,7 +444,11 @@ QList DisassemblyWidget::highlightPCLine() } } - return pcSelections; + // Don't override any extraSelections already set + QList currentSelections = mDisasTextEdit->extraSelections(); + currentSelections.append(pcSelections); + + mDisasTextEdit->setExtraSelections(currentSelections); } void DisassemblyWidget::showDisasContextMenu(const QPoint &pt) @@ -740,37 +466,63 @@ void DisassemblyWidget::updateCursorPosition() { const RVA offset = seekable->getOffset(); + // already fine where it is? + const RVA currentLineOffset = readCurrentDisassemblyOffset(); + if (currentLineOffset == offset) { + return; + } + connectCursorPositionChanged(true); - const int targetLineIndex = getLineIndex(offset, cursorLineOffset); - - bool isOffScreen = false; if (offset < topOffset || (offset > bottomOffset && bottomOffset != RVA_INVALID)) { - isOffScreen = true; - } else if (targetLineIndex < startIndex || targetLineIndex >= endIndex) { - isOffScreen = true; - } - - if (isOffScreen) { mDisasTextEdit->moveCursor(QTextCursor::Start); - mDisasTextEdit->setCursorVisible(false); + mDisasTextEdit->setExtraSelections( + createSameWordsSelections(mDisasTextEdit, curHighlightedWord)); } else { - const int targetBlockNum = targetLineIndex - startIndex; - QTextCursor cursor = mDisasTextEdit->textCursor(); + const RVA currentCursorOffset = readCurrentDisassemblyOffset(); + const QTextCursor originalCursor = mDisasTextEdit->textCursor(); + + QTextCursor cursor = originalCursor; cursor.movePosition(QTextCursor::Start); - if (targetBlockNum > 0) { - cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, targetBlockNum); - } - if (cursorCharOffset > 0) { - cursor.movePosition(QTextCursor::StartOfLine); - cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, cursorCharOffset); + + while (true) { + const RVA lineOffset = DisHlp::readDisassemblyOffset(cursor); + if (lineOffset == offset) { + if (cursorLineOffset > 0) { + cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, + cursorLineOffset); + } + if (cursorCharOffset > 0) { + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, + cursorCharOffset); + } + + mDisasTextEdit->setTextCursor(cursor); + highlightCurrentLine(); + break; + } else if (lineOffset != RVA_INVALID && lineOffset > offset) { + mDisasTextEdit->moveCursor(QTextCursor::Start); + mDisasTextEdit->setExtraSelections({}); + break; + } + + cursor.movePosition(QTextCursor::EndOfLine); + if (cursor.atEnd()) { + break; + } + + cursor.movePosition(QTextCursor::Down); } - mDisasTextEdit->setTextCursor(cursor); - mDisasTextEdit->setCursorVisible(true); + // this is true if a seek came from the user clicking on a line. + // then the cursor should be restored 1:1 to retain selection and cursor position. + if (currentCursorOffset == offset) { + mDisasTextEdit->setTextCursor(originalCursor); + } } - updateLineHighlights(); + highlightPCLine(); connectCursorPositionChanged(false); } @@ -790,137 +542,35 @@ void DisassemblyWidget::cursorPositionChanged() { const RVA offset = readCurrentDisassemblyOffset(); - const auto c = mDisasTextEdit->textCursor(); + cursorLineOffset = 0; + QTextCursor c = mDisasTextEdit->textCursor(); cursorCharOffset = c.positionInBlock(); - cursorLineOffset = getIndexInOffsetGroup(c); + while (c.blockNumber() > 0) { + c.movePosition(QTextCursor::PreviousBlock); + if (DisHlp::readDisassemblyOffset(c) != offset) { + break; + } + cursorLineOffset++; + } seekFromCursor = true; seekable->seek(offset); seekFromCursor = false; - - updateLineHighlights(); - updateContextMenuSelection(mDisasTextEdit->textCursor().hasSelection()); + highlightCurrentLine(); + highlightPCLine(); + mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection()); + if (mDisasTextEdit->textCursor().hasSelection()) { + // A word is selected so use it + mCtxMenu->setCurHighlightedWord(mDisasTextEdit->textCursor().selectedText()); + } else { + // No word is selected so use the word under the cursor + mCtxMenu->setCurHighlightedWord(curHighlightedWord); + } leftPanel->update(); } -void DisassemblyWidget::copySelection() +void DisassemblyWidget::moveCursorRelative(bool up, bool page) { - if (selectionAnchorRVA == RVA_INVALID || selectionPosRVA == RVA_INVALID) { - mDisasTextEdit->copy(); - return; - } - - // determine direction (user could have dragged upwards) - bool forward = true; - if (selectionAnchorRVA > selectionPosRVA) { - forward = false; - } else if (selectionAnchorRVA == selectionPosRVA) { - forward = (selectionAnchorChar <= selectionPosChar); - } - - const RVA startRVA = forward ? selectionAnchorRVA : selectionPosRVA; - const RVA endRVA = forward ? selectionPosRVA : selectionAnchorRVA; - const int startChar = forward ? selectionAnchorChar : selectionPosChar; - const int endChar = forward ? selectionPosChar : selectionAnchorChar; - const int startRVAIndex = forward ? selectionAnchorSubIndex : selectionPosSubIndex; - const int endRVAIndex = forward ? selectionPosSubIndex : selectionAnchorSubIndex; - - QStringList plainTextLines; - QTextDocument htmlStripper; - - // check if the entire selection is currently sitting in 'lines' buffer - int selectionStart = -1, selectionEnd = -1; - if (!lines.isEmpty() && startRVA >= lines.first().offset && endRVA <= lines.last().offset) { - selectionStart = getLineIndex(startRVA, startRVAIndex); - selectionEnd = getLineIndex(endRVA, endRVAIndex); - } - - // extract text - if (selectionStart != -1 && selectionEnd != -1 && selectionStart <= selectionEnd) { - for (int i = selectionStart; i <= selectionEnd; ++i) { - htmlStripper.setHtml(lines[i].text); - plainTextLines.append(htmlStripper.toPlainText()); - } - } else { - // selection exceeds buffer - TempConfig tempConfig; - tempConfig.set("scr.color", COLOR_MODE_DISABLED).set("asm.lines", false); - - RVA currentRVA = startRVA; - int currentStartHit = 0; - int currentEndHit = 0; - - while (currentRVA <= endRVA && currentRVA != RVA_INVALID) { - const auto fetchedLines = Core()->disassembleLines(currentRVA, maxLines); - if (fetchedLines.isEmpty()) { - break; - } - - for (const auto &line : fetchedLines) { - // skip unselected duplicate lines at the top - if (line.offset == startRVA && currentStartHit < startRVAIndex) { - currentStartHit++; - continue; - } - - if (line.offset > endRVA) { - currentRVA = RVA_INVALID; // reached target - break; - } - - plainTextLines.append(QString(line.text).replace(" ", " ")); - - if (line.offset == endRVA) { - if (currentEndHit == endRVAIndex) { - currentRVA = RVA_INVALID; // reached target - break; - } - currentEndHit++; - } - } - - if (currentRVA != RVA_INVALID) { - currentRVA = Core()->nextOpAddr(fetchedLines.last().offset, 1); - } - } - } - - if (plainTextLines.isEmpty()) { - return; - } - - if (plainTextLines.size() == 1) { - // single-line selection - const int len = qMax(0, endChar - startChar); - plainTextLines[0] = plainTextLines[0].mid(startChar, len); - } else { - // multi-line selection - if (startChar > 0) { - plainTextLines[0] = plainTextLines[0].mid(startChar); - } - if (endChar >= 0 && endChar < plainTextLines.last().length()) { - plainTextLines.last() = plainTextLines.last().left(endChar); - } - } - - const QString finalCopiedText = plainTextLines.join("\n"); - QApplication::clipboard()->setText(finalCopiedText); -} - -void DisassemblyWidget::moveCursorRelative(QTextCursor::MoveOperation op, bool page) -{ - const bool up = op == QTextCursor::Up; - const bool select = QApplication::keyboardModifiers() & Qt::ShiftModifier; - const bool hadSelection = (selectionAnchorRVA != RVA_INVALID && selectionPosRVA != RVA_INVALID); - - if (select) { - if (!hadSelection) { - updateSelectionAnchor(mDisasTextEdit->textCursor()); - } - } else { - invalidateCursorSelection(); - } - if (page) { RVA offset; if (!up) { @@ -955,53 +605,28 @@ void DisassemblyWidget::moveCursorRelative(QTextCursor::MoveOperation op, bool p } } refreshDisasm(offset); - - if (select) { - const auto newCursor = mDisasTextEdit->textCursor(); - selectionPosRVA = DisHlp::readDisassemblyOffset(newCursor); - selectionPosSubIndex = getIndexInOffsetGroup(newCursor); - selectionPosChar = newCursor.positionInBlock(); - refreshDisasm(RVA_INVALID, RefreshMode::None); - } else if (hadSelection) { - refreshDisasm(RVA_INVALID, RefreshMode::None); - } - } else { - if (op == QTextCursor::Left || op == QTextCursor::Right) { - mDisasTextEdit->moveCursor(op, - select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); - - if (select) { - updateSelectionPos(mDisasTextEdit->textCursor()); - refreshDisasm(RVA_INVALID, RefreshMode::None); - } else if (hadSelection) { - refreshDisasm(RVA_INVALID, RefreshMode::None); - } + } else { // normal arrow keys + const int blockCount = mDisasTextEdit->blockCount(); + if (blockCount < 1) { return; } const int blockNumber = mDisasTextEdit->textCursor().blockNumber(); - const int count = endIndex - startIndex; - if (up && blockNumber <= 0) { - scrollInstructions(-1); - } else if (op == QTextCursor::Down && blockNumber >= count - 1) { + if (blockNumber == blockCount - 1 && !up) { scrollInstructions(1); + } else if (blockNumber == 0 && up) { + scrollInstructions(-1); } - mDisasTextEdit->moveCursor(op, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); - mDisasTextEdit->verticalScrollBar()->setValue(startIndex - startIndex); - - if (select) { - updateSelectionPos(mDisasTextEdit->textCursor()); - refreshDisasm(RVA_INVALID, RefreshMode::None); - } + mDisasTextEdit->moveCursor(up ? QTextCursor::Up : QTextCursor::Down); + // handle cases where top instruction offsets change const RVA offset = readCurrentDisassemblyOffset(); if (offset != seekable->getOffset()) { seekable->seek(offset); - updateLineHighlights(); - } else if (!select && hadSelection) { - refreshDisasm(RVA_INVALID, RefreshMode::None); + highlightCurrentLine(); + highlightPCLine(); } } } @@ -1068,54 +693,6 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent *event) } else { jumpToOffsetUnderCursor(cursor); } - event->accept(); - return; - } - - // handle cursor shortcuts here because we need to check if shift is pressed for - // updating selection. - // shift select is not supported for page up and page down currently - bool handled = false; - QTextCursor::MoveOperation op; - - // fix for macos, since macos appends KeypadModifier to cursor keys pressed via keypad, it - // causes the QKeySequence::ExactMatch check to fail. - // We remove the modifier here - const Qt::KeyboardModifiers pureModifiers = event->modifiers() & ~Qt::KeypadModifier; - - const int baseKey = event->key() | (pureModifiers & ~Qt::ShiftModifier); - QKeySequence baseSeq(baseKey); - QKeySequence exactSeq(event->key() | event->modifiers()); - - auto matches = [&](const QString &id) { - for (const QKeySequence &seq : Shortcuts()->getKeySequences(id)) { - if (seq.matches(baseSeq) == QKeySequence::ExactMatch - || seq.matches(exactSeq) == QKeySequence::ExactMatch) { - return true; - } - } - return false; - }; - - if (matches("Disassembly.moveUp")) { - op = QTextCursor::Up; - handled = true; - } else if (matches("Disassembly.moveDown")) { - op = QTextCursor::Down; - handled = true; - } else if (matches("Disassembly.moveLeft")) { - op = QTextCursor::Left; - handled = true; - } else if (matches("Disassembly.moveRight")) { - op = QTextCursor::Right; - handled = true; - } - - if (handled) { - moveCursorRelative(op, false); - mDisasTextEdit->setCursorVisible(true); - event->accept(); - return; } MemoryDockWidget::keyPressEvent(event); @@ -1137,16 +714,6 @@ QString DisassemblyWidget::getWindowTitle() const return tr("Disassembly"); } -int DisassemblyWidget::getEndIndex() const -{ - return endIndex; -} - -int DisassemblyWidget::getStartIndex() const -{ - return startIndex; -} - void DisassemblyWidget::onSeekChanged(RVA offset, CutterCore::SeekHistoryType type) { if (type == CutterCore::SeekHistoryType::New) { @@ -1165,10 +732,6 @@ void DisassemblyWidget::onSeekChanged(RVA offset, CutterCore::SeekHistoryType ty if (!seekFromCursor) { cursorLineOffset = 0; cursorCharOffset = 0; - - // invalidate selection if the seek came from an external - // source (like seeking via VisualNavBar) - invalidateCursorSelection(); } if (topOffset != RVA_INVALID && offset >= topOffset && offset <= bottomOffset @@ -1268,35 +831,11 @@ void DisassemblyScrollArea::wheelEvent(QWheelEvent *event) emit wheelEventTriggered(); } -DisassemblyTextEdit::DisassemblyTextEdit(DisassemblyWidget *disasmWidget) - : QPlainTextEdit(disasmWidget), lockScroll(false), disasmWidget(disasmWidget) -{ - - blinkTimer = new QTimer(this); - connect(blinkTimer, &QTimer::timeout, this, [this]() { - cursorVisible = !cursorVisible; - viewport()->update(); - }); - blinkTimer->start(500); -} - qreal DisassemblyTextEdit::textOffset() const { return (blockBoundingGeometry(document()->begin()).topLeft() + contentOffset()).y(); } -void DisassemblyTextEdit::setCursorVisible(bool visible) -{ - if (visible) { - cursorVisible = true; - blinkTimer->start(500); - } else { - cursorVisible = false; - blinkTimer->stop(); - } - viewport()->update(); -} - bool DisassemblyTextEdit::viewportEvent(QEvent *event) { switch (event->type()) { @@ -1322,16 +861,6 @@ void DisassemblyTextEdit::keyPressEvent(QKeyEvent *event) void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - - const QTextCursor cursor = cursorForPosition(event->pos()); - - disasmWidget->updateSelectionAnchor(cursor); - disasmWidget->updateSelectionPos(cursor); - - disasmWidget->refreshDisasm(RVA_INVALID, RefreshMode::None); - } - QPlainTextEdit::mousePressEvent(event); if (event->button() == Qt::RightButton && !textCursor().hasSelection()) { @@ -1339,45 +868,6 @@ void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event) } } -void DisassemblyTextEdit::mouseMoveEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::LeftButton) { - - const QPoint pos = event->pos(); - const QTextCursor cursor = cursorForPosition(pos); - const RVA currentRVA = DisHlp::readDisassemblyOffset(cursor); - - if (currentRVA != RVA_INVALID) { - disasmWidget->updateSelectionPos(cursor); - } - - if (pos.y() < 0) { - disasmWidget->scrollInstructions(-1, true); - } else if (pos.y() > viewport()->height()) { - disasmWidget->scrollInstructions(1, true); - } else if (currentRVA != RVA_INVALID) { - disasmWidget->refreshDisasm(RVA_INVALID, RefreshMode::None); - } - - return; - } - - QPlainTextEdit::mouseMoveEvent(event); -} - -void DisassemblyTextEdit::paintEvent(QPaintEvent *event) -{ - QPlainTextEdit::paintEvent(event); - - if (cursorVisible) { - QPainter painter(viewport()); - QRect cRect = cursorRect(); - cRect.setWidth(2); - painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); - painter.fillRect(cRect, Qt::white); - } -} - void DisassemblyWidget::seekPrev() { Core()->seekPrev(); @@ -1435,27 +925,15 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) cursor.movePosition(QTextCursor::Down); } - const int startIndex = disas->getStartIndex(); - const int endIndex = qMin(static_cast(lines.size()), disas->getEndIndex()); - const int maxLines = endIndex - startIndex; - - // Capture how many lines are ACTUALLY drawn on the screen right now - const int visibleCount = qMin(maxLines, static_cast(lines.size()) - startIndex); - if (visibleCount <= 0) { - return; - } - using LineInfo = std::pair; std::vector lineOffsets; - lineOffsets.reserve(visibleCount + arrows.size()); + lineOffsets.reserve(lines.size() + arrows.size()); - RVA minViewOffset = lines[startIndex].offset; - RVA maxViewOffset = minViewOffset; + RVA minViewOffset = 0, maxViewOffset = 0; + minViewOffset = maxViewOffset = lines[0].offset; - for (int i = startIndex; i < endIndex; i++) { - // Map offset to a viewport-relative index (0 to maxLines) instead of absolute - // buffer index 'i' - lineOffsets.emplace_back(lines[i].offset, i - startIndex); + for (int i = 0; i < lines.size(); i++) { + lineOffsets.emplace_back(lines[i].offset, i); minViewOffset = std::min(minViewOffset, lines[i].offset); maxViewOffset = std::max(maxViewOffset, lines[i].offset); if (lines[i].arrow != RVA_INVALID) { @@ -1499,7 +977,7 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) [&](const LineInfo &line) { return line.first > maxViewOffset; }) - lineOffsets.begin(); for (size_t i = firstLineAfter; i < lineOffsets.size(); i++) { - lineOffsets[i].second = visibleCount + (i - firstLineAfter) + lineOffsets[i].second = lines.size() + (i - firstLineAfter) + 1; // +1 to ensure end of arrrow is drawn outside screen } @@ -1519,11 +997,12 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) return res->second; }; - std::sort(std::begin(arrows), std::end(arrows), [](const Arrow &l, const Arrow &r) { - const auto lLen = l.length(); - const auto rLen = r.length(); - if (lLen != rLen) { - return lLen < rLen; + auto fitsInScreen = [&](const Arrow &a) { return maxViewOffset - minViewOffset < a.length(); }; + + std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow &l, const Arrow &r) { + const int lScreen = fitsInScreen(l), rScreen = fitsInScreen(r); + if (lScreen != rScreen) { + return lScreen < rScreen; } return l.max != r.max ? l.max < r.max : l.min > r.min; }); @@ -1558,11 +1037,7 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) const RVA currOffset = disas->getSeekable()->getOffset(); const qreal pixelRatio = qhelpers::devicePixelRatio(p.device()); - - const int targetEndIndex = - qMin(static_cast(lines.size() - 1), startIndex + visibleCount - 1); - const Arrow visibleRange { lines.at(startIndex).offset, lines.at(targetEndIndex).offset }; - + const Arrow visibleRange { lines.first().offset, lines.last().offset }; // Draw the lines for (const auto &arrow : arrows) { if (!visibleRange.intersects(arrow)) { diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index a83b261f..abd6d99b 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -19,8 +19,6 @@ class DisassemblyContextMenu; class DisassemblyLeftPanel; class AddressRangeScrollBar; -enum class RefreshMode : ut8 { Append, Prepend, Reset, Keep, None }; - /** * @brief Main widget for showing disassembly of a binary * @@ -35,67 +33,48 @@ public: static QString getWidgetType(); - QFontMetricsF getFontMetrics(); - QList getLines(); - - int getStartIndex() const; - int getEndIndex() const; - - /** - * @brief Updates the offset and character position where the cursor selection ends - */ - void updateSelectionPos(const QTextCursor &cursor); - - /** - * @brief Updates the offset and character position where the cursor selection starts - */ - void updateSelectionAnchor(const QTextCursor &cursor); - public slots: /** * @brief Highlights the currently selected line and updates the * highlighting of the same words under the cursor in the visible screen. - * @return List of selections to be highlighted + * This overrides all previous highlighting. */ - QList highlightCurrentLine(); + void highlightCurrentLine(); /** * @brief Adds the PC line highlighting to the other current highlighting. - * This is generally called after highlightCurrentLine - * @return List of selections to be highlighted + * This should be called after highlightCurrentLine since that function + * overrides all previous highlighting. */ - QList highlightPCLine(); + void highlightPCLine(); void showDisasContextMenu(const QPoint &pt); void fontsUpdatedSlot(); void colorsUpdatedSlot(); void scrollInstructions(int count, bool clampToScrollBarRange = false); void seekPrev(); void setPreviewMode(bool previewMode); + QFontMetricsF getFontMetrics(); + QList getLines(); /** * @brief Forces the transient vertical scrollbar to appear on scroll */ void showTransientScrollBar(); - - void refreshDisasm(RVA offset = RVA_INVALID, RefreshMode mode = RefreshMode::Reset); - protected slots: void onSeekChanged(RVA offset, CutterCore::SeekHistoryType type); void refreshIfInRange(RVA offset); void instructionChanged(RVA offset); + void refreshDisasm(RVA offset = RVA_INVALID); bool updateMaxLines(); void cursorPositionChanged(); - /** - * @brief Copies the currently highlighted disassembly text to the system clipboard - */ - void copySelection(); protected: DisassemblyContextMenu *mCtxMenu; DisassemblyScrollArea *mDisasScrollArea; DisassemblyTextEdit *mDisasTextEdit; DisassemblyLeftPanel *leftPanel; + QList lines; private: RVA topOffset; @@ -122,31 +101,7 @@ private: int topOffsetHistoryPos = 0; QList topOffsetHistory; - int startIndex = 0; - int endIndex = 0; - QList lines; - - // Cursor selection related - RVA selectionAnchorRVA = RVA_INVALID; - /** - * @brief metadata lines attached to instruction have the same offset as the instruction itself, - * this keeps track of which metdata line the cursor was at inside the offset block - */ - int selectionAnchorSubIndex = 0; - int selectionAnchorChar = 0; - RVA selectionPosRVA = RVA_INVALID; - /** - * @brief same use-case as @ref selectionAnchorSubIndex but for cursor position instead if - * anchor - */ - int selectionPosSubIndex = 0; - int selectionPosChar = 0; - QList breakpoints; - /** - * @brief Set whenever breakpoints have been updated or the screen has been manually refreshed - */ - bool breakpointsDirty = true; void setupFonts(); void setupColors(); @@ -155,53 +110,9 @@ private: void connectCursorPositionChanged(bool disconnect); - void moveCursorRelative(QTextCursor::MoveOperation op, bool page); + void moveCursorRelative(bool up, bool page); void jumpToOffsetUnderCursor(const QTextCursor &); - - /** - * @brief Visually highlights the text on screen between the selection start (anchor) and the - * current cursor position - */ - void updateSelection(); - void updateContextMenuSelection(bool hasSelection); - - /** - * @brief Finds the visual line number in the current view for a specific offset - * @param offset The memory address (RVA) to look for - * @param offsetSubIndex The specific sub-line to target if the instructions has metadata - * attached to it - * @return The index of the line, or -1 if not found - * - * @see DisassemblyHelper::getIndexInOffsetGroup() - */ - int getLineIndex(RVA offset, int offsetSubIndex) const; - /** - * @brief Refreshes the background colors in the disassembly view. - * It applies highlights to both the line currently under the user's cursor and the Program - * Counter (PC) line. - */ - void updateLineHighlights(); - /** - * @brief Clears the current text selection so nothing is highlighted as selected - */ - void invalidateCursorSelection(); - - /** - * @brief Metadata lines attached to an instruction have the same offset saved with it as the - * instruction, This function returns the index of the current line within that offset - * group/block - * * e.g: - * * @code - * ; a comment - * ; another comment <------ assume cursor is here - * ; void func1() - * 0x1000 mov rax, rax - * @endcode - * - * Then the returned index is "1" - */ - int getIndexInOffsetGroup(const QTextCursor &cursor) const; }; class DisassemblyScrollArea : public QAbstractScrollArea @@ -231,30 +142,23 @@ class DisassemblyTextEdit : public QPlainTextEdit Q_OBJECT public: - explicit DisassemblyTextEdit(DisassemblyWidget *disasmWidget = nullptr); + explicit DisassemblyTextEdit(QWidget *parent = nullptr) + : QPlainTextEdit(parent), lockScroll(false) + { + } void setLockScroll(bool lock) { this->lockScroll = lock; } qreal textOffset() const; - void setCursorVisible(bool visible); - protected: bool viewportEvent(QEvent *event) override; void scrollContentsBy(int dx, int dy) override; void keyPressEvent(QKeyEvent *event) override; void mousePressEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - - void mouseMoveEvent(QMouseEvent *event) override; private: bool lockScroll; - QTimer *blinkTimer; - bool cursorVisible = true; - QColor cursorColor; - - DisassemblyWidget *disasmWidget = nullptr; }; /** diff --git a/src/widgets/GraphView.cpp b/src/widgets/GraphView.cpp index e7c6ba11..2aa66a9f 100644 --- a/src/widgets/GraphView.cpp +++ b/src/widgets/GraphView.cpp @@ -349,16 +349,13 @@ void GraphView::paint(QPainter &p, QPoint offset, QRect viewport, qreal scale, b p.setBrush(Qt::gray); + // Always draw edges + // TODO: Only draw edges if they are actually visible ... // Draw edges for (GraphEdge &edge : block.edges) { if (edge.polyline.empty()) { continue; } - // Skips expensive polyline and arrow rendering if the edge's bounding box - // is completely outside the current viewport. - if (!edge.polyline.boundingRect().adjusted(-1, -1, 1, 1).intersects(windowF)) { - continue; - } const QPolygonF polyline = edge.polyline; const EdgeConfiguration ec = edgeConfiguration(block, &blocks[edge.target], interactive);