fix: Crashes and usability issues with the pattern tree filter

This commit is contained in:
WerWolv 2025-12-01 19:35:22 +01:00
parent 8f57dd86af
commit 980438008c
No known key found for this signature in database
GPG Key ID: B5B84C2ACF708377
5 changed files with 165 additions and 51 deletions

@ -1 +1 @@
Subproject commit 7b2a0523ee8bb235cb1a8591eef969095e6c4fe2
Subproject commit 32394f789a81a5401d905689a5ea52a6634005f5

View File

@ -85,7 +85,7 @@ namespace hex::ui {
void drawColorColumn(const pl::ptrn::Pattern& pattern);
void drawCommentColumn(const pl::ptrn::Pattern& pattern);
bool beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<pl::ptrn::Pattern*> &sortedPatterns, float height) const;
bool beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<std::shared_ptr<pl::ptrn::Pattern>> &sortedPatterns, float height) const;
bool createTreeNode(const pl::ptrn::Pattern& pattern, bool leaf = false);
void createDefaultEntry(const pl::ptrn::Pattern &pattern);
void closeTreeNode(bool inlined) const;
@ -93,23 +93,28 @@ namespace hex::ui {
bool sortPatterns(const ImGuiTableSortSpecs* sortSpecs, const pl::ptrn::Pattern * left, const pl::ptrn::Pattern * right) const;
[[nodiscard]] bool isEditingPattern(const pl::ptrn::Pattern& pattern) const;
void resetEditing();
void traversePatternTree(pl::ptrn::Pattern &pattern, std::vector<std::string> &patternPath, const std::function<void(pl::ptrn::Pattern&)> &callback);
void traversePatternTree(const std::shared_ptr<pl::ptrn::Pattern> &pattern, std::vector<std::string> &patternPath, const std::function<void(const std::shared_ptr<pl::ptrn::Pattern>&)> &callback);
[[nodiscard]] std::string getDisplayName(const pl::ptrn::Pattern& pattern) const;
[[nodiscard]] std::vector<std::string> getPatternPath(const pl::ptrn::Pattern *pattern) const;
struct Filter {
std::vector<std::string> path;
bool inverted = false;
bool typeMatch = false;
std::strong_ordering operation = std::strong_ordering::equal;
std::optional<pl::core::Token::Literal> value;
};
[[nodiscard]] static bool matchesFilter(const std::vector<std::string> &filterPath, const std::vector<std::string> &patternPath, bool fullMatch);
[[nodiscard]] static std::optional<Filter> parseRValueFilter(const std::string &filter);
[[nodiscard]] static std::optional<Filter> parseComparison(const Filter &currFilter, std::string filterString);
void updateFilter();
private:
std::map<const pl::ptrn::Pattern*, u64> m_displayEnd;
std::vector<pl::ptrn::Pattern*> m_sortedPatterns;
std::vector<std::shared_ptr<pl::ptrn::Pattern>> m_sortedPatterns;
const pl::ptrn::Pattern *m_editingPattern = nullptr;
u64 m_editingPatternOffset = 0;
@ -125,8 +130,8 @@ namespace hex::ui {
std::set<pl::ptrn::Pattern*> m_visualizedPatterns;
std::string m_filterText;
Filter m_filter;
std::vector<pl::ptrn::Pattern*> m_filteredPatterns;
std::optional<Filter> m_filter;
std::vector<std::shared_ptr<pl::ptrn::Pattern>> m_filteredPatterns;
std::vector<std::string> m_currPatternPath;
std::map<std::vector<std::string>, std::shared_ptr<pl::ptrn::Pattern>> m_favorites;

View File

@ -32,6 +32,8 @@
#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <fonts/vscode_icons.hpp>
#include <hex/api/tutorial_manager.hpp>
#include <pl/core/ast/ast_node_mathematical_expression.hpp>
#include <wolv/io/file.hpp>
@ -197,6 +199,61 @@ namespace hex::ui {
}
std::optional<PatternDrawer::Filter> PatternDrawer::parseComparison(const Filter &currFilter, std::string filterString) {
auto result = currFilter;
pl::core::Lexer lexer;
filterString = wolv::util::trim(filterString);
if (filterString.empty())
return std::nullopt;
else if (filterString.starts_with("===")) {
result.operation = std::strong_ordering::equal;
result.inverted = false;
result.typeMatch = true;
filterString = filterString.substr(3);
} else if (filterString.starts_with("==")) {
result.operation = std::strong_ordering::equal;
result.inverted = false;
filterString = filterString.substr(2);
} else if (filterString.starts_with("!=")) {
result.operation = std::strong_ordering::equal;
result.inverted = true;
filterString = filterString.substr(2);
} else if (filterString.starts_with(">=")) {
result.operation = std::strong_ordering::less;
result.inverted = true;
filterString = filterString.substr(2);
} else if (filterString.starts_with("<=")) {
result.operation = std::strong_ordering::greater;
result.inverted = true;
filterString = filterString.substr(2);
} else if (filterString.starts_with(">")) {
result.operation = std::strong_ordering::greater;
result.inverted = false;
filterString = filterString.substr(1);
} else if (filterString.starts_with("<")) {
result.operation = std::strong_ordering::less;
result.inverted = false;
filterString = filterString.substr(1);
} else {
return std::nullopt;
}
pl::api::Source source(filterString);
auto tokens = lexer.lex(&source);
if (!tokens.isOk() || tokens.unwrap().size() != 2)
return std::nullopt;
auto literal = std::get_if<pl::core::Token::Literal>(&tokens.unwrap().front().value);
if (literal == nullptr)
return std::nullopt;
result.value = *literal;
return result;
}
std::optional<PatternDrawer::Filter> PatternDrawer::parseRValueFilter(const std::string &filter) {
Filter result;
@ -208,30 +265,30 @@ namespace hex::ui {
for (size_t i = 0; i < filter.size(); i += 1) {
char c = filter[i];
if (i < filter.size() - 1 && c == '=' && filter[i + 1] == '=') {
pl::core::Lexer lexer;
pl::api::Source source(filter.substr(i + 2));
auto tokens = lexer.lex(&source);
if (!tokens.isOk() || tokens.unwrap().size() != 2)
if (c == '.') {
if (result.path.back().empty())
return std::nullopt;
auto literal = std::get_if<pl::core::Token::Literal>(&tokens.unwrap().front().value);
if (literal == nullptr)
return std::nullopt;
result.value = *literal;
break;
} else if (c == '.') {
result.path.emplace_back();
} else if (c == '*') {
if (!result.path.back().empty())
return std::nullopt;
result.path.back() = "*";
} else if (c == '[') {
result.path.emplace_back();
result.path.back() += c;
} else if (c == ']') {
result.path.back() += c;
} else if (c == ' ') {
// Skip whitespace
} else {
} else if (std::isalnum(c) || c == '_') {
if (result.path.back() == "*")
return std::nullopt;
result.path.back() += c;
} else {
return parseComparison(result, filter.substr(i));
}
}
@ -241,7 +298,10 @@ namespace hex::ui {
void PatternDrawer::updateFilter() {
m_filteredPatterns.clear();
if (m_filter.path.empty()) {
if (!m_filter.has_value())
return;
if (m_filter->path.empty()) {
m_filteredPatterns = m_sortedPatterns;
return;
}
@ -251,13 +311,23 @@ namespace hex::ui {
if (m_filteredPatterns.size() > m_maxFilterDisplayItems)
break;
traversePatternTree(*pattern, treePath, [this, &treePath](auto &pattern) {
traversePatternTree(pattern, treePath, [this, &treePath](auto &pattern) {
if (m_filteredPatterns.size() > m_maxFilterDisplayItems)
return;
if (matchesFilter(m_filter.path, treePath, false)) {
if (!m_filter.value.has_value() || pattern.getValue() == m_filter.value)
m_filteredPatterns.push_back(&pattern);
if (matchesFilter(m_filter->path, treePath, false)) {
if (!m_filter->value.has_value()) {
m_filteredPatterns.push_back(pattern);
} else {
auto patternValue = pattern->getValue();
if (!m_filter->inverted && (patternValue <=> m_filter->value) == m_filter->operation) {
if (!m_filter->typeMatch || (m_filter->value->index() == patternValue.index()))
m_filteredPatterns.push_back(pattern);
} else if (m_filter->inverted && (patternValue <=> m_filter->value) != m_filter->operation) {
if (!m_filter->typeMatch || (m_filter->value->index() == patternValue.index()))
m_filteredPatterns.push_back(pattern);
}
}
}
});
}
@ -608,7 +678,7 @@ namespace hex::ui {
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&] (u64, auto *field) {
pattern.forEachEntry(0, pattern.getEntryCount(), [&] (u64, const auto &field) {
ImGui::PushID(id);
this->draw(*field);
ImGui::PopID();
@ -804,7 +874,7 @@ namespace hex::ui {
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member){
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, const auto &member){
ImGui::PushID(id);
this->draw(*member);
ImGui::PopID();
@ -849,7 +919,7 @@ namespace hex::ui {
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member) {
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, const auto &member) {
ImGui::PushID(id);
this->draw(*member);
ImGui::PopID();
@ -1051,7 +1121,7 @@ namespace hex::ui {
}
int id = 1;
iterable.forEachEntry(i, endIndex, [&](u64, auto *entry){
iterable.forEachEntry(i, endIndex, [&](u64, const auto &entry){
ImGui::PushID(id);
this->draw(*entry);
ImGui::PopID();
@ -1100,7 +1170,7 @@ namespace hex::ui {
return sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending ? result == std::strong_ordering::less : result == std::strong_ordering::greater;
}
bool PatternDrawer::beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<pl::ptrn::Pattern*> &sortedPatterns, float height) const {
bool PatternDrawer::beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<std::shared_ptr<pl::ptrn::Pattern>> &sortedPatterns, float height) const {
if (!ImGui::BeginTable("##Patterntable", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, height))) {
return false;
}
@ -1128,13 +1198,10 @@ namespace hex::ui {
}
if (!m_favoritesUpdateTask.isRunning()) {
sortedPatterns.clear();
std::transform(patterns.begin(), patterns.end(), std::back_inserter(sortedPatterns), [](const std::shared_ptr<pl::ptrn::Pattern> &pattern) {
return pattern.get();
});
sortedPatterns = patterns;
std::stable_sort(sortedPatterns.begin(), sortedPatterns.end(), [this, &sortSpecs](const pl::ptrn::Pattern *left, const pl::ptrn::Pattern *right) -> bool {
return this->sortPatterns(sortSpecs, left, right);
std::stable_sort(sortedPatterns.begin(), sortedPatterns.end(), [this, &sortSpecs](const std::shared_ptr<pl::ptrn::Pattern> &left, const std::shared_ptr<pl::ptrn::Pattern> &right) -> bool {
return this->sortPatterns(sortSpecs, left.get(), right.get());
});
for (auto &pattern : sortedPatterns) {
@ -1149,14 +1216,18 @@ namespace hex::ui {
return true;
}
void PatternDrawer::traversePatternTree(pl::ptrn::Pattern &pattern, std::vector<std::string> &patternPath, const std::function<void(pl::ptrn::Pattern&)> &callback) {
patternPath.push_back(pattern.getVariableName());
void PatternDrawer::traversePatternTree(const std::shared_ptr<pl::ptrn::Pattern> &pattern, std::vector<std::string> &patternPath, const std::function<void(const std::shared_ptr<pl::ptrn::Pattern>&)> &callback) {
patternPath.push_back(pattern->getVariableName());
ON_SCOPE_EXIT { patternPath.pop_back(); };
callback(pattern);
if (auto iterable = dynamic_cast<pl::ptrn::IIterable*>(&pattern); iterable != nullptr) {
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, pl::ptrn::Pattern *entry) {
traversePatternTree(*entry, patternPath, callback);
if (auto iterable = dynamic_cast<pl::ptrn::IIterable*>(pattern.get()); iterable != nullptr) {
// Don't index individual characters of strings
if (dynamic_cast<pl::ptrn::PatternString*>(pattern.get()) || dynamic_cast<pl::ptrn::PatternWideString*>(pattern.get()))
return;
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, const auto &entry) {
traversePatternTree(entry, patternPath, callback);
});
}
}
@ -1199,10 +1270,48 @@ namespace hex::ui {
auto &style = ImGui::GetStyle();
ImGui::PushItemWidth(-(style.ItemSpacing.x * 2 + style.WindowPadding.x * 2 + ImGui::GetTextLineHeightWithSpacing() * 5 + 15_scaled));
const bool filterError = !m_filterText.empty() && !m_filter.has_value();
if (filterError) {
ImGui::PushStyleColor(ImGuiCol_Border, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_LoggerError));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1_scaled);
}
if (ImGuiExt::InputTextIcon("##Search", ICON_VS_FILTER, m_filterText)) {
m_filter = parseRValueFilter(m_filterText).value_or(Filter{ });
auto newFilter = parseRValueFilter(m_filterText);
if (m_filterText.empty()) {
m_filter.reset();
updateFilter();
} else if (!newFilter.has_value()) {
m_filter.reset();
m_filteredPatterns.clear();
} else {
m_filter = newFilter;
updateFilter();
}
}
TutorialManager::setLastItemInteractiveHelpPopup([] {
ImGuiExt::TextFormattedWrapped("{}",
"Allows filtering of the pattern tree using the following syntax:\n\n"
"- variable_name: Shows all patterns with this name\n"
"- container.var: Shows all patterns named var that are inside a pattern called container\n"
"- a.*.b: Searches all children of pattern a for a pattern named b\n"
"- a[10].b: Shows patterns named b inside the 10th array entry of a pattern named a\n"
"- x > 10: Shows all patterns named x that have a value greater than 10\n"
"- a.b == 100: Shows all patterns named b with a value of 100 that are inside a pattern named a\n"
"- a == \"Hello\": Shows all patterns named a whose value is the string \"Hello\"\n\n"
"If nothing was found, the tree will be empty. If there was a parsing error, the filter text field will be highlighted in red."
);
});
if (filterError) {
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
ImGui::PopItemWidth();
ImGui::SameLine();
@ -1322,7 +1431,7 @@ namespace hex::ui {
m_showFavoriteStars = true;
for (auto &pattern : m_filter.path.empty() ? m_sortedPatterns : m_filteredPatterns) {
for (auto &pattern : m_filterText.empty() ? m_sortedPatterns : m_filteredPatterns) {
ImGui::PushID(id);
this->draw(*pattern);
ImGui::PopID();
@ -1372,14 +1481,14 @@ namespace hex::ui {
continue;
patternPath.clear();
traversePatternTree(*pattern, patternPath, [&, this](const pl::ptrn::Pattern &currPattern) {
traversePatternTree(pattern, patternPath, [&, this](const std::shared_ptr<pl::ptrn::Pattern> &currPattern) {
for (auto &[path, favoritePattern] : m_favorites) {
if (updatedFavorites == m_favorites.size())
task.interrupt();
task.update();
if (matchesFilter(patternPath, path, true)) {
favoritePattern = currPattern.clone();
favoritePattern = currPattern->clone();
updatedFavorites += 1;
break;

View File

@ -29,9 +29,9 @@ namespace hex::plugin::visualizers {
dataPoints.clear();
lastPoint = { 0, 0 };
bitfield->forEachEntry(0, bitfield->getEntryCount(), [&](u64, pl::ptrn::Pattern *entry) {
bitfield->forEachEntry(0, bitfield->getEntryCount(), [&](u64, const auto &entry) {
size_t bitSize;
if (const auto *bitfieldField = dynamic_cast<pl::ptrn::PatternBitfieldField*>(entry); bitfieldField != nullptr)
if (const auto *bitfieldField = dynamic_cast<pl::ptrn::PatternBitfieldField*>(entry.get()); bitfieldField != nullptr)
bitSize = bitfieldField->getBitSize();
else
bitSize = entry->getSize() * 8;

View File

@ -33,7 +33,7 @@ namespace hex::plugin::visualizers {
height = u64(arguments[2].toUnsigned());
auto iterable = dynamic_cast<pl::ptrn::IIterable*>(pattern.get());
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, pl::ptrn::Pattern *entry) {
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, const auto &entry) {
tableContent.push_back(entry->toString());
});
}