I am building a program that does a merge validation between two xml files using libxml2 v2.11.
In summary, I am creating a copy of a target dom to then replace all nodes with the replacement dom's nodes, only if target node has a property "overloadable" equal to true. Finally, I validate the result dom against a schema and schematron file.
Currently, this code does it (Sorry if it is messy, I had been debugging a lot over it) :
/**
* replace_nodes:
* @target_node: A node to be replaced
* @replace_node: A node to copy
* @Output: 0 on Success or error code.
*
* Replace target_node with a copy of replace_node. This function changes the dom tree.
*/
static xmlNodePtr replace_node(xmlNodePtr target_node, xmlNodePtr replace_node){
xmlNodePtr new_target_node = xmlCopyNode(replace_node,1);
xmlSetProp(new_target_node, (xmlChar *) "overloadable", (xmlChar *) "true");
xmlReplaceNode (target_node, new_target_node);
DEV_DEBUG_PRINT("Replaced node %s \n", replace_node->name);
return new_target_node;
}
int validate_overloadables_merge(xmlDocPtr target_dom, xmlDocPtr replace_dom){
struct xpath_exp {
char target_xpath_exp[100];
char replace_xpath_exp[100];
};
struct xpath_exp find_xpr_tb =
{
.target_xpath_exp = "/configuration",
.replace_xpath_exp = "/configurations/replacement/*",
};
int ret = 0;
// copy target dom
xmlDocPtr copy_dom = xmlCopyDoc(target_dom, 1);
xmlChar xpath_expr_replace[STR_LEN], xpath_expr_target[STR_LEN];
xmlXPathObjectPtr xpath_obj_target, xpath_obj_replace;
strcpy((char *)xpath_expr_target, find_xpr_tb.target_xpath_exp);
strcpy((char *)xpath_expr_replace, find_xpr_tb.replace_xpath_exp);
// Get all inputs from target dom with an xpath expr
int ret_target = query_dom(xpath_expr_target, &xpath_obj_target, copy_dom);
// Get all inputs from replacement dom with an xpath expr
int ret_replace = query_dom(xpath_expr_replace, &xpath_obj_replace, replace_dom);
// Error handling in case there is not matches
if(ret_target == -EACCES || ret_replace == -EACCES)
{
ERR_MSG("Error: Fail to query\n");
if(ret_target != -EACCES)
{
xmlXPathFreeObject(xpath_obj_target);
}
xmlFreeDoc(copy_dom);
return -EACCES;
}
else if (ret_target == -ESRCH || ret_replace == -ESRCH ||
xpath_obj_target->nodesetval->nodeNr < 1 ||
xpath_obj_replace->nodesetval->nodeNr < 1)
{
DEV_DEBUG_PRINT("DEBUG_INFO: No replaceomization nodes found\n");
xmlXPathFreeObject(xpath_obj_target);
xmlXPathFreeObject(xpath_obj_replace);
xmlFreeDoc(copy_dom);
}
// Get first node to potentially replace it
xmlNodePtr target_node = xpath_obj_target->nodesetval->nodeTab[0];
while(target_node)
{
// Get next sibling to potentially replace it. This is done here as we might free the target node
xmlNodePtr nx_target_node = target_node->next;
char * overloadable = (char *) xmlGetProp(target_node, (xmlChar *) "overloadable");
//If overloadable, we replace it
if(overloadable != NULL && 0==strcmp(overloadable,"true"))
{
for(int replace_idx=0; replace_idx < xpath_obj_replace->nodesetval->nodeNr; replace_idx++){
xmlNodePtr replace_node = xpath_obj_replace->nodesetval->nodeTab[replace_idx];
if(0==strcmp((char *)target_node->name,
(char *)replace_node->name))
{
xmlNodePtr new_target_node = replace_node(target_node, replace_node);
//xmlFreeNode(target_node);
xmlReconciliateNs(copy_dom, new_target_node);
}
}
}
if(overloadable)
{
xmlFree(overloadable);
}
target_node = nx_target_node;
}
if( is_conf_sct && ret >= 0){
DEV_DEBUG_PRINT("DEBUG_INFO: Validating copy sct\n");
ret = xml_validate_sct(copy_dom, conf_sct_path);
}
if(is_conf_xsd && ret >= 0){
DEV_DEBUG_PRINT("DEBUG_INFO: Validating copy xsd\n");
ret = xml_validate_xsd(copy_dom, conf_xsd_path);
}
xmlXPathFreeObject(xpath_obj_target);
xmlXPathFreeObject(xpath_obj_replace);
xmlFreeDoc(copy_dom);
return ret;
}
The problems comes when I pass valgrind for identifying memory leaks:
jmgs@esswlab3:~/Source/config$ valgrind --leak-check=full ./config inputs/config.xml -c inputs/custom.xml -x schemas/config.xsd -s schematrons/config.sct
==96255== Memcheck, a memory error detector
==96255== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==96255== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==96255== Command: ./config inputs/config.xml -c inputs/custom.xml -x schemas/config.xsd -s schematrons/config.sct
==96255==
Document inputs/config.xml loaded
Document complient with schema
Document complient with schematron
Replaced node input_1
Replaced node input_4
Document complient with schematron
Document complient with schema
==96255==
==96255== HEAP SUMMARY:
==96255== in use at exit: 3,343 bytes in 52 blocks
==96255== total heap usage: 54,905 allocs, 54,853 frees, 5,176,922 bytes allocated
==96255==
==96255== 3,343 (240 direct, 3,103 indirect) bytes in 2 blocks are definitely lost in loss record 8 of 8
==96255== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96255== by 0x49B550F: xmlStaticCopyNode (tree.c:4311)
==96255== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
==96255== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
==96255== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
==96255== by 0x10DFE8: validate_overloadables_merge (in /home/jmgs/Source/config)
==96255== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config-manager)
==96255== by 0x10FBFE: method_load_customization (in /home/jmgs/Source/config)
==96255== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96255== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96255== by 0x111683: config_register_dbus (in /home/jmgs/Source/config)
==96255== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96255==
==96255== LEAK SUMMARY:
==96255== definitely lost: 240 bytes in 2 blocks
==96255== indirectly lost: 3,103 bytes in 50 blocks
==96255== possibly lost: 0 bytes in 0 blocks
==96255== still reachable: 0 bytes in 0 blocks
==96255== suppressed: 0 bytes in 0 blocks
==96255==
==96255== For lists of detected and suppressed errors, rerun with: -s
==96255== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0
My guess is that the memory leak comes from not freeing the target_node when it is replaced. However, when i do this I get the following valgrind error message:
==96577== Memcheck, a memory error detector
==96577== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==96577== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==96577== Command: ./config inputs/config.xml -c inputs/custom.xml -x schemas/config.xsd -s schematrons/config.sct
==96577==
Document inputs/config.xml loaded
Document complient with schema
Document complient with schematron
Document inputs/custom.xml loaded
Replaced node input_1
==96577== Invalid read of size 8
==96577== at 0x10E229: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Address 0x54811c0 is 16 bytes inside a block of size 120 free'd
==96577== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x10E298: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Block was alloc'd at
==96577== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49B550F: xmlStaticCopyNode (tree.c:4311)
==96577== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
==96577== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
==96577== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
==96577== by 0x10DFE8: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577==
==96577== Invalid read of size 1
==96577== at 0x484FBD4: strcmp (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x10E239: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Address 0x5481270 is 0 bytes inside a block of size 7 free'd
==96577== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49B1512: xmlFreeNode (tree.c:3892)
==96577== by 0x49B1512: xmlFreeNode (tree.c:3838)
==96577== by 0x10E298: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Block was alloc'd at
==96577== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49CA8B5: xmlStrndup (xmlstring.c:49)
==96577== by 0x49B58B4: xmlStaticCopyNode (tree.c:4331)
==96577== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
==96577== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
==96577== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
==96577== by 0x10DFE8: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577==
==96577== Invalid read of size 1
==96577== at 0x484FBE8: strcmp (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x10E239: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Address 0x5481271 is 1 bytes inside a block of size 7 free'd
==96577== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49B1512: xmlFreeNode (tree.c:3892)
==96577== by 0x49B1512: xmlFreeNode (tree.c:3838)
==96577== by 0x10E298: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Block was alloc'd at
==96577== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49CA8B5: xmlStrndup (xmlstring.c:49)
==96577== by 0x49B58B4: xmlStaticCopyNode (tree.c:4331)
==96577== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
==96577== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
==96577== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
==96577== by 0x10DFE8: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577==
Replaced node input_4
Document complient with schematron
Document complient with schema
==96577== Invalid read of size 4
==96577== at 0x4A4A2FA: xmlXPathFreeNodeSet (xpath.c:4115)
==96577== by 0x4A4C541: xmlXPathFreeObject (xpath.c:5467)
==96577== by 0x10E1A6: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Address 0x54811b8 is 8 bytes inside a block of size 120 free'd
==96577== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x10E298: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577== Block was alloc'd at
==96577== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==96577== by 0x49B550F: xmlStaticCopyNode (tree.c:4311)
==96577== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
==96577== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
==96577== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
==96577== by 0x10DFE8: validate_overloadables_merge (in /home/jmgs/Source/config)
==96577== by 0x10D0DC: config_load_cust_to_shm (in /home/jmgs/Source/config)
==96577== by 0x10FC0E: method_load_customization (in /home/jmgs/Source/config)
==96577== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
==96577== by 0x111693: config_register_dbus (in /home/jmgs/Source/config)
==96577== by 0x10CD2B: main (in /home/jmgs/Source/config)
==96577==
Document complient with schematron
Document complient with schema
==96577==
==96577== HEAP SUMMARY:
==96577== in use at exit: 0 bytes in 0 blocks
==96577== total heap usage: 54,910 allocs, 54,910 frees, 5,177,146 bytes allocated
==96577==
==96577== All heap blocks were freed -- no leaks are possible
==96577==
==96577== For lists of detected and suppressed errors, rerun with: -s
==96577== ERROR SUMMARY: 9 errors from 4 contexts (suppressed: 0 from 0)
Thanks in advance.
I have discovered the bug that triggers valgrind's Invalid Read message. I am accessing (within the if statement) target_node, once already freed strcmp((char *)target_node->name
. A valid solution would be to add a break statement:
for(int replace_idx=0; replace_idx < xpath_obj_replace->nodesetval->nodeNr; replace_idx++)
{
xmlNodePtr replace_node = xpath_obj_replace->nodesetval->nodeTab[replace_idx];
if(0==strcmp((char *)target_node->name,
(char *)replace_node->name))
{
xmlNodePtr new_target_node = replace_node(target_node, replace_node);
xmlFreeNode(target_node);
xmlReconciliateNs(copy_dom, new_target_node);
break;
}
}
As piece of advice (that I would have liked to have) for similar situations, do not forget to compile with debug flag -g. This lets valgrind produce a more detailed report. E.g. In my case
┆ ==100275== Invalid read of size 8
┆ ==100275== at 0x10EC7F: validate_overloadables_merge (config-parsing.c:465)
┆ ==100275== by 0x10CDCF: config_load_cust_to_shm (config.c:111)
┆ ==100275== by 0x111685: method_load_customization (config-sdbus.c:46)
┆ ==100275== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x114CE5: config_register_dbus (config-sdbus.c:807)
┆ ==100275== by 0x10D741: main (config.c:328)
┆ ==100275== Address 0x5cf2460 is 16 bytes inside a block of size 120 free'd
┆ ==100275== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
┆ ==100275== by 0x10ECB9: validate_overloadables_merge (config-parsing.c:470)
┆ ==100275== by 0x10CDCF: config_load_cust_to_shm (config.c:111)
┆ ==100275== by 0x111685: method_load_customization (config-sdbus.c:46)
┆ ==100275== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x114CE5: config_register_dbus (config-sdbus.c:807)
┆ ==100275== by 0x10D741: main (config.c:328)
┆ ==100275== Block was alloc'd at
┆ ==100275== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
┆ ==100275== by 0x49B550F: xmlStaticCopyNode (tree.c:4311)
┆ ==100275== by 0x49B569E: xmlStaticCopyNode (tree.c:4422)
┆ ==100275== by 0x49B5BB3: xmlStaticCopyNodeList (tree.c:4495)
┆ ==100275== by 0x49B60A0: xmlCopyDoc (tree.c:4716)
┆ ==100275== by 0x10E9C5: validate_overloadables_merge (config-parsing.c:423)
┆ ==100275== by 0x10CDCF: config_load_cust_to_shm (config.c:111)
┆ ==100275== by 0x111685: method_load_customization (config-sdbus.c:46)
┆ ==100275== by 0x48B327A: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x48C176C: ??? (in /usr/lib/x86_64-linux-gnu/libsystemd.so.0.32.0)
┆ ==100275== by 0x114CE5: config_register_dbus (config-sdbus.c:807)
┆ ==100275== by 0x10D741: main (config.c:328)
I also recommend this other reading: How do I use valgrind to find memory leaks?