summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJordan Wiens <github@psifertex.com>2025-12-19 15:57:20 -0500
committerJordan Wiens <github@psifertex.com>2025-12-19 16:05:02 -0500
commit34622adc292cb10b84c7984fc0cef966ee2c19b7 (patch)
treeb08efae7118bbc20ffbd4f0b4a25bb8ae3d7e42a /examples
parentdbdf3381e2dd32527cffaca6081fc5a9913f1cc7 (diff)
fix strings missing race condition
Diffstat (limited to 'examples')
-rw-r--r--examples/triage/exports.cpp11
-rw-r--r--examples/triage/exports.h2
-rw-r--r--examples/triage/strings.cpp140
-rw-r--r--examples/triage/strings.h36
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);