Skip to content

Commit ebac408

Browse files
committed
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.
1 parent d252eb0 commit ebac408

File tree

2 files changed

+70
-21
lines changed

2 files changed

+70
-21
lines changed

examples/triage/exports.cpp

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,10 @@ GenericExportsModel::GenericExportsModel(QWidget* parent, BinaryViewRef data): Q
2525
}
2626

2727
m_updateTimer = new QTimer(this);
28-
m_updateTimer->setSingleShot(true);
2928
m_updateTimer->setInterval(500);
3029
connect(m_updateTimer, &QTimer::timeout, this, &GenericExportsModel::updateModel);
31-
connect(this, &GenericExportsModel::modelUpdate, this, [=, this]() {
32-
if (m_updateTimer->isActive())
33-
return;
34-
m_updateTimer->start();
30+
connect(this, &GenericExportsModel::updateTimerOnUIThread, this, [=, this]() {
31+
updateTimer(m_needsUpdate);
3532
});
3633

3734
m_data->RegisterNotification(this);
@@ -49,6 +46,10 @@ GenericExportsModel::~GenericExportsModel()
4946

5047
void GenericExportsModel::updateModel()
5148
{
49+
if (!m_needsUpdate)
50+
return;
51+
52+
setNeedsUpdate(false);
5253
beginResetModel();
5354
m_allEntries.clear();
5455
for (auto& sym : m_data->GetSymbolsOfType(FunctionSymbol))
@@ -237,40 +238,64 @@ void GenericExportsModel::setFilter(const std::string& filterText)
237238
endResetModel();
238239
}
239240

240-
241-
void GenericExportsModel::OnAnalysisFunctionAdded(BinaryNinja::BinaryView* view, BinaryNinja::Function* func)
241+
void GenericExportsModel::setNeedsUpdate(bool needed)
242242
{
243-
emit modelUpdate();
243+
if (m_needsUpdate.exchange(needed) == needed)
244+
return;
245+
246+
updateTimer(needed);
244247
}
245248

249+
void GenericExportsModel::updateTimer(bool needsUpdate)
250+
{
251+
if (needsUpdate && !m_updateTimer->isActive())
252+
m_updateTimer->start();
253+
if (!needsUpdate && m_updateTimer->isActive())
254+
m_updateTimer->stop();
255+
}
246256

247-
void GenericExportsModel::OnAnalysisFunctionRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Function* func)
257+
void GenericExportsModel::pauseUpdates()
248258
{
249-
emit modelUpdate();
259+
m_updatesPaused = true;
260+
setNeedsUpdate(false);
250261
}
251262

263+
void GenericExportsModel::resumeUpdates()
264+
{
265+
m_updatesPaused = false;
266+
setNeedsUpdate(true);
267+
}
252268

253-
void GenericExportsModel::OnAnalysisFunctionUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Function* func)
269+
void GenericExportsModel::onBinaryViewNotification()
254270
{
255-
emit modelUpdate();
271+
if (m_updatesPaused)
272+
return;
273+
274+
// This can be called from any thread so we cannot directly
275+
// update the timer. Emitting a signal is relatively expensive
276+
// given how frequently we receive notifications, so we only
277+
// emit a signal if we didn't already need an update.
278+
if (!m_needsUpdate.exchange(true))
279+
emit updateTimerOnUIThread();
256280
}
257281

258282

259283
void GenericExportsModel::OnSymbolAdded(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym)
260284
{
261-
emit modelUpdate();
285+
if ((sym->GetBinding() == GlobalBinding) || (sym->GetBinding() == WeakBinding))
286+
onBinaryViewNotification();
262287
}
263288

264289

265290
void GenericExportsModel::OnSymbolUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym)
266291
{
267-
emit modelUpdate();
292+
onBinaryViewNotification();
268293
}
269294

270295

271296
void GenericExportsModel::OnSymbolRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym)
272297
{
273-
emit modelUpdate();
298+
onBinaryViewNotification();
274299
}
275300

276301

@@ -403,6 +428,19 @@ void ExportsTreeView::keyPressEvent(QKeyEvent* event)
403428
QTreeView::keyPressEvent(event);
404429
}
405430

431+
void ExportsTreeView::showEvent(QShowEvent* event)
432+
{
433+
QTreeView::showEvent(event);
434+
m_model->resumeUpdates();
435+
}
436+
437+
438+
void ExportsTreeView::hideEvent(QHideEvent* event)
439+
{
440+
QTreeView::hideEvent(event);
441+
m_model->pauseUpdates();
442+
}
443+
406444

407445
ExportsWidget::ExportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data) : QWidget(parent)
408446
{

examples/triage/exports.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,25 @@ class GenericExportsModel : public QAbstractItemModel, public BinaryNinja::Binar
1313
BinaryViewRef m_data;
1414
std::vector<SymbolRef> m_allEntries, m_entries;
1515
std::string m_filter;
16+
QTimer* m_updateTimer;
1617
Qt::SortOrder m_sortOrder;
1718
int m_sortCol;
1819
bool m_hasOrdinals;
19-
QTimer* m_updateTimer;
20+
21+
// Read from arbitrary threads while processing notifications.
22+
std::atomic<bool> m_updatesPaused = false;
23+
// Read/written from arbitrary threads while processing notifications.
24+
std::atomic<bool> m_needsUpdate = true;
2025

2126
void performSort(int col, Qt::SortOrder order);
2227
void updateModel();
2328

24-
signals:
25-
void modelUpdate();
29+
void updateTimer(bool);
30+
void setNeedsUpdate(bool);
31+
void onBinaryViewNotification();
32+
33+
signals:
34+
void updateTimerOnUIThread();
2635

2736
public:
2837
GenericExportsModel(QWidget* parent, BinaryViewRef data);
@@ -37,11 +46,11 @@ class GenericExportsModel : public QAbstractItemModel, public BinaryNinja::Binar
3746
virtual void sort(int col, Qt::SortOrder order) override;
3847
void setFilter(const std::string& filterText);
3948

49+
void pauseUpdates();
50+
void resumeUpdates();
51+
4052
SymbolRef getSymbol(const QModelIndex& index);
4153

42-
virtual void OnAnalysisFunctionAdded(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override;
43-
virtual void OnAnalysisFunctionRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override;
44-
virtual void OnAnalysisFunctionUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) override;
4554
virtual void OnSymbolAdded(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override;
4655
virtual void OnSymbolUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override;
4756
virtual void OnSymbolRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) override;
@@ -75,6 +84,8 @@ class ExportsTreeView : public QTreeView, public FilterTarget
7584

7685
protected:
7786
virtual void keyPressEvent(QKeyEvent* event) override;
87+
virtual void showEvent(QShowEvent* event) override;
88+
virtual void hideEvent(QHideEvent* event) override;
7889

7990
private Q_SLOTS:
8091
void exportSelected(const QModelIndex& cur, const QModelIndex& prev);

0 commit comments

Comments
 (0)