Skip to content

Fix xmlDestroyNode not removing nodes from saved XML files#5002

Open
QueryOfficial wants to merge 1 commit into
multitheftauto:masterfrom
QueryOfficial:fix/xml-destroy-node-save
Open

Fix xmlDestroyNode not removing nodes from saved XML files#5002
QueryOfficial wants to merge 1 commit into
multitheftauto:masterfrom
QueryOfficial:fix/xml-destroy-node-save

Conversation

@QueryOfficial

@QueryOfficial QueryOfficial commented Jul 4, 2026

Copy link
Copy Markdown

Summary

Fix xmlDestroyNode so deleted nodes are actually removed from the saved XML file.

CXMLNodeImpl::~CXMLNodeImpl() now keeps the underlying tinyxml2 document in sync with the wrapper tree: wrapper children are deleted first (each child unlinks its own tinyxml2 node), then the destroyed node is removed from its parent via DeleteChild(). m_pNode is nullified before wrapper teardown completes so DeleteWrapper / full document unload does not double-unlink.

Motivation

Fixes #5001.

After the tinyxml → tinyxml2 migration (#4942), per-node DOM removal was dropped from the destructor to avoid ownership conflicts during full file teardown. That path is still safe via DeleteWrapper, but xmlDestroyNode only removed the MTA wrapper from m_Children — the tinyxml2 tree was unchanged, so xmlSaveFile wrote stale content.

Symptoms:

  • xmlDestroyNode + xmlSaveFile leaves deleted children in the file
  • Map editor: second save fails with "Map resource could not be saved" because meta.xml still lists a <map> entry after xmlDestroyNode, while the .map file was already removed (addResourceMap treats it as a duplicate)

Test plan

Run the reproduction script in a test resource and confirm the chat output is map exists: false and the saved file contains only <meta/>.

In the map editor, place an object, save, then save again. The second save should complete without the "Map resource could not be saved" error.

Load an XML file with xmlLoadFile and unload it with xmlUnloadFile without manually destroying child nodes. Confirm there is no crash or double-free.

Load XML with xmlLoadString, destroy a child node with xmlDestroyNode, and confirm the node is removed from the in-memory tree.

Regression checks: ACL save, resource settings via getResourceInfo() / CopyNode, and basic xmlCreateChild + xmlSaveFile flows should still work as before.

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

Reproduction script

addEventHandler("onResourceStart", resourceRoot, function()
    local path = ":".. getResourceName(getThisResource()) .."/test.xml"
    local xml = xmlCreateFile(path, "meta")
    xmlCreateChild(xml, "map")
    xmlSaveFile(xml)

    xmlDestroyNode(xmlFindChild(xml, "map", 0))
    xmlSaveFile(xml)
    xmlUnloadFile(xml)

    local test = xmlLoadFile(path)
    outputChatBox("map exists: ".. tostring(xmlFindChild(test, "map", 0) ~= false))
    xmlUnloadFile(test)
end)

…ocument synchronization

- Updated the destructor to first delete wrapper children, allowing each child to unlink its own tinyxml2 node before the parent is removed.
- Added logic to keep the underlying document in sync when nodes are destroyed via xmlDestroyNode, ensuring m_pNode is nullified before deletion to prevent unlinking issues during document teardown.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

xmlDestroyNode doesn't remove the child when you save

1 participant