From ebac408aec4bbeab7db3d6336520f5ea2a033a2c Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 9 Jul 2025 13:36:58 -0700 Subject: [PATCH] Fix the Triage Summary view's exports list to only update when visible The `ExportsTreeView` now asks the model to pause / resume updates when its visibility changes. When paused, all notifications from the view are ignored. When resumed, notifications set a flag to indicate that an update is needed and resume the update timer if it is not already active. The timer is stopped after an update is processed. There's some extra complexity here to avoid emitting a signal for every `BinaryView` notification that is processed. These notifications are typically generated on background threads. The overhead of emitting a signal and it being routed to the main thread adds up given the number of notifications involved when loading a large binary. --- examples/triage/exports.cpp | 68 +++++++++++++++++++++++++++++-------- examples/triage/exports.h | 23 +++++++++---- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/examples/triage/exports.cpp b/examples/triage/exports.cpp index 4d274b1a22..0454714436 100644 --- a/examples/triage/exports.cpp +++ b/examples/triage/exports.cpp @@ -25,13 +25,10 @@ GenericExportsModel::GenericExportsModel(QWidget* parent, BinaryViewRef data): Q } m_updateTimer = new QTimer(this); - m_updateTimer->setSingleShot(true); m_updateTimer->setInterval(500); connect(m_updateTimer, &QTimer::timeout, this, &GenericExportsModel::updateModel); - connect(this, &GenericExportsModel::modelUpdate, this, [=, this]() { - if (m_updateTimer->isActive()) - return; - m_updateTimer->start(); + connect(this, &GenericExportsModel::updateTimerOnUIThread, this, [=, this]() { + updateTimer(m_needsUpdate); }); m_data->RegisterNotification(this); @@ -49,6 +46,10 @@ GenericExportsModel::~GenericExportsModel() void GenericExportsModel::updateModel() { + if (!m_needsUpdate) + return; + + setNeedsUpdate(false); beginResetModel(); m_allEntries.clear(); for (auto& sym : m_data->GetSymbolsOfType(FunctionSymbol)) @@ -237,40 +238,64 @@ void GenericExportsModel::setFilter(const std::string& filterText) endResetModel(); } - -void GenericExportsModel::OnAnalysisFunctionAdded(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) +void GenericExportsModel::setNeedsUpdate(bool needed) { - emit modelUpdate(); + if (m_needsUpdate.exchange(needed) == needed) + return; + + updateTimer(needed); } +void GenericExportsModel::updateTimer(bool needsUpdate) +{ + if (needsUpdate && !m_updateTimer->isActive()) + m_updateTimer->start(); + if (!needsUpdate && m_updateTimer->isActive()) + m_updateTimer->stop(); +} -void GenericExportsModel::OnAnalysisFunctionRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) +void GenericExportsModel::pauseUpdates() { - emit modelUpdate(); + m_updatesPaused = true; + setNeedsUpdate(false); } +void GenericExportsModel::resumeUpdates() +{ + m_updatesPaused = false; + setNeedsUpdate(true); +} -void GenericExportsModel::OnAnalysisFunctionUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) +void GenericExportsModel::onBinaryViewNotification() { - emit modelUpdate(); + if (m_updatesPaused) + return; + + // This can be called from any thread so we cannot directly + // update the timer. Emitting a signal is relatively expensive + // given how frequently we receive notifications, so we only + // emit a signal if we didn't already need an update. + if (!m_needsUpdate.exchange(true)) + emit updateTimerOnUIThread(); } void GenericExportsModel::OnSymbolAdded(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { - emit modelUpdate(); + if ((sym->GetBinding() == GlobalBinding) || (sym->GetBinding() == WeakBinding)) + onBinaryViewNotification(); } void GenericExportsModel::OnSymbolUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { - emit modelUpdate(); + onBinaryViewNotification(); } void GenericExportsModel::OnSymbolRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { - emit modelUpdate(); + onBinaryViewNotification(); } @@ -403,6 +428,19 @@ void ExportsTreeView::keyPressEvent(QKeyEvent* event) QTreeView::keyPressEvent(event); } +void ExportsTreeView::showEvent(QShowEvent* event) +{ + QTreeView::showEvent(event); + m_model->resumeUpdates(); +} + + +void ExportsTreeView::hideEvent(QHideEvent* event) +{ + QTreeView::hideEvent(event); + m_model->pauseUpdates(); +} + ExportsWidget::ExportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data) : QWidget(parent) { diff --git a/examples/triage/exports.h b/examples/triage/exports.h index 6ab94407e2..96c2b77978 100644 --- a/examples/triage/exports.h +++ b/examples/triage/exports.h @@ -13,16 +13,25 @@ class GenericExportsModel : public QAbstractItemModel, public BinaryNinja::Binar BinaryViewRef m_data; std::vector m_allEntries, m_entries; std::string m_filter; + QTimer* m_updateTimer; Qt::SortOrder m_sortOrder; int m_sortCol; bool m_hasOrdinals; - QTimer* m_updateTimer; + + // Read from arbitrary threads while processing notifications. + std::atomic m_updatesPaused = false; + // Read/written from arbitrary threads while processing notifications. + std::atomic m_needsUpdate = true; void performSort(int col, Qt::SortOrder order); void updateModel(); - signals: - void modelUpdate(); + void updateTimer(bool); + void setNeedsUpdate(bool); + void onBinaryViewNotification(); + +signals: + void updateTimerOnUIThread(); public: GenericExportsModel(QWidget* parent, BinaryViewRef data); @@ -37,11 +46,11 @@ class GenericExportsModel : public QAbstractItemModel, public BinaryNinja::Binar virtual void sort(int col, Qt::SortOrder order) override; void setFilter(const std::string& filterText); + void pauseUpdates(); + void resumeUpdates(); + SymbolRef getSymbol(const QModelIndex& index); - virtual void OnAnalysisFunctionAdded(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override; - virtual void OnAnalysisFunctionRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override; - virtual void OnAnalysisFunctionUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override; virtual void OnSymbolAdded(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override; virtual void OnSymbolUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override; virtual void OnSymbolRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override; @@ -75,6 +84,8 @@ class ExportsTreeView : public QTreeView, public FilterTarget protected: virtual void keyPressEvent(QKeyEvent* event) override; + virtual void showEvent(QShowEvent* event) override; + virtual void hideEvent(QHideEvent* event) override; private Q_SLOTS: void exportSelected(const QModelIndex& cur, const QModelIndex& prev);