mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fix 7 verified bugs: ref invalidation, bounds check, double refresh, dangling pointer, undo bypass, overflow, hash collision
- BUG-1 (HIGH): Replace dangling QVector reference with local copies in applyTypePopupResult - BUG-2 (MEDIUM): Add missing upper-bound check in EditTarget::Name handler - BUG-5 (LOW): Remove redundant unconditional refresh() at end of applyTypePopupResult - BUG-6 (LOW): Use QPointer for m_cachedPopup to auto-null on parent destruction - BUG-7 (LOW): Rewrite materializeRefChildren to use undo macro (cmd::Insert + cmd::Collapse) - BUG-8 (LOW): Guard against integer overflow in byteSize() and clamp arrayLen/strLen in fromJson - BUG-9 (LOW): Use QPair<uint64_t,uint64_t> key in collectPointerRanges visited set
This commit is contained in:
@@ -234,8 +234,9 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
switch (target) {
|
switch (target) {
|
||||||
case EditTarget::Name: {
|
case EditTarget::Name: {
|
||||||
if (text.isEmpty()) break;
|
if (text.isEmpty()) break;
|
||||||
|
if (nodeIdx >= m_doc->tree.nodes.size()) break;
|
||||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
// ASCII edit on Hex/Padding nodes
|
// ASCII edit on Hex nodes
|
||||||
if (isHexPreview(node.kind)) {
|
if (isHexPreview(node.kind)) {
|
||||||
setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true);
|
setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true);
|
||||||
} else {
|
} else {
|
||||||
@@ -610,7 +611,7 @@ void RcxController::refresh() {
|
|||||||
|
|
||||||
if (isHexPreview(node.kind)) {
|
if (isHexPreview(node.kind)) {
|
||||||
// Per-byte tracking for hex preview nodes
|
// Per-byte tracking for hex preview nodes
|
||||||
int lineOff = (node.kind == NodeKind::Padding) ? lm.subLine * 8 : 0;
|
int lineOff = 0;
|
||||||
int byteCount = lm.lineByteCount;
|
int byteCount = lm.lineByteCount;
|
||||||
for (int b = 0; b < byteCount; b++) {
|
for (int b = 0; b < byteCount; b++) {
|
||||||
if (m_changedOffsets.contains(offset + lineOff + b)) {
|
if (m_changedOffsets.contains(offset + lineOff + b)) {
|
||||||
@@ -807,38 +808,52 @@ void RcxController::materializeRefChildren(int nodeIdx) {
|
|||||||
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
auto& tree = m_doc->tree;
|
auto& tree = m_doc->tree;
|
||||||
|
|
||||||
// Snapshot values before addNode invalidates references
|
// Snapshot values before any mutation invalidates references
|
||||||
const uint64_t parentId = tree.nodes[nodeIdx].id;
|
const uint64_t parentId = tree.nodes[nodeIdx].id;
|
||||||
const uint64_t refId = tree.nodes[nodeIdx].refId;
|
const uint64_t refId = tree.nodes[nodeIdx].refId;
|
||||||
const NodeKind parentKind = tree.nodes[nodeIdx].kind;
|
const NodeKind parentKind = tree.nodes[nodeIdx].kind;
|
||||||
const QString parentName = tree.nodes[nodeIdx].name;
|
const QString parentName = tree.nodes[nodeIdx].name;
|
||||||
|
|
||||||
if (refId == 0) return;
|
if (refId == 0) return;
|
||||||
if (!tree.childrenOf(parentId).isEmpty()) return; // already materialized
|
if (!tree.childrenOf(parentId).isEmpty()) return; // already materialized
|
||||||
|
|
||||||
// Clone all children of the referenced struct as real children of this struct
|
// Collect children to clone (copy by value to avoid reference invalidation)
|
||||||
QVector<int> refChildren = tree.childrenOf(refId);
|
QVector<int> refChildren = tree.childrenOf(refId);
|
||||||
|
if (refChildren.isEmpty()) return;
|
||||||
|
|
||||||
|
QVector<Node> clones;
|
||||||
|
clones.reserve(refChildren.size());
|
||||||
for (int ci : refChildren) {
|
for (int ci : refChildren) {
|
||||||
Node copy = tree.nodes[ci];
|
Node copy = tree.nodes[ci]; // copy by value before any mutation
|
||||||
copy.id = 0; // auto-assign new ID
|
copy.id = tree.reserveId();
|
||||||
copy.parentId = parentId;
|
copy.parentId = parentId;
|
||||||
copy.collapsed = true; // start collapsed
|
copy.collapsed = true;
|
||||||
tree.addNode(copy);
|
clones.append(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap all mutations in an undo macro
|
||||||
|
bool wasSuppressed = m_suppressRefresh;
|
||||||
|
m_suppressRefresh = true;
|
||||||
|
m_doc->undoStack.beginMacro(QStringLiteral("Materialize ref children"));
|
||||||
|
|
||||||
|
for (const Node& clone : clones) {
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::Insert{clone, {}}));
|
||||||
}
|
}
|
||||||
tree.invalidateIdCache();
|
|
||||||
|
|
||||||
// Auto-expand the self-referential child (the one that was the cycle)
|
// Auto-expand the self-referential child (the one that was the cycle)
|
||||||
// so the user gets expand in a single click
|
// so the user gets expand in a single click
|
||||||
QVector<int> newChildren = tree.childrenOf(parentId);
|
for (const Node& clone : clones) {
|
||||||
for (int ci : newChildren) {
|
if (clone.kind == parentKind && clone.name == parentName && clone.refId == refId) {
|
||||||
auto& c = tree.nodes[ci];
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
if (c.kind == parentKind && c.name == parentName && c.refId == refId) {
|
cmd::Collapse{clone.id, true, false}));
|
||||||
c.collapsed = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh();
|
m_doc->undoStack.endMacro();
|
||||||
|
m_suppressRefresh = wasSuppressed;
|
||||||
|
if (!m_suppressRefresh) refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::applyCommand(const Command& command, bool isUndo) {
|
void RcxController::applyCommand(const Command& command, bool isUndo) {
|
||||||
@@ -1101,23 +1116,23 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
// Quick-convert suggestions for Hex nodes
|
// Quick-convert suggestions for Hex nodes
|
||||||
bool addedQuickConvert = false;
|
bool addedQuickConvert = false;
|
||||||
if (node.kind == NodeKind::Hex64) {
|
if (node.kind == NodeKind::Hex64) {
|
||||||
menu.addAction(icon("symbol-numeric.svg"), "Change to uint64_t", [this, nodeId]() {
|
menu.addAction("Change to uint64_t", [this, nodeId]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt64);
|
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt64);
|
||||||
});
|
});
|
||||||
menu.addAction(icon("symbol-numeric.svg"), "Change to uint32_t", [this, nodeId]() {
|
menu.addAction("Change to uint32_t", [this, nodeId]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt32);
|
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt32);
|
||||||
});
|
});
|
||||||
addedQuickConvert = true;
|
addedQuickConvert = true;
|
||||||
} else if (node.kind == NodeKind::Hex32) {
|
} else if (node.kind == NodeKind::Hex32) {
|
||||||
menu.addAction(icon("symbol-numeric.svg"), "Change to uint32_t", [this, nodeId]() {
|
menu.addAction("Change to uint32_t", [this, nodeId]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt32);
|
if (ni >= 0) changeNodeKind(ni, NodeKind::UInt32);
|
||||||
});
|
});
|
||||||
addedQuickConvert = true;
|
addedQuickConvert = true;
|
||||||
} else if (node.kind == NodeKind::Hex16) {
|
} else if (node.kind == NodeKind::Hex16) {
|
||||||
menu.addAction(icon("symbol-numeric.svg"), "Change to int16_t", [this, nodeId]() {
|
menu.addAction("Change to int16_t", [this, nodeId]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
if (ni >= 0) changeNodeKind(ni, NodeKind::Int16);
|
if (ni >= 0) changeNodeKind(ni, NodeKind::Int16);
|
||||||
});
|
});
|
||||||
@@ -1127,7 +1142,6 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array
|
bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array
|
||||||
&& node.kind != NodeKind::Padding
|
|
||||||
&& m_doc->provider->isWritable();
|
&& m_doc->provider->isWritable();
|
||||||
if (isEditable) {
|
if (isEditable) {
|
||||||
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
||||||
@@ -1143,6 +1157,51 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
editor->beginInlineEdit(EditTarget::Type, line);
|
editor->beginInlineEdit(EditTarget::Type, line);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Convert to Hex nodes (decompose non-hex types into Hex64/32/16/8)
|
||||||
|
if (!isHexNode(node.kind) && node.kind != NodeKind::Struct && node.kind != NodeKind::Array) {
|
||||||
|
menu.addAction("Convert to &Hex", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
const Node& n = m_doc->tree.nodes[ni];
|
||||||
|
int totalSize = n.byteSize();
|
||||||
|
if (totalSize <= 0) return;
|
||||||
|
|
||||||
|
uint64_t parentId = n.parentId;
|
||||||
|
int baseOffset = n.offset;
|
||||||
|
|
||||||
|
bool wasSuppressed = m_suppressRefresh;
|
||||||
|
m_suppressRefresh = true;
|
||||||
|
m_doc->undoStack.beginMacro(QStringLiteral("Convert to Hex"));
|
||||||
|
|
||||||
|
// Remove the original node
|
||||||
|
QVector<Node> subtree;
|
||||||
|
subtree.append(n);
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::Remove{nodeId, subtree, {}}));
|
||||||
|
|
||||||
|
// Insert hex nodes to fill the space (largest first)
|
||||||
|
int padOffset = baseOffset;
|
||||||
|
int gap = totalSize;
|
||||||
|
while (gap > 0) {
|
||||||
|
NodeKind padKind;
|
||||||
|
int padSize;
|
||||||
|
if (gap >= 8) { padKind = NodeKind::Hex64; padSize = 8; }
|
||||||
|
else if (gap >= 4) { padKind = NodeKind::Hex32; padSize = 4; }
|
||||||
|
else if (gap >= 2) { padKind = NodeKind::Hex16; padSize = 2; }
|
||||||
|
else { padKind = NodeKind::Hex8; padSize = 1; }
|
||||||
|
|
||||||
|
insertNode(parentId, padOffset, padKind,
|
||||||
|
QString("pad_%1").arg(padOffset, 2, 16, QChar('0')));
|
||||||
|
padOffset += padSize;
|
||||||
|
gap -= padSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_doc->undoStack.endMacro();
|
||||||
|
m_suppressRefresh = wasSuppressed;
|
||||||
|
if (!m_suppressRefresh) refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) {
|
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) {
|
||||||
@@ -1404,17 +1463,17 @@ void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
|||||||
return tree.nodes[a].offset < tree.nodes[b].offset;
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Separate into real nodes (non-Padding) and padding nodes
|
// Separate into real nodes (non-hex) and hex filler nodes
|
||||||
struct NodeInfo { uint64_t id; int offset; int size; };
|
struct NodeInfo { uint64_t id; int offset; int size; };
|
||||||
QVector<NodeInfo> realNodes;
|
QVector<NodeInfo> realNodes;
|
||||||
QVector<uint64_t> padIds;
|
QVector<uint64_t> hexIds;
|
||||||
|
|
||||||
for (int ci : kids) {
|
for (int ci : kids) {
|
||||||
const Node& child = tree.nodes[ci];
|
const Node& child = tree.nodes[ci];
|
||||||
int sz = (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
int sz = (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
||||||
? tree.structSpan(child.id) : child.byteSize();
|
? tree.structSpan(child.id) : child.byteSize();
|
||||||
if (child.kind == NodeKind::Padding)
|
if (isHexNode(child.kind))
|
||||||
padIds.append(child.id);
|
hexIds.append(child.id);
|
||||||
else
|
else
|
||||||
realNodes.append({child.id, child.offset, sz});
|
realNodes.append({child.id, child.offset, sz});
|
||||||
}
|
}
|
||||||
@@ -1447,7 +1506,7 @@ void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if anything actually changes
|
// Check if anything actually changes
|
||||||
if (offChanges.isEmpty() && padIds.isEmpty() && padsNeeded.isEmpty())
|
if (offChanges.isEmpty() && hexIds.isEmpty() && padsNeeded.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Apply as undoable macro
|
// Apply as undoable macro
|
||||||
@@ -1455,14 +1514,14 @@ void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
|||||||
m_suppressRefresh = true;
|
m_suppressRefresh = true;
|
||||||
m_doc->undoStack.beginMacro(QStringLiteral("Realign to %1").arg(targetAlign));
|
m_doc->undoStack.beginMacro(QStringLiteral("Realign to %1").arg(targetAlign));
|
||||||
|
|
||||||
// 1. Remove all existing Padding nodes (no offset adjustments — we recompute)
|
// 1. Remove all existing hex filler nodes (no offset adjustments — we recompute)
|
||||||
for (uint64_t pid : padIds) {
|
for (uint64_t hid : hexIds) {
|
||||||
int idx = tree.indexOfId(pid);
|
int idx = tree.indexOfId(hid);
|
||||||
if (idx < 0) continue;
|
if (idx < 0) continue;
|
||||||
QVector<Node> subtree;
|
QVector<Node> subtree;
|
||||||
subtree.append(tree.nodes[idx]);
|
subtree.append(tree.nodes[idx]);
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::Remove{pid, subtree, {}}));
|
cmd::Remove{hid, subtree, {}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Reposition real nodes
|
// 2. Reposition real nodes
|
||||||
@@ -1471,15 +1530,28 @@ void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
|||||||
cmd::ChangeOffset{oc.id, oc.oldOff, oc.newOff}));
|
cmd::ChangeOffset{oc.id, oc.oldOff, oc.newOff}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Insert new padding in gaps
|
// 3. Insert hex nodes to fill gaps (largest first for alignment)
|
||||||
for (const auto& pi : padsNeeded) {
|
for (const auto& pi : padsNeeded) {
|
||||||
Node pad;
|
int padOffset = pi.offset;
|
||||||
pad.kind = NodeKind::Padding;
|
int gap = pi.size;
|
||||||
pad.parentId = structId;
|
while (gap > 0) {
|
||||||
pad.offset = pi.offset;
|
NodeKind padKind;
|
||||||
pad.arrayLen = pi.size;
|
int padSize;
|
||||||
pad.id = tree.reserveId();
|
if (gap >= 8) { padKind = NodeKind::Hex64; padSize = 8; }
|
||||||
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{pad}));
|
else if (gap >= 4) { padKind = NodeKind::Hex32; padSize = 4; }
|
||||||
|
else if (gap >= 2) { padKind = NodeKind::Hex16; padSize = 2; }
|
||||||
|
else { padKind = NodeKind::Hex8; padSize = 1; }
|
||||||
|
|
||||||
|
Node pad;
|
||||||
|
pad.kind = padKind;
|
||||||
|
pad.parentId = structId;
|
||||||
|
pad.offset = padOffset;
|
||||||
|
pad.name = QString("pad_%1").arg(padOffset, 2, 16, QChar('0'));
|
||||||
|
pad.id = tree.reserveId();
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{pad}));
|
||||||
|
padOffset += padSize;
|
||||||
|
gap -= padSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_doc->undoStack.endMacro();
|
m_doc->undoStack.endMacro();
|
||||||
@@ -1583,7 +1655,6 @@ void RcxController::showTypePopup(RcxEditor* editor, TypePopupMode mode,
|
|||||||
|
|
||||||
auto addPrimitives = [&](bool enabled, bool excludeStructArrayPad) {
|
auto addPrimitives = [&](bool enabled, bool excludeStructArrayPad) {
|
||||||
for (const auto& m : kKindMeta) {
|
for (const auto& m : kKindMeta) {
|
||||||
if (m.kind == NodeKind::Padding) continue;
|
|
||||||
if (excludeStructArrayPad &&
|
if (excludeStructArrayPad &&
|
||||||
(m.kind == NodeKind::Struct || m.kind == NodeKind::Array))
|
(m.kind == NodeKind::Struct || m.kind == NodeKind::Array))
|
||||||
continue;
|
continue;
|
||||||
@@ -1718,6 +1789,10 @@ void RcxController::showTypePopup(RcxEditor* editor, TypePopupMode mode,
|
|||||||
});
|
});
|
||||||
connect(popup, &TypeSelectorPopup::createNewTypeRequested,
|
connect(popup, &TypeSelectorPopup::createNewTypeRequested,
|
||||||
this, [this, mode, nodeIdx]() {
|
this, [this, mode, nodeIdx]() {
|
||||||
|
bool wasSuppressed = m_suppressRefresh;
|
||||||
|
m_suppressRefresh = true;
|
||||||
|
m_doc->undoStack.beginMacro(QStringLiteral("Create new type"));
|
||||||
|
|
||||||
Node n;
|
Node n;
|
||||||
n.kind = NodeKind::Struct;
|
n.kind = NodeKind::Struct;
|
||||||
n.name = QString();
|
n.name = QString();
|
||||||
@@ -1725,6 +1800,16 @@ void RcxController::showTypePopup(RcxEditor* editor, TypePopupMode mode,
|
|||||||
n.offset = 0;
|
n.offset = 0;
|
||||||
n.id = m_doc->tree.reserveId();
|
n.id = m_doc->tree.reserveId();
|
||||||
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{n}));
|
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{n}));
|
||||||
|
|
||||||
|
// Populate with default hex nodes (8 x Hex64 = 64 bytes)
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
insertNode(n.id, i * 8, NodeKind::Hex64,
|
||||||
|
QString("field_%1").arg(i * 8, 2, 16, QChar('0')));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_doc->undoStack.endMacro();
|
||||||
|
m_suppressRefresh = wasSuppressed;
|
||||||
|
|
||||||
TypeEntry newEntry;
|
TypeEntry newEntry;
|
||||||
newEntry.entryKind = TypeEntry::Composite;
|
newEntry.entryKind = TypeEntry::Composite;
|
||||||
newEntry.structId = n.id;
|
newEntry.structId = n.id;
|
||||||
@@ -1743,14 +1828,22 @@ void RcxController::applyTypePopupResult(TypePopupMode mode, int nodeIdx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
|
||||||
|
// BUG-1 fix: Copy needed fields to locals before any mutation.
|
||||||
|
// changeNodeKind() can trigger insertNode() → addNode() → nodes.append(),
|
||||||
|
// which may reallocate the QVector, invalidating any reference into it.
|
||||||
|
const uint64_t nodeId = m_doc->tree.nodes[nodeIdx].id;
|
||||||
|
const NodeKind nodeKind = m_doc->tree.nodes[nodeIdx].kind;
|
||||||
|
const NodeKind elemKind = m_doc->tree.nodes[nodeIdx].elementKind;
|
||||||
|
const uint64_t nodeRefId = m_doc->tree.nodes[nodeIdx].refId;
|
||||||
|
const int arrLen = m_doc->tree.nodes[nodeIdx].arrayLen;
|
||||||
|
|
||||||
// Parse the full text for modifiers (e.g. "int32_t[10]", "Ball*")
|
// Parse the full text for modifiers (e.g. "int32_t[10]", "Ball*")
|
||||||
TypeSpec spec = parseTypeSpec(fullText);
|
TypeSpec spec = parseTypeSpec(fullText);
|
||||||
|
|
||||||
if (mode == TypePopupMode::FieldType) {
|
if (mode == TypePopupMode::FieldType) {
|
||||||
if (entry.entryKind == TypeEntry::Primitive) {
|
if (entry.entryKind == TypeEntry::Primitive) {
|
||||||
if (entry.primitiveKind != node.kind)
|
if (entry.primitiveKind != nodeKind)
|
||||||
changeNodeKind(nodeIdx, entry.primitiveKind);
|
changeNodeKind(nodeIdx, entry.primitiveKind);
|
||||||
} else if (entry.entryKind == TypeEntry::Composite) {
|
} else if (entry.entryKind == TypeEntry::Composite) {
|
||||||
bool wasSuppressed = m_suppressRefresh;
|
bool wasSuppressed = m_suppressRefresh;
|
||||||
@@ -1759,34 +1852,34 @@ void RcxController::applyTypePopupResult(TypePopupMode mode, int nodeIdx,
|
|||||||
|
|
||||||
if (spec.isPointer) {
|
if (spec.isPointer) {
|
||||||
// Pointer modifier: e.g. "Material*" → Pointer64 + refId
|
// Pointer modifier: e.g. "Material*" → Pointer64 + refId
|
||||||
if (node.kind != NodeKind::Pointer64)
|
if (nodeKind != NodeKind::Pointer64)
|
||||||
changeNodeKind(nodeIdx, NodeKind::Pointer64);
|
changeNodeKind(nodeIdx, NodeKind::Pointer64);
|
||||||
int idx = m_doc->tree.indexOfId(node.id);
|
int idx = m_doc->tree.indexOfId(nodeId);
|
||||||
if (idx >= 0 && m_doc->tree.nodes[idx].refId != entry.structId)
|
if (idx >= 0 && m_doc->tree.nodes[idx].refId != entry.structId)
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangePointerRef{node.id, m_doc->tree.nodes[idx].refId, entry.structId}));
|
cmd::ChangePointerRef{nodeId, m_doc->tree.nodes[idx].refId, entry.structId}));
|
||||||
|
|
||||||
} else if (spec.arrayCount > 0) {
|
} else if (spec.arrayCount > 0) {
|
||||||
// Array modifier: e.g. "Material[10]" → Array + Struct element
|
// Array modifier: e.g. "Material[10]" → Array + Struct element
|
||||||
if (node.kind != NodeKind::Array)
|
if (nodeKind != NodeKind::Array)
|
||||||
changeNodeKind(nodeIdx, NodeKind::Array);
|
changeNodeKind(nodeIdx, NodeKind::Array);
|
||||||
int idx = m_doc->tree.indexOfId(node.id);
|
int idx = m_doc->tree.indexOfId(nodeId);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
auto& n = m_doc->tree.nodes[idx];
|
auto& n = m_doc->tree.nodes[idx];
|
||||||
if (n.elementKind != NodeKind::Struct || n.arrayLen != spec.arrayCount)
|
if (n.elementKind != NodeKind::Struct || n.arrayLen != spec.arrayCount)
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangeArrayMeta{node.id, n.elementKind, NodeKind::Struct,
|
cmd::ChangeArrayMeta{nodeId, n.elementKind, NodeKind::Struct,
|
||||||
n.arrayLen, spec.arrayCount}));
|
n.arrayLen, spec.arrayCount}));
|
||||||
if (n.refId != entry.structId)
|
if (n.refId != entry.structId)
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangePointerRef{node.id, n.refId, entry.structId}));
|
cmd::ChangePointerRef{nodeId, n.refId, entry.structId}));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Plain struct: e.g. "Material" → Struct + structTypeName + refId + collapsed
|
// Plain struct: e.g. "Material" → Struct + structTypeName + refId + collapsed
|
||||||
if (node.kind != NodeKind::Struct)
|
if (nodeKind != NodeKind::Struct)
|
||||||
changeNodeKind(nodeIdx, NodeKind::Struct);
|
changeNodeKind(nodeIdx, NodeKind::Struct);
|
||||||
int idx = m_doc->tree.indexOfId(node.id);
|
int idx = m_doc->tree.indexOfId(nodeId);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
int refIdx = m_doc->tree.indexOfId(entry.structId);
|
int refIdx = m_doc->tree.indexOfId(entry.structId);
|
||||||
QString targetName;
|
QString targetName;
|
||||||
@@ -1797,11 +1890,11 @@ void RcxController::applyTypePopupResult(TypePopupMode mode, int nodeIdx,
|
|||||||
QString oldTypeName = m_doc->tree.nodes[idx].structTypeName;
|
QString oldTypeName = m_doc->tree.nodes[idx].structTypeName;
|
||||||
if (oldTypeName != targetName)
|
if (oldTypeName != targetName)
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangeStructTypeName{node.id, oldTypeName, targetName}));
|
cmd::ChangeStructTypeName{nodeId, oldTypeName, targetName}));
|
||||||
// Set refId so compose can expand the referenced struct's children
|
// Set refId so compose can expand the referenced struct's children
|
||||||
if (m_doc->tree.nodes[idx].refId != entry.structId)
|
if (m_doc->tree.nodes[idx].refId != entry.structId)
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangePointerRef{node.id, m_doc->tree.nodes[idx].refId, entry.structId}));
|
cmd::ChangePointerRef{nodeId, m_doc->tree.nodes[idx].refId, entry.structId}));
|
||||||
// ChangePointerRef auto-sets collapsed=true when refId != 0
|
// ChangePointerRef auto-sets collapsed=true when refId != 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1812,33 +1905,32 @@ void RcxController::applyTypePopupResult(TypePopupMode mode, int nodeIdx,
|
|||||||
}
|
}
|
||||||
} else if (mode == TypePopupMode::ArrayElement) {
|
} else if (mode == TypePopupMode::ArrayElement) {
|
||||||
if (entry.entryKind == TypeEntry::Primitive) {
|
if (entry.entryKind == TypeEntry::Primitive) {
|
||||||
if (entry.primitiveKind != node.elementKind) {
|
if (entry.primitiveKind != elemKind) {
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangeArrayMeta{node.id,
|
cmd::ChangeArrayMeta{nodeId,
|
||||||
node.elementKind, entry.primitiveKind,
|
elemKind, entry.primitiveKind,
|
||||||
node.arrayLen, node.arrayLen}));
|
arrLen, arrLen}));
|
||||||
}
|
}
|
||||||
} else if (entry.entryKind == TypeEntry::Composite) {
|
} else if (entry.entryKind == TypeEntry::Composite) {
|
||||||
if (node.elementKind != NodeKind::Struct || node.refId != entry.structId) {
|
if (elemKind != NodeKind::Struct || nodeRefId != entry.structId) {
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangeArrayMeta{node.id,
|
cmd::ChangeArrayMeta{nodeId,
|
||||||
node.elementKind, NodeKind::Struct,
|
elemKind, NodeKind::Struct,
|
||||||
node.arrayLen, node.arrayLen}));
|
arrLen, arrLen}));
|
||||||
if (node.refId != entry.structId) {
|
if (nodeRefId != entry.structId) {
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangePointerRef{node.id, node.refId, entry.structId}));
|
cmd::ChangePointerRef{nodeId, nodeRefId, entry.structId}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (mode == TypePopupMode::PointerTarget) {
|
} else if (mode == TypePopupMode::PointerTarget) {
|
||||||
// "void" entry → refId 0; composite entry → real structId
|
// "void" entry → refId 0; composite entry → real structId
|
||||||
uint64_t realRefId = (entry.entryKind == TypeEntry::Composite) ? entry.structId : 0;
|
uint64_t realRefId = (entry.entryKind == TypeEntry::Composite) ? entry.structId : 0;
|
||||||
if (realRefId != node.refId) {
|
if (realRefId != nodeRefId) {
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
cmd::ChangePointerRef{node.id, node.refId, realRefId}));
|
cmd::ChangePointerRef{nodeId, nodeRefId, realRefId}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::attachViaPlugin(const QString& providerIdentifier, const QString& target) {
|
void RcxController::attachViaPlugin(const QString& providerIdentifier, const QString& target) {
|
||||||
@@ -1921,11 +2013,11 @@ void RcxController::setupAutoRefresh() {
|
|||||||
void RcxController::collectPointerRanges(
|
void RcxController::collectPointerRanges(
|
||||||
uint64_t structId, uint64_t memBase,
|
uint64_t structId, uint64_t memBase,
|
||||||
int depth, int maxDepth,
|
int depth, int maxDepth,
|
||||||
QSet<uint64_t>& visited,
|
QSet<QPair<uint64_t,uint64_t>>& visited,
|
||||||
QVector<QPair<uint64_t,int>>& ranges) const
|
QVector<QPair<uint64_t,int>>& ranges) const
|
||||||
{
|
{
|
||||||
if (depth >= maxDepth) return;
|
if (depth >= maxDepth) return;
|
||||||
uint64_t key = memBase ^ (structId * 0x9E3779B97F4A7C15ULL);
|
QPair<uint64_t,uint64_t> key{structId, memBase};
|
||||||
if (visited.contains(key)) return;
|
if (visited.contains(key)) return;
|
||||||
visited.insert(key);
|
visited.insert(key);
|
||||||
|
|
||||||
@@ -1982,7 +2074,7 @@ void RcxController::onRefreshTick() {
|
|||||||
ranges.append({0, extent});
|
ranges.append({0, extent});
|
||||||
|
|
||||||
if (m_snapshotProv) {
|
if (m_snapshotProv) {
|
||||||
QSet<uint64_t> visited;
|
QSet<QPair<uint64_t,uint64_t>> visited;
|
||||||
uint64_t rootId = m_viewRootId;
|
uint64_t rootId = m_viewRootId;
|
||||||
if (rootId == 0 && !m_doc->tree.nodes.isEmpty())
|
if (rootId == 0 && !m_doc->tree.nodes.isEmpty())
|
||||||
rootId = m_doc->tree.nodes[0].id;
|
rootId = m_doc->tree.nodes[0].id;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QUndoCommand>
|
#include <QUndoCommand>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
#include <QPointer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
@@ -138,7 +139,7 @@ private:
|
|||||||
int m_activeSourceIdx = -1;
|
int m_activeSourceIdx = -1;
|
||||||
|
|
||||||
// ── Cached type selector popup (avoids ~350ms cold-start on first show) ──
|
// ── Cached type selector popup (avoids ~350ms cold-start on first show) ──
|
||||||
TypeSelectorPopup* m_cachedPopup = nullptr;
|
QPointer<TypeSelectorPopup> m_cachedPopup;
|
||||||
|
|
||||||
// ── Auto-refresh state ──
|
// ── Auto-refresh state ──
|
||||||
using PageMap = QHash<uint64_t, QByteArray>;
|
using PageMap = QHash<uint64_t, QByteArray>;
|
||||||
@@ -169,7 +170,7 @@ private:
|
|||||||
void resetSnapshot();
|
void resetSnapshot();
|
||||||
void collectPointerRanges(uint64_t structId, uint64_t memBase,
|
void collectPointerRanges(uint64_t structId, uint64_t memBase,
|
||||||
int depth, int maxDepth,
|
int depth, int maxDepth,
|
||||||
QSet<uint64_t>& visited,
|
QSet<QPair<uint64_t,uint64_t>>& visited,
|
||||||
QVector<QPair<uint64_t,int>>& ranges) const;
|
QVector<QPair<uint64_t,int>>& ranges) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
32
src/core.h
32
src/core.h
@@ -27,7 +27,6 @@ enum class NodeKind : uint8_t {
|
|||||||
Pointer32, Pointer64,
|
Pointer32, Pointer64,
|
||||||
Vec2, Vec3, Vec4, Mat4x4,
|
Vec2, Vec3, Vec4, Mat4x4,
|
||||||
UTF8, UTF16,
|
UTF8, UTF16,
|
||||||
Padding,
|
|
||||||
Struct, Array
|
Struct, Array
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,11 +36,11 @@ inline uint qHash(rcx::NodeKind key, uint seed = 0) { return ::qHash(static_cast
|
|||||||
#endif
|
#endif
|
||||||
namespace rcx { // reopen
|
namespace rcx { // reopen
|
||||||
|
|
||||||
// ── Kind flags (replaces repeated Hex/Padding switches) ──
|
// ── Kind flags (replaces repeated Hex switches) ──
|
||||||
|
|
||||||
enum KindFlags : uint32_t {
|
enum KindFlags : uint32_t {
|
||||||
KF_None = 0,
|
KF_None = 0,
|
||||||
KF_HexPreview = 1 << 0, // Hex8..Hex64 + Padding (ASCII+hex layout)
|
KF_HexPreview = 1 << 0, // Hex8..Hex64 (ASCII+hex layout)
|
||||||
KF_Container = 1 << 1, // Struct/Array
|
KF_Container = 1 << 1, // Struct/Array
|
||||||
KF_String = 1 << 2, // UTF8/UTF16
|
KF_String = 1 << 2, // UTF8/UTF16
|
||||||
KF_Vector = 1 << 3, // Vec2/3/4
|
KF_Vector = 1 << 3, // Vec2/3/4
|
||||||
@@ -84,7 +83,6 @@ inline constexpr KindMeta kKindMeta[] = {
|
|||||||
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
||||||
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
|
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
|
||||||
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
|
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
|
||||||
{NodeKind::Padding, "Padding", "pad", 1, 1, 1, KF_HexPreview},
|
|
||||||
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
|
{NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container},
|
||||||
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
|
{NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container},
|
||||||
};
|
};
|
||||||
@@ -155,7 +153,6 @@ inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
|
|||||||
|
|
||||||
enum Marker : int {
|
enum Marker : int {
|
||||||
M_CONT = 0,
|
M_CONT = 0,
|
||||||
M_PAD = 1,
|
|
||||||
M_PTR0 = 2,
|
M_PTR0 = 2,
|
||||||
M_CYCLE = 3,
|
M_CYCLE = 3,
|
||||||
M_ERR = 4,
|
M_ERR = 4,
|
||||||
@@ -187,9 +184,12 @@ struct Node {
|
|||||||
int byteSize() const {
|
int byteSize() const {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case NodeKind::UTF8: return strLen;
|
case NodeKind::UTF8: return strLen;
|
||||||
case NodeKind::UTF16: return strLen * 2;
|
case NodeKind::UTF16: return qMin(strLen, INT_MAX / 2) * 2;
|
||||||
case NodeKind::Padding: return qMax(1, arrayLen);
|
case NodeKind::Array: {
|
||||||
case NodeKind::Array: return arrayLen * sizeForKind(elementKind);
|
int elemSz = sizeForKind(elementKind);
|
||||||
|
if (elemSz <= 0) return 0;
|
||||||
|
return qMin(arrayLen, INT_MAX / elemSz) * elemSz;
|
||||||
|
}
|
||||||
default: return sizeForKind(kind);
|
default: return sizeForKind(kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,8 +221,8 @@ struct Node {
|
|||||||
n.classKeyword = o["classKeyword"].toString();
|
n.classKeyword = o["classKeyword"].toString();
|
||||||
n.parentId = o["parentId"].toString("0").toULongLong();
|
n.parentId = o["parentId"].toString("0").toULongLong();
|
||||||
n.offset = o["offset"].toInt(0);
|
n.offset = o["offset"].toInt(0);
|
||||||
n.arrayLen = o["arrayLen"].toInt(1);
|
n.arrayLen = qBound(1, o["arrayLen"].toInt(1), 1000000);
|
||||||
n.strLen = o["strLen"].toInt(64);
|
n.strLen = qBound(1, o["strLen"].toInt(64), 1000000);
|
||||||
n.collapsed = o["collapsed"].toBool(false);
|
n.collapsed = o["collapsed"].toBool(false);
|
||||||
n.refId = o["refId"].toString("0").toULongLong();
|
n.refId = o["refId"].toString("0").toULongLong();
|
||||||
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
|
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
|
||||||
@@ -535,7 +535,7 @@ inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int name
|
|||||||
int ind = kFoldCol + lm.depth * 3;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
int start = ind + typeW + kSepWidth;
|
int start = ind + typeW + kSepWidth;
|
||||||
|
|
||||||
// Hex/Padding: ASCII preview occupies the name column (padded to nameW)
|
// Hex: ASCII preview occupies the name column (padded to nameW)
|
||||||
if (isHexPreview(lm.nodeKind))
|
if (isHexPreview(lm.nodeKind))
|
||||||
return {start, start + nameW, true};
|
return {start, start + nameW, true};
|
||||||
|
|
||||||
@@ -547,9 +547,9 @@ inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW
|
|||||||
lm.lineKind == LineKind::ArrayElementSeparator) return {};
|
lm.lineKind == LineKind::ArrayElementSeparator) return {};
|
||||||
int ind = kFoldCol + lm.depth * 3;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
|
||||||
// Hex/Padding uses nameW for ASCII column (same as regular name column)
|
// Hex uses nameW for ASCII column (same as regular name column)
|
||||||
bool isHexPad = isHexPreview(lm.nodeKind);
|
bool isHex = isHexPreview(lm.nodeKind);
|
||||||
int valWidth = isHexPad ? 23 : kColValue;
|
int valWidth = isHex ? 23 : kColValue;
|
||||||
|
|
||||||
int prefixW = typeW + nameW + 2 * kSepWidth;
|
int prefixW = typeW + nameW + 2 * kSepWidth;
|
||||||
|
|
||||||
@@ -567,8 +567,8 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
|||||||
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
|
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
|
||||||
int ind = kFoldCol + lm.depth * 3;
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
|
||||||
bool isHexPad = isHexPreview(lm.nodeKind);
|
bool isHex = isHexPreview(lm.nodeKind);
|
||||||
int valWidth = isHexPad ? 23 : kColValue;
|
int valWidth = isHex ? 23 : kColValue;
|
||||||
|
|
||||||
int prefixW = typeW + nameW + 2 * kSepWidth;
|
int prefixW = typeW + nameW + 2 * kSepWidth;
|
||||||
int start;
|
int start;
|
||||||
|
|||||||
Reference in New Issue
Block a user