From 2a44d2ac5778c1b49721aef9764603d9bf39ef96 Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Thu, 19 Feb 2026 13:05:25 -0700 Subject: [PATCH] fix: narrow inline editor selection for pointer values, resolve correct write address resolvedSpanFor() now applies narrowPtrValueSpan() to trim the "// Module+offset" symbol comment from the editable span, matching hitTestTarget(). Previously the full value column text was selected, making the parser fail on commit (toULongLong rejected the non-hex suffix), so pointer value saves were silently no-ops. With the parse now succeeding, a second bug was exposed: setNodeValue() computed write addresses via computeOffset() which sums tree offsets without dereferencing pointers. For nodes inside expanded pointer targets (e.g. VTable entries), this wrote to struct_base+child_offset instead of *ptr_value+child_offset, causing an access violation crash. The fix passes lm->offsetAddr (the compose-resolved absolute address) through the inlineEditCommitted signal so setNodeValue() uses the correct dereferenced address. --- src/controller.cpp | 23 ++++++++++++++++------- src/controller.h | 3 ++- src/editor.cpp | 17 +++++++++++++---- src/editor.h | 3 ++- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index f9bb411..1a7594c 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -230,7 +230,8 @@ void RcxController::connectEditor(RcxEditor* editor) { // Inline editing signals connect(editor, &RcxEditor::inlineEditCommitted, - this, [this](int nodeIdx, int subLine, EditTarget target, const QString& text) { + this, [this](int nodeIdx, int subLine, EditTarget target, const QString& text, + uint64_t resolvedAddr) { // CommandRow BaseAddress/Source/RootClass edit has nodeIdx=-1 if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source && target != EditTarget::RootClassType && target != EditTarget::RootClassName) { refresh(); return; } @@ -241,7 +242,7 @@ void RcxController::connectEditor(RcxEditor* editor) { const Node& node = m_doc->tree.nodes[nodeIdx]; // ASCII edit on Hex nodes if (isHexPreview(node.kind)) { - setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true); + setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true, resolvedAddr); } else { renameNode(nodeIdx, text); } @@ -311,7 +312,7 @@ void RcxController::connectEditor(RcxEditor* editor) { break; } case EditTarget::Value: - setNodeValue(nodeIdx, subLine, text); + setNodeValue(nodeIdx, subLine, text, /*isAscii=*/false, resolvedAddr); break; case EditTarget::BaseAddress: { QString s = text.trimmed(); @@ -1023,14 +1024,22 @@ void RcxController::applyCommand(const Command& command, bool isUndo) { } void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text, - bool isAscii) { + bool isAscii, uint64_t resolvedAddr) { if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return; if (!m_doc->provider->isWritable()) return; const Node& node = m_doc->tree.nodes[nodeIdx]; - int64_t signedAddr = m_doc->tree.computeOffset(nodeIdx); - if (signedAddr < 0) return; // malformed tree: negative offset - uint64_t addr = m_doc->tree.baseAddress + static_cast(signedAddr); + + // Use the compose-resolved address when available (correct for pointer children). + // Fall back to tree.baseAddress + computeOffset for callers that don't supply it. + uint64_t addr; + if (resolvedAddr != 0) { + addr = resolvedAddr; + } else { + int64_t signedAddr = m_doc->tree.computeOffset(nodeIdx); + if (signedAddr < 0) return; // malformed tree: negative offset + addr = m_doc->tree.baseAddress + static_cast(signedAddr); + } // For vector components, redirect to float parsing at sub-offset NodeKind editKind = node.kind; diff --git a/src/controller.h b/src/controller.h index c28d690..c636299 100644 --- a/src/controller.h +++ b/src/controller.h @@ -92,7 +92,8 @@ public: void removeNode(int nodeIdx); void toggleCollapse(int nodeIdx); void materializeRefChildren(int nodeIdx); - void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false); + void setNodeValue(int nodeIdx, int subLine, const QString& text, + bool isAscii = false, uint64_t resolvedAddr = 0); void duplicateNode(int nodeIdx); void convertToTypedPointer(uint64_t nodeId); void splitHexNode(uint64_t nodeId); diff --git a/src/editor.cpp b/src/editor.cpp index 456d8f1..e7100fa 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -488,8 +488,10 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) { if (id == 1 && (m_editState.target == EditTarget::Type || m_editState.target == EditTarget::ArrayElementType || m_editState.target == EditTarget::PointerTarget)) { + const LineMeta* lm = metaForLine(m_editState.line); + uint64_t addr = lm ? lm->offsetAddr : 0; auto info = endInlineEdit(); - emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text); + emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text, addr); } }); @@ -1515,7 +1517,8 @@ bool RcxEditor::resolvedSpanFor(int line, EditTarget t, switch (t) { case EditTarget::Type: s = typeSpan(*lm, typeW); break; case EditTarget::Name: s = nameSpan(*lm, typeW, nameW); break; - case EditTarget::Value: s = valueSpan(*lm, textLen, typeW, nameW); break; + case EditTarget::Value: s = narrowPtrValueSpan(*lm, + valueSpan(*lm, textLen, typeW, nameW), lineText); break; case EditTarget::BaseAddress: break; // No longer on header lines case EditTarget::ArrayIndex: case EditTarget::ArrayCount: @@ -2368,8 +2371,12 @@ void RcxEditor::commitInlineEdit() { if (m_editState.target == EditTarget::Type && editedText.isEmpty()) editedText = m_editState.original; + // Grab resolved address from LineMeta before endInlineEdit clears state + const LineMeta* lm = metaForLine(m_editState.line); + uint64_t addr = lm ? lm->offsetAddr : 0; + auto info = endInlineEdit(); - emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, editedText); + emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, editedText, addr); } // ── Cancel inline edit ── @@ -2469,13 +2476,15 @@ void RcxEditor::showSourcePicker() { QAction* sel = menu.exec(pos); if (sel) { + const LineMeta* lm = metaForLine(m_editState.line); + uint64_t addr = lm ? lm->offsetAddr : 0; auto info = endInlineEdit(); QString text = sel->text(); if (sel->data().toString() == QStringLiteral("#clear")) text = QStringLiteral("#clear"); else if (sel->data().isValid()) text = QStringLiteral("#saved:") + QString::number(sel->data().toInt()); - emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text); + emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text, addr); } else { cancelInlineEdit(); } diff --git a/src/editor.h b/src/editor.h index ecf8c71..ceadd96 100644 --- a/src/editor.h +++ b/src/editor.h @@ -69,7 +69,8 @@ signals: void keywordConvertRequested(const QString& newKeyword); void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods); void inlineEditCommitted(int nodeIdx, int subLine, - EditTarget target, const QString& text); + EditTarget target, const QString& text, + uint64_t resolvedAddr = 0); void inlineEditCancelled(); void typeSelectorRequested(); void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);