2016-07-12 3 views
1

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?

Antwort

3

Wenn Sie dies tun:

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

Sie effektiv einen Verweis auf eine temporäre in einer Weise, die Bindung, die nicht Lebensdauer verlängert ist. operator[] gibt eine Referenz zurück, daher binden Sie keine Referenz an ein temporäres Objekt - Sie binden einen Verweis auf eine Referenz. Aber die zurückgegebene Referenz von operator[] bezieht sich auf ein Element in der zugrunde liegenden temporärevector von GetChildren() zurückgegeben, die am Ende der Zeile außerhalb des Umfangs geht, so dass Sie eine dangling Referenz.


Wenn Sie jedoch stattdessen versucht:

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

, die für die syntaktischen Zucker ist:

{ 
    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; 
     // ... 
    } 
} 

hier, *b ist keine vorübergehende oder ein Teil eines temporären - es ist eine Referenz in einen Container, dessen Lebensdauer so lange dauert wie __range, was durch den gesamten Körper der Schleife erfolgt. Keine hängende Referenz.Ähnlich


,

xmlNodePtr entry = GetChildren(child)[0]; 

ist nur das Kopieren, keinerlei Bezug Fragen.

+0

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

Verwandte Themen