J'ai passé quelque temps hier à déboguer un charmant Heisenbug où un type de xmlNodePtr
changerait sur moi. Cet exemple montre l'erreur:libxml2 Le type de xmlNodePtr change quand je l'observe
#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;
}
}
Compilé avec:
g++ -g -std=c++14 -Wall -Wextra -pedantic -I/usr/include/libxml2 libxml2-fail.cpp -lxml2 -o fail.out
Le fichier xml:
<?xml version="1.0" encoding="utf-8"?>
<data>
<tag>
<subtag>1</subtag>
</tag>
</data>
course me donne le résultat suivant:
Expected 1 but was 17
Parcourant avec gdb, tout ce que je s bien jusqu'à ce que nous atteignions la ligne const xmlNodePtr & = ...
. Au lieu d'avoir le type XML_ELEMENT_NODE
, il a le type XML_ENTITY_DECL
. Cependant, si je lance les commandes suivantes, la référence xmlNodePtr
dans le type morphes je me attendre:
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)
Je n'ai pas le problème quand je boucle à la place sur l'un des éléments comme ceci:
for (const xmlNodePtr &entry : GetChildren(child)) {
...
}
Je
aussi ne pas le problème quand je ne fais pas la xmlNodePtr
une référence const comme ceci:
xmlNodePtr entry = GetChildren(child)[0];
Toutefois, selon this stackoverflow question, ça ne devrait pas poser de problème.
La structure SomeDataType
est étrangement nécessaire; sinon, je reçois un segfault car entry
devient un pointeur nul.
D'où vient ce bogue?
D'accord, c'est logique. Je suppose que gdb stocke la valeur de retour de 'GetChildren' qui vient de rendre la référence valide à nouveau (UB si) – Justin