Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't cleanup doc reference of unlinked node. #34

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ZigzagAK
Copy link
Contributor

This is cause if incorrect free in xmlFreeNode (DICT_FREE(cur->name)).

local xmlua = require "xmlua"

local xml = [[<?xml version='1.0' encoding = 'windows-1251'?>
<ROOT/>]]

local doc = xmlua.XML.parse(xml)
doc:root():unlink()

collectgarbage("collect")
collectgarbage("collect")

debug.log

@kou
Copy link
Member

kou commented Dec 14, 2023

This was introduced to solve #24.
If we use this, we have another problem.

@ZigzagAK
Copy link
Contributor Author

Cleanup doc reference is incorrect. We need to solve problem of lifetime document and node because node is a part of document and it must be freed before document. In #24 node created in one document is moved to another. I don't know correctly it or not.
I think to solve problem with simple unlink we may add remove method

function libxml2.xmlRemoveNode(node)
  xml2.xmlUnlinkNode(node)
  xml2.xmlFreeNode(node)
end

@ZigzagAK
Copy link
Contributor Author

xmlua have big problem with lifetime of XmlNode. Every time while we use XPath or any other methods that wrap XmlNode to ffi.gc we create unique lua object with same C-level pointers. This is cause of all memory issues.

For example:

local xmlua = require "xmlua"

local xml = [[<?xml version='1.0' encoding = 'windows-1251'?>
<ROOT>
   <A>
        <B>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </B>
        <B>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </B>
        <X>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </X>
        <X>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </X>
   </A>
</ROOT>]]

local doc = xmlua.XML.parse(xml)

local a = doc:search("/ROOT/A")
local b = doc:search("/ROOT/A/B")

a:unlink()
a = nil

collectgarbage("collect")
collectgarbage("collect")

b:unlink()
b = nil

collectgarbage("collect")
collectgarbage("collect")

In case:

function libxml2.xmlUnlinkNode(node)
  xml2.xmlUnlinkNode(node)
  --xml2.xmlSetTreeDoc(node, ffi.NULL)
  return ffi.gc(node, xml2.xmlFreeNode)
end

we have errors like:

==175939== Invalid read of size 4
==175939== at 0x5E02D9D: xmlUnlinkNode (tree.c:3818)
==175939== by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==175939== by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==175939== by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==175939== by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==175939== by 0x423672: lua_pcall (lj_api.c:1145)
==175939== by 0x405D06: docall (luajit.c:122)
==175939== by 0x405D06: handle_script (luajit.c:292)
==175939== by 0x405D06: pmain (luajit.c:550)
==175939== by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==175939== by 0x4236CC: lua_cpcall (lj_api.c:1173)
==175939== by 0x404343: main (luajit.c:581)
==175939== Address 0x5a07f38 is 8 bytes inside a block of size 120 free'd
==175939== at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==175939== by 0x5E0333B: xmlFreeNodeList__internal_alias (tree.c:3721)
==175939== by 0x5E0333B: xmlFreeNodeList (tree.c:3665)
==175939== by 0x5E036E4: xmlFreeNode__internal_alias (tree.c:3766)
==175939== by 0x5E036E4: xmlFreeNode (tree.c:3735)
==175939== by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==175939== by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==175939== by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==175939== by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==175939== by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==175939== by 0x40B620: gc_finalize (lj_gc.c:555)
==175939== by 0x40B620: gc_onestep (lj_gc.c:706)
==175939== by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==175939== by 0x423CD4: lua_gc (lj_api.c:1295)
==175939== by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==175939== Block was alloc'd at
==175939== at 0x4C38185: malloc (vg_replace_malloc.c:431)
==175939== by 0x5E01ED4: xmlNewNodeEatName (tree.c:2281)
==175939== by 0x5E062A1: xmlNewDocNodeEatName (tree.c:2356)
==175939== by 0x5EAC8BB: xmlSAX2StartElementNs (SAX2.c:2278)
==175939== by 0x5DF596D: xmlParseStartTag2 (parser.c:9645)
==175939== by 0x5DF966E: xmlParseElement (parser.c:9992)
==175939== by 0x5DF8CD5: xmlParseContent (parser.c:9910)
==175939== by 0x5DF9588: xmlParseElement (parser.c:10078)
==175939== by 0x5DF8CD5: xmlParseContent (parser.c:9910)
==175939== by 0x5DF9588: xmlParseElement (parser.c:10078)
==175939== by 0x5DF9C89: xmlParseDocument (parser.c:10775)
==175939== by 0x5DFA096: xmlDoRead (parser.c:15247)

with

function libxml2.xmlUnlinkNode(node)
  xml2.xmlUnlinkNode(node)
  xml2.xmlSetTreeDoc(node, ffi.NULL)
  return ffi.gc(node, xml2.xmlFreeNode)
end

we have errors like:

==176221== Invalid free() / delete / delete[] / realloc()
==176221== at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==176221== by 0x5E033A7: xmlFreeNodeList__internal_alias (tree.c:3703)
==176221== by 0x5E033A7: xmlFreeNodeList (tree.c:3665)
==176221== by 0x5E036E4: xmlFreeNode__internal_alias (tree.c:3766)
==176221== by 0x5E036E4: xmlFreeNode (tree.c:3735)
==176221== by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==176221== by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==176221== by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==176221== by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==176221== by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==176221== by 0x40B620: gc_finalize (lj_gc.c:555)
==176221== by 0x40B620: gc_onestep (lj_gc.c:706)
==176221== by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==176221== by 0x423CD4: lua_gc (lj_api.c:1295)
==176221== by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==176221== Address 0x59f3523 is 99 bytes inside a block of size 1,048 alloc'd
==176221== at 0x4C38185: malloc (vg_replace_malloc.c:431)
==176221== by 0x5EA895A: xmlDictAddString.isra.0 (dict.c:270)
==176221== by 0x5EA94A4: xmlDictLookup (dict.c:955)
==176221== by 0x5DE5F9C: xmlDetectSAX2 (parser.c:1113)
==176221== by 0x5DF99B9: xmlParseDocument (parser.c:10657)
==176221== by 0x5DFA096: xmlDoRead (parser.c:15247)
==176221== by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==176221== by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==176221== by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==176221== by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==176221== by 0x423672: lua_pcall (lj_api.c:1145)
==176221== by 0x405D06: docall (luajit.c:122)
==176221== by 0x405D06: handle_script (luajit.c:292)
==176221== by 0x405D06: pmain (luajit.c:550)

And i don't know how to resolve this problem.

@ZigzagAK
Copy link
Contributor Author

ZigzagAK commented Dec 15, 2023

IMHO, to solve partially the problem xml2.xmlSetTreeDoc(node, ffi.NULL) must be removed from unlink. This solves problem with free unlinked node.

This:

function libxml2.xmlUnlinkNode(node)
  xml2.xmlUnlinkNode(node)
  xml2.xmlSetTreeDoc(node, ffi.NULL)
  return ffi.gc(node, xml2.xmlFreeNode)
end

absolutely incorrect and this is a crash in most cases in xmlFreeNode.

@ZigzagAK
Copy link
Contributor Author

Small changes in #24:

local xmlua = require("xmlua")

local function add_child(parent, child)
  return parent:add_child(child)
end

local function create_element(name, attributes)
  local elm = xmlua.XML.parse("<?xml version='1.0' encoding = 'windows-1251'?><" .. name .. "/>"):root()

  if attributes then
    for att_name, val in pairs(attributes) do
      elm:set_attribute(att_name, val)
    end
  end

  return elm:unlink()
end

do
  local a = create_element("a", {
    ["a1"] = "a1-test",
    ["a2"]  = "a2-test"
  })

  local b = create_element("b", {
    ["b1"] = "b1-test",
    ["b2"]  = "b2-test"
  })

  add_child(a, b)
end

collectgarbage("collect")
collectgarbage("collect")
function libxml2.xmlUnlinkNode(node)
  xml2.xmlUnlinkNode(node)
  xml2.xmlSetTreeDoc(node, ffi.NULL)
  return ffi.gc(node, xml2.xmlFreeNode)
end

produces:

==216730== Memcheck, a memory error detector
==216730== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==216730== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==216730== Command: lua test24.lua
==216730== Parent PID: 66196
==216730== 
==216730== Invalid free() / delete / delete[] / realloc()
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5E034B5: xmlFreeProp (tree.c:2098)
==216730==    by 0x5E03608: xmlFreePropList__internal_alias (tree.c:2072)
==216730==    by 0x5E03608: xmlFreePropList (tree.c:2067)
==216730==    by 0x5E032CA: xmlFreeNodeList__internal_alias (tree.c:3697)
==216730==    by 0x5E032CA: xmlFreeNodeList (tree.c:3665)
==216730==    by 0x5E036E4: xmlFreeNode__internal_alias (tree.c:3766)
==216730==    by 0x5E036E4: xmlFreeNode (tree.c:3735)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==  Address 0x5a09929 is 89 bytes inside a block of size 1,048 free'd
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5EA9186: xmlDictFree__internal_alias (dict.c:841)
==216730==    by 0x5EA9186: xmlDictFree (dict.c:793)
==216730==    by 0x5DE3F2C: xmlFreeParserCtxt__internal_alias.part.11 (parserInternals.c:1793)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==    by 0x423CD4: lua_gc (lj_api.c:1295)
==216730==    by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==216730==  Block was alloc'd at
==216730==    at 0x4C38185: malloc (vg_replace_malloc.c:431)
==216730==    by 0x5EA895A: xmlDictAddString.isra.0 (dict.c:270)
==216730==    by 0x5EA94A4: xmlDictLookup (dict.c:955)
==216730==    by 0x5DE5F9C: xmlDetectSAX2 (parser.c:1113)
==216730==    by 0x5DF99B9: xmlParseDocument (parser.c:10657)
==216730==    by 0x5DFA096: xmlDoRead (parser.c:15247)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x423672: lua_pcall (lj_api.c:1145)
==216730==    by 0x405D06: docall (luajit.c:122)
==216730==    by 0x405D06: handle_script (luajit.c:292)
==216730==    by 0x405D06: pmain (luajit.c:550)
==216730== 
==216730== Invalid free() / delete / delete[] / realloc()
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5E03334: xmlFreeNodeList__internal_alias (tree.c:3720)
==216730==    by 0x5E03334: xmlFreeNodeList (tree.c:3665)
==216730==    by 0x5E036E4: xmlFreeNode__internal_alias (tree.c:3766)
==216730==    by 0x5E036E4: xmlFreeNode (tree.c:3735)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==    by 0x423CD4: lua_gc (lj_api.c:1295)
==216730==    by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==216730==  Address 0x5a09927 is 87 bytes inside a block of size 1,048 free'd
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5EA9186: xmlDictFree__internal_alias (dict.c:841)
==216730==    by 0x5EA9186: xmlDictFree (dict.c:793)
==216730==    by 0x5DE3F2C: xmlFreeParserCtxt__internal_alias.part.11 (parserInternals.c:1793)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==    by 0x423CD4: lua_gc (lj_api.c:1295)
==216730==    by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==216730==  Block was alloc'd at
==216730==    at 0x4C38185: malloc (vg_replace_malloc.c:431)
==216730==    by 0x5EA895A: xmlDictAddString.isra.0 (dict.c:270)
==216730==    by 0x5EA94A4: xmlDictLookup (dict.c:955)
==216730==    by 0x5DE5F9C: xmlDetectSAX2 (parser.c:1113)
==216730==    by 0x5DF99B9: xmlParseDocument (parser.c:10657)
==216730==    by 0x5DFA096: xmlDoRead (parser.c:15247)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x423672: lua_pcall (lj_api.c:1145)
==216730==    by 0x405D06: docall (luajit.c:122)
==216730==    by 0x405D06: handle_script (luajit.c:292)
==216730==    by 0x405D06: pmain (luajit.c:550)
==216730== 
==216730== Invalid free() / delete / delete[] / realloc()
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5E034B5: xmlFreeProp (tree.c:2098)
==216730==    by 0x5E03708: xmlFreePropList__internal_alias (tree.c:2072)
==216730==    by 0x5E03708: xmlFreePropList__internal_alias (tree.c:2067)
==216730==    by 0x5E03708: xmlFreeNode__internal_alias (tree.c:3771)
==216730==    by 0x5E03708: xmlFreeNode (tree.c:3735)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==    by 0x423CD4: lua_gc (lj_api.c:1295)
==216730==    by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==216730==  Address 0x59f3519 is 89 bytes inside a block of size 1,048 alloc'd
==216730==    at 0x4C38185: malloc (vg_replace_malloc.c:431)
==216730==    by 0x5EA895A: xmlDictAddString.isra.0 (dict.c:270)
==216730==    by 0x5EA94A4: xmlDictLookup (dict.c:955)
==216730==    by 0x5DE5F9C: xmlDetectSAX2 (parser.c:1113)
==216730==    by 0x5DF99B9: xmlParseDocument (parser.c:10657)
==216730==    by 0x5DFA096: xmlDoRead (parser.c:15247)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x423672: lua_pcall (lj_api.c:1145)
==216730==    by 0x405D06: docall (luajit.c:122)
==216730==    by 0x405D06: handle_script (luajit.c:292)
==216730==    by 0x405D06: pmain (luajit.c:550)
==216730== 
==216730== Invalid free() / delete / delete[] / realloc()
==216730==    at 0x4C3AC2B: free (vg_replace_malloc.c:974)
==216730==    by 0x5E0373D: xmlFreeNode__internal_alias (tree.c:3789)
==216730==    by 0x5E0373D: xmlFreeNode (tree.c:3735)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x40A213: gc_call_finalizer.isra.3 (lj_gc.c:520)
==216730==    by 0x40B620: gc_finalize (lj_gc.c:555)
==216730==    by 0x40B620: gc_onestep (lj_gc.c:706)
==216730==    by 0x40BB97: lj_gc_fullgc (lj_gc.c:798)
==216730==    by 0x423CD4: lua_gc (lj_api.c:1295)
==216730==    by 0x43A690: lj_cf_collectgarbage (lib_base.c:470)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==  Address 0x59f3517 is 87 bytes inside a block of size 1,048 alloc'd
==216730==    at 0x4C38185: malloc (vg_replace_malloc.c:431)
==216730==    by 0x5EA895A: xmlDictAddString.isra.0 (dict.c:270)
==216730==    by 0x5EA94A4: xmlDictLookup (dict.c:955)
==216730==    by 0x5DE5F9C: xmlDetectSAX2 (parser.c:1113)
==216730==    by 0x5DF99B9: xmlParseDocument (parser.c:10657)
==216730==    by 0x5DFA096: xmlDoRead (parser.c:15247)
==216730==    by 0x40A005: lj_vm_ffi_call (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x464D4E: lj_ccall_func (lj_ccall.c:1382)
==216730==    by 0x43D05C: lj_cf_ffi_meta___call (lib_ffi.c:230)
==216730==    by 0x407BE5: lj_BC_FUNCC (in /usr/local/bin/luajit-2.1.0-beta3)
==216730==    by 0x423672: lua_pcall (lj_api.c:1145)
==216730==    by 0x405D06: docall (luajit.c:122)
==216730==    by 0x405D06: handle_script (luajit.c:292)
==216730==    by 0x405D06: pmain (luajit.c:550)

xml2.xmlSetTreeDoc(node, ffi.NULL) - is not a guard from the problem.

@ZigzagAK
Copy link
Contributor Author

@kou I pushed various fixes for memory issues. Also fixed problem with moving unlinked node to another document.
Problem with multiple references to same xmlNode returned by XPath still exists. I don't know how to fix it.

Copy link
Member

@kou kou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we don't merge a PR that has multiple logical changes.
You should split this to one PR per logical change.

I don't think your point (calling xml2.xmlSetTreeDoc(node, ffi.NULL) in libxml2.xmlUnlinkNode() is wrong) is the real problem.
I think that the real problem is xmlFreeDoc()/xmlFreeDoc() order. If both of xmlDoc and xmlNode are GC targets, LuaJIT may use freeing xmlDoc and then freeing xmlNode order. I think that it's the real problem.

If we think that "xmlua" is responsible for the case, "xmlua" needs to do something for the case.
If we think that "users" are responsible for the case, "xmlua" just needs to provide Node:free() that calls libxml2.xmlFreeNode() and "users" use it for the case.

@ZigzagAK
Copy link
Contributor Author

ZigzagAK commented Dec 16, 2023

@kou
In this patch i have add document reference to element and context reference to document. In case of unlinking node from document it still holds reference to it's document and document will be freed after freeing the element.
In case of moving node between documents we need to make copy of node with xmlDocCopyNode (for ex: https://github.com/GNOME/libxml2/blob/master/tree.c#L4207) method. This method fixes dict references to new document.

@ZigzagAK
Copy link
Contributor Author

ZigzagAK commented Dec 16, 2023

I don't think your point (calling xml2.xmlSetTreeDoc(node, ffi.NULL) in libxml2.xmlUnlinkNode() is wrong) is the real problem.

If we reset to NULL doc reference of cur->node (for example) this line https://github.com/GNOME/libxml2/blob/master/tree.c#L3775 will be work incorrectly. It will not see cur->doc->dict and try to use xmlFree(cur->name). This is a cause of Invalid Free because cur->name - is a dictionary entry. It may be a small chunk of big chunk allocated memory.

@ZigzagAK
Copy link
Contributor Author

Ok. I can split this patch but some later because i must fix issues in this library for production use. Now i want to make it stable for my cases. After that i will split it.

@kou
Copy link
Member

kou commented Dec 16, 2023

In case of unlinking node from document it still holds reference to it's document and document will be freed after freeing the element.

Is this really change this situation?
If both of the unlinked node and its original document are GCed in the same GC, does LuaJIT really care free order? (Can we assume freeing its original document and then freeing unlinked node order?)

@ZigzagAK
Copy link
Contributor Author

Is this really change this situation?

You are right. In small cases i can't make this situation but in big program:

2023/12/16 17:00:15 [debug] 401238#401238: *38227 [lua] xmlCtxtReadMemory: cdata<struct _xmlDoc *>: 0x462fc210, ctx=cdata<struct _xmlParserCtxt *>: 0x11af1200
2023/12/16 17:00:15 [debug] 401238#401238: *38227 [lua] xmlUnlinkNode: cdata<struct _xmlNode *>: 0x131316d0, doc=cdata<struct _xmlDoc *>: 0x462fc210
2023/12/16 17:00:15 [debug] 401238#401238: *38227 [lua] xmlUnlinkNode: cdata<struct _xmlNode *>: 0x146bb4b0, doc=cdata<struct _xmlDoc *>: 0x462fc210
2023/12/16 17:00:20 [debug] 401238#401238: [lua] xmlFreeDoc: cdata<struct _xmlDoc *>: 0x462fc210, ctx=cdata<struct _xmlParserCtxt *>: 0x11af1200
2023/12/16 17:00:20 [debug] 401238#401238: [lua] xmlFreeNode: cdata<struct _xmlNode *>: 0x131316d0, doc=cdata<struct _xmlDoc *>: 0x462fc210

reverse order. But it happens only on destroy vm

==401238== Invalid read of size 8
==401238==    at 0xDB56676: xmlFreeNode__internal_alias (tree.c:3757)
==401238==    by 0xDB56676: xmlFreeNode (tree.c:3735)
==401238==    by 0x948AFC5: lj_vm_ffi_call (in /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2.1.0)
==401238==    by 0x94D5A67: lj_ccall_func (lj_ccall.c:1382)
==401238==    by 0x94EB9BA: lj_cf_ffi_meta___call (lib_ffi.c:230)
==401238==    by 0x9488BA5: lj_BC_FUNCC (in /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2.1.0)
==401238==    by 0x948B323: gc_call_finalizer.isra.3 (lj_gc.c:520)
==401238==    by 0x948C543: lj_gc_finalize_cdata (lj_gc.c:596)
==401238==    by 0x9493D80: cpfinalize (lj_state.c:272)
==401238==    by 0x9488F9B: lj_vm_cpcall (inI will try /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2.1.0)
==401238==    by 0x9494163: lua_close (lj_state.c:299)
==401238==    by 0x9244C5C: ngx_http_lua_cleanup_vm (ngx_http_lua_util.c:4034)
==401238==    by 0x9244C5C: ngx_http_lua_cleanup_vm (ngx_http_lua_util.c:4013)
==401238==    by 0x41658D: ngx_destroy_pool (ngx_palloc.c:133)

while service works this situation isn't happening.

I will think how to solve it. Maybe add a list of unlinked nodes to document and free it on document destroy. This may be only in case of destroy last unlinked node.

@ZigzagAK
Copy link
Contributor Author

Maybe it works:

function Document.new(raw_document, errors)
  if not errors then
    errors = {}
  end
  local unlinked_nodes = {}
  local document = {
    raw_document = raw_document,
    errors = errors,
    unlinked_nodes = unlinked_nodes
  }
  ffi.gc(document.raw_document, function(pdocument)
    for _,node in ipairs(unlinked_nodes) do
      print_dbg("Free unlinked: ", node)
      libxml2.xmlFreeNode(node)
    end
    print_dbg("xmlFreeDoc: ", pdocument)
    libxml2.xmlFreeDoc(pdocument)
  end)
  setmetatable(document, metatable)
  return document
end
function methods:unlink()
  Node.unlink(self)
  local unlinked_nodes = self.document.unlinked_nodes
  table.insert(unlinked_nodes, self.node)
  return self
end
function libxml2.xmlUnlinkNode(node)
  xml2.xmlUnlinkNode(node)
  print_dbg("xmlUnlinkNode: ", node, ", doc=", node.doc)
end

First checks is OK.

@ZigzagAK
Copy link
Contributor Author

Last version also works fine with:

local xmlua = require "xmlua"

local xml = [[<?xml version='1.0' encoding = 'windows-1251'?>
<ROOT>
   <A>
        <B>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </B>
        <B>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </B>
        <X>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </X>
        <X>
               <C>1</C>
               <C>1</C>
               <C>1</C>
        </X>
   </A>
</ROOT>]]

local doc = xmlua.XML.parse(xml)

local a = doc:search("/ROOT/A")
local b = doc:search("/ROOT/A/B")

a:unlink()

a = nil
doc = nil

collectgarbage("collect")
collectgarbage("collect")

b:unlink()
b = nil

collectgarbage("collect")
collectgarbage("collect")
xmlNewParserCtxt: 	cdata<struct _xmlParserCtxt *>: 0x059d27c0
xmlCtxtReadMemory: 	cdata<struct _xmlDoc *>: 0x059d72f0	, ctx=	cdata<struct _xmlParserCtxt *>: 0x059d27c0
xmlFreeDoc: 	cdata<struct _xmlDoc *>: 0x059d72f0	, ctx=	cdata<struct _xmlParserCtxt *>: 0x059d27c0
xmlNewParserCtxt: 	cdata<struct _xmlParserCtxt *>: 0x05a0d600
xmlCtxtReadMemory: 	cdata<struct _xmlDoc *>: 0x05a23460	, ctx=	cdata<struct _xmlParserCtxt *>: 0x05a0d600
xmlUnlinkNode: 	cdata<struct _xmlNode *>: 0x05a257c0	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
xmlFreeParserCtxt: 	cdata<struct _xmlParserCtxt *>: 0x059d27c0
xmlUnlinkNode: 	cdata<struct _xmlNode *>: 0x05a259a0	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
xmlUnlinkNode: 	cdata<struct _xmlNode *>: 0x05a26300	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
Free unlinked: 	cdata<struct _xmlNode *>: 0x05a257c0
xmlFreeNode: 	cdata<struct _xmlNode *>: 0x05a257c0	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
Free unlinked: 	cdata<struct _xmlNode *>: 0x05a26300
xmlFreeNode: 	cdata<struct _xmlNode *>: 0x05a26300	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
Free unlinked: 	cdata<struct _xmlNode *>: 0x05a259a0
xmlFreeNode: 	cdata<struct _xmlNode *>: 0x05a259a0	, doc=	cdata<struct _xmlDoc *>: 0x05a23460
xmlFreeDoc: 	cdata<struct _xmlDoc *>: 0x05a23460
xmlFreeParserCtxt: 	cdata<struct _xmlParserCtxt *>: 0x05a0d600

@ZigzagAK ZigzagAK force-pushed the master branch 2 times, most recently from 68ca520 to c4fda2e Compare December 16, 2023 19:51
@ZigzagAK
Copy link
Contributor Author

ZigzagAK commented Dec 17, 2023

Fixed serialization memory leak if using custom encoding.

 SSI,                30000,            979200000, UNKNOWN
#0  0x00007fc5d82ec7d6 malloc ./heap_usage_tracker/libheap_usage_tracker.so
#1  0x00007fc5d68d04c4 __gconv_open /lib64/libc.so.6
#2  0x00007fc5d68d0044 iconv_open /lib64/libc.so.6
#3  0x00007fc5ce6f6f88 xmlFindCharEncodingHandler /lib64/libxml2.so.2
#4  0x00007fc5ce7d0c8d ?? () /lib64/libxml2.so.2
#5  0x00007fc5ce7d1267 xmlSaveToBuffer /lib64/libxml2.so.2
#6  0x00007fc5d2c29fc5 ?? () /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2
#7  0x00007fc5d2c74a67 ?? () /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2
#8  0x00007fc5d2c8a9ba ?? () /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2
#9  0x00007fc5d2c27ba5 ?? () /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2
#10 0x00007fc5d2c3cccb lua_pcall /opt/api_gateway_3/apigw/lib/libluajit-5.1.so.2
#11 0x00007fc5d2edec75 ngx_http_lua_body_filter_by_chunk /opt/api_gateway_3/apigw/modules/ngx_http_lua_module.so
#12 0x00007fc5d2ededd7 ngx_http_lua_body_filter_inline /opt/api_gateway_3/apigw/modules/ngx_http_lua_module.so
#13 0x00007fc5d2ede925 ?? () /opt/api_gateway_3/apigw/modules/ngx_http_lua_module.so
#14 0x000000000041929f ngx_output_chain nginx: worker process

libxml2 don't cleanup xmlSaveCtxtPtr->handler in xmlFreeSaveCtxt. I don't know why.
Workaround: cleanup manually with xmlCharEncCloseFunc before xmlSaveClose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants