summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Rowe <mark@vector35.com>2025-07-09 13:36:58 -0700
committerMark Rowe <mark@vector35.com>2025-07-10 16:08:11 -0700
commitebac408aec4bbeab7db3d6336520f5ea2a033a2c (patch)
treecc0145ce5392989c8a826e1076404120c63ac264
parentd252eb03954d254275ee7981070d8071795073ac (diff)
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.
-rw-r--r--examples/triage/exports.cpp68
-rw-r--r--examples/triage/exports.h23
2 files changed, 70 insertions, 21 deletions
diff --git a/examples/triage/exports.cpp b/examples/triage/exports.cpp
index 4d274b1a..04547144 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 6ab94407..96c2b779 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<SymbolRef> 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<bool> m_updatesPaused = false;
+ // Read/written from arbitrary threads while processing notifications.
+ std::atomic<bool> 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);