// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "SymbolTreeModel.h" #include #include #include #include "common/Assertions.h" #include "TypeString.h" SymbolTreeModel::SymbolTreeModel(DebugInterface& cpu, QObject* parent) : QAbstractItemModel(parent) , m_cpu(cpu) { } QModelIndex SymbolTreeModel::index(int row, int column, const QModelIndex& parent) const { SymbolTreeNode* parent_node = nodeFromIndex(parent); if (!parent_node) return QModelIndex(); if (row < 0 || row >= (int)parent_node->children().size()) return QModelIndex(); const SymbolTreeNode* child_node = parent_node->children()[row].get(); if (!child_node) return QModelIndex(); return createIndex(row, column, child_node); } QModelIndex SymbolTreeModel::parent(const QModelIndex& index) const { if (!index.isValid()) return QModelIndex(); SymbolTreeNode* child_node = nodeFromIndex(index); if (!child_node) return QModelIndex(); const SymbolTreeNode* parent_node = child_node->parent(); if (!parent_node || parent_node == m_root.get()) return QModelIndex(); return indexFromNode(*parent_node); } int SymbolTreeModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) return 0; SymbolTreeNode* node = nodeFromIndex(parent); if (!node) return 0; return (int)node->children().size(); } int SymbolTreeModel::columnCount(const QModelIndex& parent) const { return COLUMN_COUNT; } bool SymbolTreeModel::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) return true; SymbolTreeNode* parent_node = nodeFromIndex(parent); if (!parent_node) return true; // If a node doesn't have a type, it can't generate any children, so all the // children that will exist must already be there. if (!parent_node->type.valid()) return !parent_node->children().empty(); bool result = true; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { const ccc::ast::Node* type = parent_node->type.lookup_node(database); if (!type) return; result = nodeHasChildren(*type, database); }); return result; } QVariant SymbolTreeModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); SymbolTreeNode* node = nodeFromIndex(index); if (!node) return QVariant(); if (role == Qt::ForegroundRole) { bool active = true; // Gray out the names of symbols that have been overwritten in memory. if (index.column() == NAME) active = node->matchesMemory(); // Gray out the values of variables that are dead. if (index.column() == VALUE && node->liveness().has_value()) active = *node->liveness(); QPalette::ColorGroup group = active ? QPalette::Active : QPalette::Disabled; return QBrush(QApplication::palette().color(group, QPalette::Text)); } if (role != Qt::DisplayRole) return QVariant(); switch (index.column()) { case NAME: { return node->name; } case VALUE: { if (node->tag != SymbolTreeNode::OBJECT) return QVariant(); return node->display_value(); } case LOCATION: { return node->location.toString(m_cpu).rightJustified(8); } case SIZE: { if (!node->size.has_value()) return QVariant(); return QString::number(*node->size); } case TYPE: { QVariant result; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { const ccc::ast::Node* type = node->type.lookup_node(database); if (!type) return; result = typeToString(type, database); }); return result; } case LIVENESS: { if (!node->liveness().has_value()) return QVariant(); return *node->liveness() ? tr("Alive") : tr("Dead"); } } return QVariant(); } bool SymbolTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; SymbolTreeNode* node = nodeFromIndex(index); if (!node) return false; bool data_changed = false; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { switch (role) { case EDIT_ROLE: data_changed = node->writeToVM(value, m_cpu, database); break; case UPDATE_FROM_MEMORY_ROLE: data_changed = node->readFromVM(m_cpu, database); break; } }); if (data_changed) emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(COLUMN_COUNT - 1)); return data_changed; } void SymbolTreeModel::fetchMore(const QModelIndex& parent) { if (!parent.isValid()) return; SymbolTreeNode* parent_node = nodeFromIndex(parent); if (!parent_node || !parent_node->type.valid()) return; if (!parent_node->children().empty()) return; std::vector> children; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { const ccc::ast::Node* logical_parent_type = parent_node->type.lookup_node(database); if (!logical_parent_type) return; children = populateChildren( parent_node->name, parent_node->location, *logical_parent_type, parent_node->type, m_cpu, database); }); bool insert_children = !children.empty(); if (insert_children) beginInsertRows(parent, 0, children.size() - 1); parent_node->setChildren(std::move(children)); if (insert_children) endInsertRows(); } bool SymbolTreeModel::canFetchMore(const QModelIndex& parent) const { if (!parent.isValid()) return false; SymbolTreeNode* parent_node = nodeFromIndex(parent); if (!parent_node || !parent_node->type.valid()) return false; bool result = false; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { const ccc::ast::Node* parent_type = parent_node->type.lookup_node(database); if (!parent_type) return; result = nodeHasChildren(*parent_type, database) && !parent_node->childrenFetched(); }); return result; } Qt::ItemFlags SymbolTreeModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.column() == LOCATION || index.column() == TYPE || index.column() == VALUE) flags |= Qt::ItemIsEditable; return flags; } QVariant SymbolTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); switch (section) { case NAME: return tr("Name"); case VALUE: return tr("Value"); case LOCATION: return tr("Location"); case SIZE: return tr("Size"); case TYPE: return tr("Type"); case LIVENESS: return tr("Liveness"); } return QVariant(); } QModelIndex SymbolTreeModel::indexFromNode(const SymbolTreeNode& node) const { int row = 0; if (node.parent()) { for (int i = 0; i < (int)node.parent()->children().size(); i++) if (node.parent()->children()[i].get() == &node) row = i; } else row = 0; return createIndex(row, 0, &node); } SymbolTreeNode* SymbolTreeModel::nodeFromIndex(const QModelIndex& index) const { if (!index.isValid()) return m_root.get(); SymbolTreeNode* node = static_cast(index.internalPointer()); if (!node) return m_root.get(); return node; } void SymbolTreeModel::reset(std::unique_ptr new_root) { beginResetModel(); m_root = std::move(new_root); endResetModel(); } void SymbolTreeModel::resetChildren(QModelIndex index) { pxAssertRel(index.isValid(), "Invalid model index."); SymbolTreeNode* node = nodeFromIndex(index); if (!node || node->tag != SymbolTreeNode::OBJECT) return; resetChildrenRecursive(*node); } void SymbolTreeModel::resetChildrenRecursive(SymbolTreeNode& node) { for (const std::unique_ptr& child : node.children()) resetChildrenRecursive(*child); bool remove_rows = !node.children().empty(); if (remove_rows) beginRemoveRows(indexFromNode(node), 0, node.children().size() - 1); node.clearChildren(); if (remove_rows) endRemoveRows(); } bool SymbolTreeModel::needsReset() const { if (!m_root) return true; bool needs_reset = false; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { needs_reset = !m_root->anySymbolsValid(database); }); return needs_reset; } std::optional SymbolTreeModel::changeTypeTemporarily(QModelIndex index, std::string_view type_string) { SymbolTreeNode* node = nodeFromIndex(index); if (!node) return std::nullopt; resetChildren(index); QString error_message; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { std::unique_ptr type = stringToType(type_string, database, error_message); if (!error_message.isEmpty()) return; node->temporary_type = std::move(type); node->type = ccc::NodeHandle(node->temporary_type.get()); }); setData(index, QVariant(), UPDATE_FROM_MEMORY_ROLE); return error_message; } std::optional SymbolTreeModel::typeFromModelIndexToString(QModelIndex index) { SymbolTreeNode* node = nodeFromIndex(index); if (!node || node->tag != SymbolTreeNode::OBJECT) return std::nullopt; QString result; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { const ccc::ast::Node* type = node->type.lookup_node(database); if (!type) return; result = typeToString(type, database); }); return result; } std::vector> SymbolTreeModel::populateChildren( const QString& name, SymbolTreeLocation location, const ccc::ast::Node& logical_type, ccc::NodeHandle parent_handle, DebugInterface& cpu, const ccc::SymbolDatabase& database) { auto [physical_type, symbol] = logical_type.physical_type(database); // If we went through a type name, we need to make the node handles for the // children point to the new symbol instead of the original one. if (symbol) parent_handle = ccc::NodeHandle(*symbol, nullptr); std::vector> children; switch (physical_type->descriptor) { case ccc::ast::ARRAY: { const ccc::ast::Array& array = physical_type->as(); for (s32 i = 0; i < array.element_count; i++) { SymbolTreeLocation element_location = location.addOffset(i * array.element_type->size_bytes); if (element_location.type == SymbolTreeLocation::NONE) continue; std::unique_ptr element = std::make_unique(); element->name = QString("[%1]").arg(i); element->type = parent_handle.handle_for_child(array.element_type.get()); element->location = element_location; children.emplace_back(std::move(element)); } break; } case ccc::ast::POINTER_OR_REFERENCE: { const ccc::ast::PointerOrReference& pointer_or_reference = physical_type->as(); u32 address = location.read32(cpu); if (!cpu.isValidAddress(address)) break; std::unique_ptr pointee = std::make_unique(); pointee->name = QString("*%1").arg(name); pointee->type = parent_handle.handle_for_child(pointer_or_reference.value_type.get()); pointee->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, address); children.emplace_back(std::move(pointee)); break; } case ccc::ast::STRUCT_OR_UNION: { const ccc::ast::StructOrUnion& struct_or_union = physical_type->as(); std::vector fields; struct_or_union.flatten_fields(fields, nullptr, database, true); for (const ccc::ast::StructOrUnion::FlatField& field : fields) { if (symbol) parent_handle = ccc::NodeHandle(*symbol, nullptr); SymbolTreeLocation field_location = location.addOffset(field.base_offset + field.node->offset_bytes); if (field_location.type == SymbolTreeLocation::NONE) continue; std::unique_ptr child_node = std::make_unique(); if (!field.node->name.empty()) child_node->name = QString::fromStdString(field.node->name); else child_node->name = QString("(anonymous %1)").arg(ccc::ast::node_type_to_string(*field.node)); child_node->type = parent_handle.handle_for_child(field.node); child_node->location = field_location; children.emplace_back(std::move(child_node)); } break; } default: { } } for (std::unique_ptr& child : children) child->readFromVM(cpu, database); return children; } bool SymbolTreeModel::nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database) { const ccc::ast::Node& type = *logical_type.physical_type(database).first; bool result = false; switch (type.descriptor) { case ccc::ast::ARRAY: { const ccc::ast::Array& array = type.as(); result = array.element_count > 0; break; } case ccc::ast::POINTER_OR_REFERENCE: { result = true; break; } case ccc::ast::STRUCT_OR_UNION: { const ccc::ast::StructOrUnion& struct_or_union = type.as(); result = !struct_or_union.base_classes.empty() || !struct_or_union.fields.empty(); break; } default: { } } return result; }