// Copyright 2009, Squish Tech, LLC. #include #include #include "libxmljs.h" #include "xml_document.h" #include "xml_node.h" #include "xml_sax_parser.h" #include "xml_namespace.h" namespace libxmljs { // ensure destruction at exit time // v8 doesn't cleanup its resources LibXMLJS LibXMLJS::instance; // track how much memory libxml2 is using int xml_memory_used = 0; // track how many nodes haven't been freed int nodeCount = 0; // wrapper for xmlMemMalloc to update v8's knowledge of memory used // the GC relies on this information void* xmlMemMallocWrap(size_t size) { void* res = xmlMemMalloc(size); // no need to udpate memory if we didn't allocate if (!res) { return res; } const int diff = xmlMemUsed() - xml_memory_used; xml_memory_used += diff; Nan::AdjustExternalMemory(diff); return res; } // wrapper for xmlMemFree to update v8's knowledge of memory used // the GC relies on this information void xmlMemFreeWrap(void* p) { xmlMemFree(p); // if v8 is no longer running, don't try to adjust memory // this happens when the v8 vm is shutdown and the program is exiting // our cleanup routines for libxml will be called (freeing memory) // but v8 is already offline and does not need to be informed // trying to adjust after shutdown will result in a fatal error #if (NODE_MODULE_VERSION > 14) if (v8::Isolate::GetCurrent() == 0 || v8::Isolate::GetCurrent()->IsDead()) { return; } #elif (NODE_MODULE_VERSION > 0x000B) if (v8::Isolate::GetCurrent() == 0) { return; } #else if (v8::V8::IsDead()) { return; } #endif const int diff = xmlMemUsed() - xml_memory_used; xml_memory_used += diff; Nan::AdjustExternalMemory(diff); } // wrapper for xmlMemRealloc to update v8's knowledge of memory used void* xmlMemReallocWrap(void* ptr, size_t size) { void* res = xmlMemRealloc(ptr, size); // if realloc fails, no need to update v8 memory state if (!res) { return res; } const int diff = xmlMemUsed() - xml_memory_used; xml_memory_used += diff; Nan::AdjustExternalMemory(diff); return res; } // wrapper for xmlMemoryStrdupWrap to update v8's knowledge of memory used char* xmlMemoryStrdupWrap(const char* str) { char* res = xmlMemoryStrdup(str); // if strdup fails, no need to update v8 memory state if (!res) { return res; } const int diff = xmlMemUsed() - xml_memory_used; xml_memory_used += diff; Nan::AdjustExternalMemory(diff); return res; } void deregisterNsList(xmlNs* ns) { while (ns != NULL) { if (ns->_private != NULL) { XmlNamespace* wrapper = static_cast(ns->_private); wrapper->xml_obj = NULL; ns->_private = NULL; } ns = ns->next; } } void deregisterNodeNamespaces(xmlNode* xml_obj) { xmlNs* ns = NULL; if ((xml_obj->type == XML_DOCUMENT_NODE) || #ifdef LIBXML_DOCB_ENABLED (xml_obj->type == XML_DOCB_DOCUMENT_NODE) || #endif (xml_obj->type == XML_HTML_DOCUMENT_NODE)) { ns = reinterpret_cast(xml_obj)->oldNs; } else if ((xml_obj->type == XML_ELEMENT_NODE) || (xml_obj->type == XML_XINCLUDE_START) || (xml_obj->type == XML_XINCLUDE_END)) { ns = xml_obj->nsDef; } if (ns != NULL) { deregisterNsList(ns); } } /* * Before libxmljs nodes are freed, they are passed to the deregistration * callback, (configured by `xmlDeregisterNodeDefault`). * * In deregistering each node, we update any wrapper (e.g. `XmlElement`, * `XmlAttribute`) to ensure that when it is destroyed, it doesn't try to * access the freed memory. * * Because namespaces (`xmlNs`) attached to nodes are also freed and may be * wrapped, it is necessary to update any wrappers (`XmlNamespace`) which have * been created for attached namespaces. */ void xmlDeregisterNodeCallback(xmlNode* xml_obj) { nodeCount--; deregisterNodeNamespaces(xml_obj); if (xml_obj->_private != NULL) { static_cast(xml_obj->_private)->xml_obj = NULL; xml_obj->_private = NULL; } return; } // this is called for any created nodes void xmlRegisterNodeCallback(xmlNode* xml_obj) { nodeCount++; } LibXMLJS::LibXMLJS() { // set the callback for when a node is created xmlRegisterNodeDefault(xmlRegisterNodeCallback); // set the callback for when a node is about to be freed xmlDeregisterNodeDefault(xmlDeregisterNodeCallback); // populated debugMemSize (see xmlmemory.h/c) and makes the call to // xmlMemUsed work, this must happen first! xmlMemSetup(xmlMemFreeWrap, xmlMemMallocWrap, xmlMemReallocWrap, xmlMemoryStrdupWrap); // initialize libxml LIBXML_TEST_VERSION; // initial memory usage xml_memory_used = xmlMemUsed(); } LibXMLJS::~LibXMLJS() { xmlCleanupParser(); } v8::Local listFeatures() { Nan::EscapableHandleScope scope; v8::Local target = Nan::New(); #define FEAT(x) Nan::Set(target, Nan::New(# x).ToLocalChecked(), \ Nan::New(xmlHasFeature(XML_WITH_ ## x))) // See enum xmlFeature in parser.h FEAT(THREAD); FEAT(TREE); FEAT(OUTPUT); FEAT(PUSH); FEAT(READER); FEAT(PATTERN); FEAT(WRITER); FEAT(SAX1); FEAT(FTP); FEAT(HTTP); FEAT(VALID); FEAT(HTML); FEAT(LEGACY); FEAT(C14N); FEAT(CATALOG); FEAT(XPATH); FEAT(XPTR); FEAT(XINCLUDE); FEAT(ICONV); FEAT(ISO8859X); FEAT(UNICODE); FEAT(REGEXP); FEAT(AUTOMATA); FEAT(EXPR); FEAT(SCHEMAS); FEAT(SCHEMATRON); FEAT(MODULES); FEAT(DEBUG); FEAT(DEBUG_MEM); FEAT(DEBUG_RUN); FEAT(ZLIB); FEAT(ICU); FEAT(LZMA); return scope.Escape(target); } NAN_METHOD(XmlMemUsed) { Nan::HandleScope scope; return info.GetReturnValue().Set(Nan::New(xmlMemUsed())); } NAN_METHOD(XmlNodeCount) { Nan::HandleScope scope; return info.GetReturnValue().Set(Nan::New(nodeCount)); } NAN_MODULE_INIT(init) { Nan::HandleScope scope; XmlDocument::Initialize(target); XmlSaxParser::Initialize(target); Nan::Set(target, Nan::New("libxml_version").ToLocalChecked(), Nan::New(LIBXML_DOTTED_VERSION).ToLocalChecked()); Nan::Set(target, Nan::New("libxml_parser_version").ToLocalChecked(), Nan::New(xmlParserVersion).ToLocalChecked()); Nan::Set(target, Nan::New("libxml_debug_enabled").ToLocalChecked(), Nan::New(debugging)); Nan::Set(target, Nan::New("features").ToLocalChecked(), listFeatures()); Nan::Set(target, Nan::New("libxml").ToLocalChecked(), target); Nan::SetMethod(target, "xmlMemUsed", XmlMemUsed); Nan::SetMethod(target, "xmlNodeCount", XmlNodeCount); } NAN_MODULE_WORKER_ENABLED(xmljs, init) } // namespace libxmljs