Ich verbrachte gestern einige Zeit Debugging eine schöne Heisenbug, wo ein xmlNodePtr
's Typ würde mich ändern. Dieses Beispiel zeigt den Fehler:libxml2 xmlNodePtr Typ ändert sich, wenn ich es beobachte
#include <iostream>
#include <vector>
#include <string>
#include <memory> // std::unique_ptr
#include <cstdint>
#include <libxml/tree.h>
#include <libxml/parser.h>
struct SomeDataType {
std::vector<std::vector<std::string>> data;
explicit SomeDataType(uint32_t rows_, uint32_t columns_)
: data(rows_)
{
for (uint32_t row = 0; row < rows_; ++row) {
data[row].resize(columns_);
}
}
};
static std::vector<xmlNodePtr> GetChildren(xmlNodePtr node)
{
std::vector<xmlNodePtr> children;
xmlNodePtr child = node->children;
while (child) {
if (child->type == XML_ELEMENT_NODE) {
children.push_back(child);
}
child = child->next;
}
return children;
}
int main() {
std::unique_ptr<xmlDoc, void(*)(xmlDoc*)> document = { xmlParseEntity("libxml2-fail.xml"), xmlFreeDoc };
SomeDataType{ 3, 2 };
xmlNodePtr root = xmlDocGetRootElement(document.get());
for (const xmlNodePtr &child : GetChildren(root)) {
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
std::cout << entry->name << std::endl;
}
}
Zusammengestellt mit:
g++ -g -std=c++14 -Wall -Wextra -pedantic -I/usr/include/libxml2 libxml2-fail.cpp -lxml2 -o fail.out
Die XML-Datei:
<?xml version="1.0" encoding="utf-8"?>
<data>
<tag>
<subtag>1</subtag>
</tag>
</data>
Lauf gibt mir die folgende Ausgabe:
Expected 1 but was 17
Stepping durch mit gdb, alles i Bis wir die Linie const xmlNodePtr & = ...
erreichen. Anstelle des Typs XML_ELEMENT_NODE
hat es den Typ XML_ENTITY_DECL
. Allerdings, wenn ich die folgenden Befehle ausführen, die Referenz xmlNodePtr
verwandelt sich in der Art Ich erwarte, dass:
48 const xmlNodePtr &entry = GetChildren(child)[0];
(gdb) n
49 std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
(gdb) p *entry
$1 = {_private = 0x0, type = XML_ENTITY_DECL, name = 0x0, children = 0xb7e67d7c <std::string::_Rep::_S_empty_rep_storage+12>, last = 0x0, parent = 0x69, next = 0x0, prev = 0x9, doc = 0x0, ns = 0x805edb8, content = 0x805edb8 "", properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 60648, extra = 2053}
(gdb) p *child
$2 = {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ee98 "tag", children = 0x805eea8, last = 0x805ef98, parent = 0x805edb8, next = 0x805efe8, prev = 0x805ee08, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 3, extra = 0}
(gdb) p GetChildren(child)
$3 = std::vector of length 1, capacity 1 = {0x805eef8}
(gdb) p *entry
$4 = {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ef38 "subtag", children = 0x805ef48, last = 0x805ef48, parent = 0x805ee58, next = 0x805ef98, prev = 0x805eea8, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 4, extra = 0}
(gdb)
Ich habe nicht das Problem, wenn ich stattdessen eine Schleife über dem Element ein etwa so:
for (const xmlNodePtr &entry : GetChildren(child)) {
...
}
ich habe auch nicht das Problem, wenn ich die xmlNodePtr
wie eine konstante Referenz machen nicht so:
xmlNodePtr entry = GetChildren(child)[0];
Doch nach this stackoverflow question sollte es kein Problem sein.
Die SomeDataType
Struktur ist seltsam notwendig; Ansonsten bekomme ich einen segfault, weil entry
ein Nullzeiger wird.
Woher kommt dieser Fehler?
Okay, das macht Sinn. Ich denke, dass gdb den Rückgabewert von 'GetChildren' speichert, was gerade dazu führt, dass die Referenz wieder gültig ist (UB). – Justin