2016-07-12 1 views
1

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?

Répondre

3

Lorsque vous faites ceci:

const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here... 

Vous lier efficacement une référence à un temporaire d'une manière qui n'est pas grande durée de vie. operator[] renvoie une référence, donc vous ne liez pas une référence à un temporaire - vous liez une référence à une référence. Mais cette référence renvoyée par operator[] fait référence à un élément du sous-jacente vector retourné par GetChildren() qui est hors de portée à la fin de la ligne, vous laissant une référence en suspens.


Toutefois, lorsque vous à la place essayé:

for (const xmlNodePtr &entry : GetChildren(child)) { 

qui est du sucre syntaxique pour:

{ 
    auto&& __range = GetChildren(child); // bind temporary to reference 
             // lifetime IS extended 
    auto b = begin(__range); 
    auto e = end(__range); 
    for (; b != e; ++b) { 
     const xmlNodePtr& entry = *b; 
     // ... 
    } 
} 

ici, *b n'est pas temporaire ou une partie d'un temporaire - il est une référence dans un conteneur dont la durée de vie dure aussi longtemps que __range, qui est à travers le corps entier de la boucle. Pas de référence qui pendouille


De même,

xmlNodePtr entry = GetChildren(child)[0]; 

est juste à copier, aucun problème de référence que ce soit.

+0

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