diff options
| author | Jordan Wiens <github@psifertex.com> | 2025-12-19 15:57:20 -0500 |
|---|---|---|
| committer | Jordan Wiens <github@psifertex.com> | 2025-12-19 16:05:02 -0500 |
| commit | 34622adc292cb10b84c7984fc0cef966ee2c19b7 (patch) | |
| tree | b08efae7118bbc20ffbd4f0b4a25bb8ae3d7e42a /examples | |
| parent | dbdf3381e2dd32527cffaca6081fc5a9913f1cc7 (diff) | |
fix strings missing race condition
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/triage/exports.cpp | 11 | ||||
| -rw-r--r-- | examples/triage/exports.h | 2 | ||||
| -rw-r--r-- | examples/triage/strings.cpp | 140 | ||||
| -rw-r--r-- | examples/triage/strings.h | 36 |
4 files changed, 179 insertions, 10 deletions
diff --git a/examples/triage/exports.cpp b/examples/triage/exports.cpp index c9f0e42d..776044c5 100644 --- a/examples/triage/exports.cpp +++ b/examples/triage/exports.cpp @@ -32,7 +32,7 @@ GenericExportsModel::GenericExportsModel(QWidget* parent, BinaryViewRef data): Q connect(m_updateTimer, &QTimer::timeout, this, &GenericExportsModel::updateModel); connect(this, &GenericExportsModel::updateTimerOnUIThread, this, [=, this]() { updateTimer(m_needsUpdate); - }); + }, Qt::QueuedConnection); m_data->RegisterNotification(this); @@ -260,19 +260,26 @@ void GenericExportsModel::updateTimer(bool needsUpdate) void GenericExportsModel::pauseUpdates() { m_updatesPaused = true; + m_dirtyWhilePaused = false; setNeedsUpdate(false); } void GenericExportsModel::resumeUpdates() { m_updatesPaused = false; - setNeedsUpdate(true); + // Only refresh if we got notifications while paused + if (m_dirtyWhilePaused.exchange(false)) + setNeedsUpdate(true); } void GenericExportsModel::onBinaryViewNotification() { if (m_updatesPaused) + { + // Track that updates occurred while hidden + m_dirtyWhilePaused = true; return; + } // This can be called from any thread so we cannot directly // update the timer. Emitting a signal is relatively expensive diff --git a/examples/triage/exports.h b/examples/triage/exports.h index aeca67e0..af0f4613 100644 --- a/examples/triage/exports.h +++ b/examples/triage/exports.h @@ -22,6 +22,8 @@ class GenericExportsModel : public QAbstractItemModel, public BinaryNinja::Binar std::atomic<bool> m_updatesPaused = false; // Read/written from arbitrary threads while processing notifications. std::atomic<bool> m_needsUpdate = true; + // Tracks if notifications arrived while paused + std::atomic<bool> m_dirtyWhilePaused = false; void performSort(int col, Qt::SortOrder order); void updateModel(); diff --git a/examples/triage/strings.cpp b/examples/triage/strings.cpp index a5661139..32d9ff97 100644 --- a/examples/triage/strings.cpp +++ b/examples/triage/strings.cpp @@ -11,14 +11,29 @@ #include "fontsettings.h" -GenericStringsModel::GenericStringsModel(QWidget* parent, BinaryViewRef data) : QAbstractItemModel(parent) +GenericStringsModel::GenericStringsModel(QWidget* parent, BinaryViewRef data) : QAbstractItemModel(parent), BinaryDataNotification(StringUpdates) { m_data = data; m_totalCols = 3; m_sortCol = 0; m_sortOrder = Qt::AscendingOrder; - m_allEntries = data->GetStrings(); - m_entries = m_allEntries; + + m_updateTimer = new QTimer(this); + m_updateTimer->setInterval(500); + connect(m_updateTimer, &QTimer::timeout, this, &GenericStringsModel::updateModel); + connect(this, &GenericStringsModel::updateTimerOnUIThread, this, [=, this]() { + updateTimer(m_needsUpdate); + }, Qt::QueuedConnection); + + m_data->RegisterNotification(this); + + updateModel(); +} + + +GenericStringsModel::~GenericStringsModel() +{ + m_data->UnregisterNotification(this); } @@ -168,22 +183,119 @@ void GenericStringsModel::sort(int col, Qt::SortOrder order) } -void GenericStringsModel::setFilter(const std::string& filterText) +void GenericStringsModel::applyFilter() { - beginResetModel(); m_entries.clear(); for (auto& entry : m_allEntries) { auto s = stringRefToQString(entry).toStdString(); - - if (FilteredView::match(s, filterText)) + + if (FilteredView::match(s, m_filter)) m_entries.push_back(entry); } performSort(m_sortCol, m_sortOrder); +} + + +void GenericStringsModel::setFilter(const std::string& filterText) +{ + m_filter = filterText; + beginResetModel(); + applyFilter(); endResetModel(); } +void GenericStringsModel::updateModel() +{ + if (!m_needsUpdate) + return; + + setNeedsUpdate(false); + beginResetModel(); + m_allEntries = m_data->GetStrings(); + applyFilter(); + endResetModel(); +} + + +void GenericStringsModel::setNeedsUpdate(bool needed) +{ + if (m_needsUpdate.exchange(needed) == needed) + return; + + updateTimer(needed); +} + + +void GenericStringsModel::updateTimer(bool needsUpdate) +{ + if (needsUpdate && !m_updateTimer->isActive()) + m_updateTimer->start(); + if (!needsUpdate && m_updateTimer->isActive()) + m_updateTimer->stop(); +} + + +void GenericStringsModel::pauseUpdates() +{ + m_updatesPaused = true; + m_dirtyWhilePaused = false; + setNeedsUpdate(false); +} + + +void GenericStringsModel::resumeUpdates() +{ + m_updatesPaused = false; + // Only refresh if we got notifications while paused + if (m_dirtyWhilePaused.exchange(false)) + setNeedsUpdate(true); +} + + +void GenericStringsModel::onBinaryViewNotification() +{ + if (m_updatesPaused) + { + // Track that updates occurred while hidden + m_dirtyWhilePaused = true; + 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 GenericStringsModel::OnStringFound(BinaryNinja::BinaryView* view, BNStringType type, uint64_t offset, size_t len) +{ + onBinaryViewNotification(); +} + + +void GenericStringsModel::OnStringRemoved(BinaryNinja::BinaryView* view, BNStringType type, uint64_t offset, size_t len) +{ + onBinaryViewNotification(); +} + + +void GenericStringsModel::OnDerivedStringFound(BinaryNinja::BinaryView* view, const BinaryNinja::DerivedString& str) +{ + onBinaryViewNotification(); +} + + +void GenericStringsModel::OnDerivedStringRemoved(BinaryNinja::BinaryView* view, const BinaryNinja::DerivedString& str) +{ + onBinaryViewNotification(); +} + + StringsTreeView::StringsTreeView(StringsWidget* parent, TriageView* view, BinaryViewRef data) : QTreeView(parent) { m_data = data; @@ -362,6 +474,20 @@ void StringsTreeView::keyPressEvent(QKeyEvent* event) } +void StringsTreeView::showEvent(QShowEvent* event) +{ + QTreeView::showEvent(event); + m_model->resumeUpdates(); +} + + +void StringsTreeView::hideEvent(QHideEvent* event) +{ + QTreeView::hideEvent(event); + m_model->pauseUpdates(); +} + + StringsWidget::StringsWidget(QWidget* parent, TriageView* view, BinaryViewRef data) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); diff --git a/examples/triage/strings.h b/examples/triage/strings.h index 14762b34..31617f08 100644 --- a/examples/triage/strings.h +++ b/examples/triage/strings.h @@ -1,21 +1,43 @@ #pragma once #include <QtCore/QAbstractItemModel> +#include <QtCore/QTimer> #include <QtWidgets/QTreeView> #include "filter.h" -class GenericStringsModel : public QAbstractItemModel +class GenericStringsModel : public QAbstractItemModel, public BinaryNinja::BinaryDataNotification { + Q_OBJECT + BinaryViewRef m_data; std::vector<BNStringReference> m_allEntries, m_entries; int m_totalCols, m_sortCol; Qt::SortOrder m_sortOrder; + std::string m_filter; + 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; + // Tracks if notifications arrived while paused + std::atomic<bool> m_dirtyWhilePaused = false; void performSort(int col, Qt::SortOrder order); + void updateModel(); + void applyFilter(); + + void updateTimer(bool); + void setNeedsUpdate(bool); + void onBinaryViewNotification(); + +signals: + void updateTimerOnUIThread(); public: GenericStringsModel(QWidget* parent, BinaryViewRef data); + virtual ~GenericStringsModel(); virtual int columnCount(const QModelIndex& parent) const override; virtual int rowCount(const QModelIndex& parent) const override; @@ -26,8 +48,16 @@ class GenericStringsModel : public QAbstractItemModel virtual void sort(int col, Qt::SortOrder order) override; void setFilter(const std::string& filterText); + void pauseUpdates(); + void resumeUpdates(); + BNStringReference getStringRefAt(const QModelIndex& index) const; QString stringRefToQString(const BNStringReference& index) const; + + virtual void OnStringFound(BinaryNinja::BinaryView* data, BNStringType type, uint64_t offset, size_t len) override; + virtual void OnStringRemoved(BinaryNinja::BinaryView* data, BNStringType type, uint64_t offset, size_t len) override; + virtual void OnDerivedStringFound(BinaryNinja::BinaryView* data, const BinaryNinja::DerivedString& str) override; + virtual void OnDerivedStringRemoved(BinaryNinja::BinaryView* data, const BinaryNinja::DerivedString& str) override; }; @@ -36,6 +66,8 @@ class StringsWidget; class StringsTreeView : public QTreeView, public FilterTarget { + Q_OBJECT + BinaryViewRef m_data; StringsWidget* m_parent; TriageView* m_view; @@ -59,6 +91,8 @@ class StringsTreeView : public QTreeView, public FilterTarget protected: virtual void keyPressEvent(QKeyEvent* event) override; virtual bool event(QEvent* event) override; + virtual void showEvent(QShowEvent* event) override; + virtual void hideEvent(QHideEvent* event) override; private Q_SLOTS: void stringSelected(const QModelIndex& cur, const QModelIndex& prev); |
