]> granicus.if.org Git - postgresql/commitdiff
Back-patch changes of 2009-05-13 in xml.c's memory management.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Mar 2010 02:21:40 +0000 (02:21 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Mar 2010 02:21:40 +0000 (02:21 +0000)
I was afraid to do this when these changes were first made, but now that
8.4 has seen some field use it should be all right to back-patch.  These
changes are really quite necessary in order to give xml.c any hope of
co-existing with loadable modules that also wish to use libxml2.

src/backend/access/transam/xact.c
src/backend/utils/adt/xml.c
src/include/utils/xml.h

index cc38c6ff131cb91e913af8dca740366fa27945ca..b0076dfd7d252c163540a4fe17d0ac7d1fd789cf 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257.2.6 2010/01/24 21:49:39 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257.2.7 2010/03/01 02:21:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,7 +46,6 @@
 #include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/relcache.h"
-#include "utils/xml.h"
 
 
 /*
@@ -1743,7 +1742,6 @@ CommitTransaction(void)
 
        AtEOXact_GUC(true, 1);
        AtEOXact_SPI(true);
-       AtEOXact_xml();
        AtEOXact_on_commit_actions(true);
        AtEOXact_Namespace(true);
        /* smgrcommit already done */
@@ -1976,7 +1974,6 @@ PrepareTransaction(void)
        /* PREPARE acts the same as COMMIT as far as GUC is concerned */
        AtEOXact_GUC(true, 1);
        AtEOXact_SPI(true);
-       AtEOXact_xml();
        AtEOXact_on_commit_actions(true);
        AtEOXact_Namespace(true);
        /* smgrcommit already done */
@@ -2122,7 +2119,6 @@ AbortTransaction(void)
 
                AtEOXact_GUC(false, 1);
                AtEOXact_SPI(false);
-               AtEOXact_xml();
                AtEOXact_on_commit_actions(false);
                AtEOXact_Namespace(false);
                smgrabort();
@@ -3956,7 +3952,6 @@ AbortSubTransaction(void)
 
                AtEOXact_GUC(false, s->gucNestLevel);
                AtEOSubXact_SPI(false, s->subTransactionId);
-               AtEOXact_xml();
                AtEOSubXact_on_commit_actions(false, s->subTransactionId,
                                                                          s->parent->subTransactionId);
                AtEOSubXact_Namespace(false, s->subTransactionId,
index ed6b154e2846098186a5fb25c13558203d1a1034..8e0e28fb1ea477ac21d340d667a3aadc4ed090a8 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.68.2.11 2009/09/04 10:49:50 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.68.2.12 2010/03/01 02:21:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /*
  * Notes on memory management:
  *
- * Via callbacks, libxml is told to use palloc and friends for memory
- * management, within a context that we reset at transaction end (and also at
- * subtransaction abort) to prevent memory leaks.  Resetting at transaction or
- * subtransaction abort is necessary since we might have thrown a longjmp
- * while some data structures were not linked from anywhere persistent.
- * Resetting at transaction commit might not be necessary, but seems a good
- * idea to forestall long-term leaks.
- *
  * Sometimes libxml allocates global structures in the hope that it can reuse
- * them later on.  Therefore, before resetting LibxmlContext, we must tell
- * libxml to discard any global data it has.  The libxml API documentation is
- * not very good about specifying this, but for now we assume that
- * xmlCleanupParser() will get rid of anything we need to worry about.
- *
- * We use palloc --- which will throw a longjmp on error --- for allocation
- * callbacks that officially should act like malloc, ie, return NULL on
- * out-of-memory.  This is a bit risky since there is a chance of leaving
- * persistent libxml data structures in an inconsistent partially-constructed
- * state, perhaps leading to crash in xmlCleanupParser().  However, as of
- * early 2008 it is *known* that libxml can crash on out-of-memory due to
- * inadequate checks for NULL returns, so this behavior seems the lesser
- * of two evils.
+ * them later on.  This makes it impractical to change the xmlMemSetup
+ * functions on-the-fly; that is likely to lead to trying to pfree() chunks
+ * allocated with malloc() or vice versa.  Since libxml might be used by
+ * loadable modules, eg libperl, our only safe choices are to change the
+ * functions at postmaster/backend launch or not at all.  Since we'd rather
+ * not activate libxml in sessions that might never use it, the latter choice
+ * is the preferred one.  However, for debugging purposes it can be awfully
+ * handy to constrain libxml's allocations to be done in a specific palloc
+ * context, where they're easy to track.  Therefore there is code here that
+ * can be enabled in debug builds to redirect libxml's allocations into a
+ * special context LibxmlContext.  It's not recommended to turn this on in
+ * a production build because of the possibility of bad interactions with
+ * external modules.
  */
+/* #define USE_LIBXMLCONTEXT */
 
 #include "postgres.h"
 
@@ -92,19 +85,25 @@ XmlOptionType xmloption;
 #ifdef USE_LIBXML
 
 static StringInfo xml_err_buf = NULL;
+
+static void xml_ereport(int level, int sqlcode, const char *msg);
+static void xml_errorHandler(void *ctxt, const char *msg,...);
+static void xml_ereport_by_code(int level, int sqlcode,
+                                       const char *msg, int errcode);
+
+#ifdef USE_LIBXMLCONTEXT
+
 static MemoryContext LibxmlContext = NULL;
 
-static void xml_init(void);
 static void xml_memory_init(void);
-static void xml_memory_cleanup(void);
 static void *xml_palloc(size_t size);
 static void *xml_repalloc(void *ptr, size_t size);
 static void xml_pfree(void *ptr);
 static char *xml_pstrdup(const char *string);
-static void xml_ereport(int level, int sqlcode, const char *msg);
-static void xml_errorHandler(void *ctxt, const char *msg,...);
-static void xml_ereport_by_code(int level, int sqlcode,
-                                       const char *msg, int errcode);
+
+#endif /* USE_LIBXMLCONTEXT */
+
+static void xml_init(void);
 static xmlChar *xml_text2xmlChar(text *in);
 static int parse_xml_decl(const xmlChar * str, size_t *lenp,
                           xmlChar ** version, xmlChar ** encoding, int *standalone);
@@ -154,15 +153,15 @@ static void SPI_sql_row_to_xmlelement(int rownum, StringInfo result,
 #ifdef USE_LIBXML
 
 static int
-xmlChar_to_encoding(xmlChar * encoding_name)
+xmlChar_to_encoding(const xmlChar *encoding_name)
 {
-       int                     encoding = pg_char_to_encoding((char *) encoding_name);
+       int                     encoding = pg_char_to_encoding((const char *) encoding_name);
 
        if (encoding < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("invalid encoding name \"%s\"",
-                                               (char *) encoding_name)));
+                                               (const char *) encoding_name)));
        return encoding;
 }
 #endif
@@ -587,8 +586,8 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
        int                     i;
        ListCell   *arg;
        ListCell   *narg;
-       xmlBufferPtr buf;
-       xmlTextWriterPtr writer;
+       xmlBufferPtr buf = NULL;
+       xmlTextWriterPtr writer = NULL;
 
        /*
         * We first evaluate all the arguments, then start up libxml and create
@@ -635,8 +634,16 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
        /* now safe to run libxml */
        xml_init();
 
+       PG_TRY();
+       {
        buf = xmlBufferCreate();
+       if (!buf)
+               xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+                                       "could not allocate xmlBuffer");
        writer = xmlNewTextWriterMemory(buf, 0);
+       if (!writer)
+               xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+                                       "could not allocate xmlTextWriter");
 
        xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name);
 
@@ -662,9 +669,23 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
        }
 
        xmlTextWriterEndElement(writer);
+
+       /* we MUST do this now to flush data out to the buffer ... */
        xmlFreeTextWriter(writer);
+       writer = NULL;
 
        result = xmlBuffer_to_xmltype(buf);
+       }
+       PG_CATCH();
+       {
+               if (writer)
+                       xmlFreeTextWriter(writer);
+               if (buf)
+                       xmlBufferFree(buf);
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
        xmlBufferFree(buf);
 
        return result;
@@ -821,6 +842,7 @@ xml_is_document(xmltype *arg)
        xmlDocPtr       doc = NULL;
        MemoryContext ccxt = CurrentMemoryContext;
 
+       /* We want to catch ereport(INVALID_XML_DOCUMENT) and return false */
        PG_TRY();
        {
                doc = xml_parse((text *) arg, XMLOPTION_DOCUMENT, true,
@@ -858,19 +880,6 @@ xml_is_document(xmltype *arg)
 }
 
 
-/*
- * xml cleanup function for transaction end.  This is also called on
- * subtransaction abort; see notes at top of file for rationale.
- */
-void
-AtEOXact_xml(void)
-{
-#ifdef USE_LIBXML
-       xml_memory_cleanup();
-#endif
-}
-
-
 #ifdef USE_LIBXML
 
 /*
@@ -908,8 +917,10 @@ xml_init(void)
                /* Now that xml_err_buf exists, safe to call xml_errorHandler */
                xmlSetGenericErrorFunc(NULL, xml_errorHandler);
 
+#ifdef USE_LIBXMLCONTEXT
                /* Set up memory allocation our way, too */
                xml_memory_init();
+#endif
 
                /* Check library compatibility */
                LIBXML_TEST_VERSION;
@@ -923,14 +934,13 @@ xml_init(void)
                resetStringInfo(xml_err_buf);
 
                /*
-                * We re-establish the callback functions every time.  This makes it
-                * safe for other subsystems (PL/Perl, say) to also use libxml with
+                * We re-establish the error callback function every time.  This makes
+                * it safe for other subsystems (PL/Perl, say) to also use libxml with
                 * their own callbacks ... so long as they likewise set up the
-                * callbacks on every use.      It's cheap enough to not be worth worrying
+                * callbacks on every use. It's cheap enough to not be worth worrying
                 * about, anyway.
                 */
                xmlSetGenericErrorFunc(NULL, xml_errorHandler);
-               xml_memory_init();
        }
 }
 
@@ -1142,7 +1152,7 @@ static bool
 print_xml_decl(StringInfo buf, const xmlChar * version,
                           pg_enc encoding, int standalone)
 {
-       xml_init();
+       xml_init();                                     /* why is this here? */
 
        if ((version && strcmp((char *) version, PG_XML_DEFAULT_VERSION) != 0)
                || (encoding && encoding != PG_UTF8)
@@ -1181,7 +1191,10 @@ print_xml_decl(StringInfo buf, const xmlChar * version,
 /*
  * Convert a C string to XML internal representation
  *
- * TODO maybe, libxml2's xmlreader is better? (do not construct DOM,
+ * Note: it is caller's responsibility to xmlFreeDoc() the result,
+ * else a permanent memory leak will ensue!
+ *
+ * TODO maybe libxml2's xmlreader is better? (do not construct DOM,
  * yet do not use SAX - see xmlreader.c)
  */
 static xmlDocPtr
@@ -1202,13 +1215,18 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
                                                                                   encoding,
                                                                                   PG_UTF8);
 
+       /* Start up libxml and its parser (no-ops if already done) */
        xml_init();
        xmlInitParser();
+
        ctxt = xmlNewParserCtxt();
        if (ctxt == NULL)
                xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
                                        "could not allocate parser context");
 
+       /* Use a TRY block to ensure the ctxt is released */
+       PG_TRY();
+       {
        if (xmloption_arg == XMLOPTION_DOCUMENT)
        {
                /*
@@ -1248,9 +1266,19 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
                res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0,
                                                                                           utf8string + count, NULL);
                if (res_code != 0)
+               {
+                       xmlFreeDoc(doc);
                        xml_ereport(ERROR, ERRCODE_INVALID_XML_CONTENT,
                                                "invalid XML content");
+               }
+       }
+       }
+       PG_CATCH();
+       {
+               xmlFreeParserCtxt(ctxt);
+               PG_RE_THROW();
        }
+       PG_END_TRY();
 
        xmlFreeParserCtxt(ctxt);
 
@@ -1275,18 +1303,16 @@ xml_text2xmlChar(text *in)
 }
 
 
+#ifdef USE_LIBXMLCONTEXT
+
 /*
- * Manage the special context used for all libxml allocations
+ * Manage the special context used for all libxml allocations (but only
+ * in special debug builds; see notes at top of file)
  */
 static void
 xml_memory_init(void)
 {
-       /*
-        * Create memory context if not there already.  We make it a child of
-        * TopMemoryContext, even though our current policy is that it doesn't
-        * survive past transaction end, because we want to be really really
-        * sure it doesn't go away before we've called xmlCleanupParser().
-        */
+       /* Create memory context if not there already */
        if (LibxmlContext == NULL)
                LibxmlContext = AllocSetContextCreate(TopMemoryContext,
                                                                                          "LibxmlContext",
@@ -1298,20 +1324,6 @@ xml_memory_init(void)
        xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup);
 }
 
-static void
-xml_memory_cleanup(void)
-{
-       if (LibxmlContext != NULL)
-       {
-               /* Give libxml a chance to clean up dangling pointers */
-               xmlCleanupParser();
-
-               /* And flush the context */
-               MemoryContextDelete(LibxmlContext);
-               LibxmlContext = NULL;
-       }
-}
-
 /*
  * Wrappers for memory management functions
  */
@@ -1344,6 +1356,8 @@ xml_pstrdup(const char *string)
        return MemoryContextStrdup(LibxmlContext, string);
 }
 
+#endif /* USE_LIBXMLCONTEXT */
+
 
 /*
  * Wrapper for "ereport" function for XML-related errors.  The "msg"
@@ -1773,25 +1787,48 @@ map_sql_value_to_xml_value(Datum value, Oid type)
                        case BYTEAOID:
                                {
                                        bytea      *bstr = DatumGetByteaPP(value);
-                                       xmlBufferPtr buf;
-                                       xmlTextWriterPtr writer;
+                                       xmlBufferPtr buf = NULL;
+                                       xmlTextWriterPtr writer = NULL;
                                        char       *result;
 
                                        xml_init();
 
-                                       buf = xmlBufferCreate();
-                                       writer = xmlNewTextWriterMemory(buf, 0);
-
-                                       if (xmlbinary == XMLBINARY_BASE64)
-                                               xmlTextWriterWriteBase64(writer, VARDATA_ANY(bstr),
-                                                                                                0, VARSIZE_ANY_EXHDR(bstr));
-                                       else
-                                               xmlTextWriterWriteBinHex(writer, VARDATA_ANY(bstr),
-                                                                                                0, VARSIZE_ANY_EXHDR(bstr));
+                                       PG_TRY();
+                                       {
+                                               buf = xmlBufferCreate();
+                                               if (!buf)
+                                                       xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+                                                                               "could not allocate xmlBuffer");
+                                               writer = xmlNewTextWriterMemory(buf, 0);
+                                               if (!writer)
+                                                       xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
+                                                                               "could not allocate xmlTextWriter");
+
+                                               if (xmlbinary == XMLBINARY_BASE64)
+                                                       xmlTextWriterWriteBase64(writer, VARDATA_ANY(bstr),
+                                                                                                        0, VARSIZE_ANY_EXHDR(bstr));
+                                               else
+                                                       xmlTextWriterWriteBinHex(writer, VARDATA_ANY(bstr),
+                                                                                                        0, VARSIZE_ANY_EXHDR(bstr));
+
+                                               /* we MUST do this now to flush data out to the buffer */
+                                               xmlFreeTextWriter(writer);
+                                               writer = NULL;
+
+                                               result = pstrdup((const char *) xmlBufferContent(buf));
+                                       }
+                                       PG_CATCH();
+                                       {
+                                               if (writer)
+                                                       xmlFreeTextWriter(writer);
+                                               if (buf)
+                                                       xmlBufferFree(buf);
+                                               PG_RE_THROW();
+                                       }
+                                       PG_END_TRY();
 
-                                       xmlFreeTextWriter(writer);
-                                       result = pstrdup((const char *) xmlBufferContent(buf));
                                        xmlBufferFree(buf);
+
                                        return result;
                                }
 #endif   /* USE_LIBXML */
@@ -3239,25 +3276,46 @@ SPI_sql_row_to_xmlelement(int rownum, StringInfo result, char *tablename,
 static text *
 xml_xmlnodetoxmltype(xmlNodePtr cur)
 {
-       xmlChar    *str;
        xmltype    *result;
-       size_t          len;
-       xmlBufferPtr buf;
 
        if (cur->type == XML_ELEMENT_NODE)
        {
+               xmlBufferPtr buf;
+
                buf = xmlBufferCreate();
-               xmlNodeDump(buf, NULL, cur, 0, 1);
-               result = xmlBuffer_to_xmltype(buf);
+               PG_TRY();
+               {
+                       xmlNodeDump(buf, NULL, cur, 0, 1);
+                       result = xmlBuffer_to_xmltype(buf);
+               }
+               PG_CATCH();
+               {
+                       xmlBufferFree(buf);
+                       PG_RE_THROW();
+               }
+               PG_END_TRY();
                xmlBufferFree(buf);
        }
        else
        {
+               xmlChar    *str;
+
                str = xmlXPathCastNodeToString(cur);
-               len = strlen((char *) str);
-               result = (text *) palloc(len + VARHDRSZ);
-               SET_VARSIZE(result, len + VARHDRSZ);
-               memcpy(VARDATA(result), str, len);
+               PG_TRY();
+               {
+                       size_t          len;
+
+                       len = strlen((char *) str);
+                       result = (text *) palloc(len + VARHDRSZ);
+                       SET_VARSIZE(result, len + VARHDRSZ);
+                       memcpy(VARDATA(result), str, len);
+               }
+               PG_CATCH();
+               {
+                       xmlFree(str);
+                       PG_RE_THROW();
+               }
+               PG_END_TRY();
                xmlFree(str);
        }
 
@@ -3284,11 +3342,11 @@ xpath(PG_FUNCTION_ARGS)
        xmltype    *data = PG_GETARG_XML_P(1);
        ArrayType  *namespaces = PG_GETARG_ARRAYTYPE_P(2);
        ArrayBuildState *astate = NULL;
-       xmlParserCtxtPtr ctxt;
-       xmlDocPtr       doc;
-       xmlXPathContextPtr xpathctx;
-       xmlXPathCompExprPtr xpathcomp;
-       xmlXPathObjectPtr xpathobj;
+       xmlParserCtxtPtr ctxt = NULL;
+       xmlDocPtr       doc = NULL;
+       xmlXPathContextPtr xpathctx = NULL;
+       xmlXPathCompExprPtr xpathcomp = NULL;
+       xmlXPathObjectPtr xpathobj = NULL;
        char       *datastr;
        int32           len;
        int32           xpath_len;
@@ -3347,8 +3405,6 @@ xpath(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DATA_EXCEPTION),
                                 errmsg("empty XPath expression")));
 
-       xml_init();
-
        /* These extra chars for string and xpath_expr allow for hacks below */
 
        string = (xmlChar *) palloc((len + 8) * sizeof(xmlChar));
@@ -3357,10 +3413,12 @@ xpath(PG_FUNCTION_ARGS)
 
        memcpy (string, datastr, len);
        string[len] = '\0';
-       
 
+       xml_init();
        xmlInitParser();
 
+       PG_TRY();
+       {
        /*
         * redundant XML parsing (two parsings for the same value during one
         * command execution are possible)
@@ -3478,10 +3536,8 @@ xpath(PG_FUNCTION_ARGS)
 
        xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
        if (xpathobj == NULL)   /* TODO: reason? */
-               ereport(ERROR,
-                               (errmsg("could not create XPath object")));
-
-       xmlXPathFreeCompExpr(xpathcomp);
+               xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
+                                       "could not create XPath object");
 
        /* return empty array in cases when nothing is found */
        if (xpathobj->nodesetval == NULL)
@@ -3502,8 +3558,25 @@ xpath(PG_FUNCTION_ARGS)
                                                                          CurrentMemoryContext);
                }
        }
+       }
+       PG_CATCH();
+       {
+               if (xpathobj)
+                       xmlXPathFreeObject(xpathobj);
+               if (xpathcomp)
+                       xmlXPathFreeCompExpr(xpathcomp);
+               if (xpathctx)
+                       xmlXPathFreeContext(xpathctx);
+               if (doc)
+                       xmlFreeDoc(doc);
+               if (ctxt)
+                       xmlFreeParserCtxt(ctxt);
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
 
        xmlXPathFreeObject(xpathobj);
+       xmlXPathFreeCompExpr(xpathcomp);
        xmlXPathFreeContext(xpathctx);
        xmlFreeDoc(doc);
        xmlFreeParserCtxt(ctxt);
index 99a46f8ef765fa9d8cbd50321fd50bd7962b35d0..7871e10b05055e08996f3146589792df0ef55ca9 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.23 2008/01/15 18:57:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.23.2.1 2010/03/01 02:21:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -75,8 +75,6 @@ extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped, boo
 extern char *map_xml_name_to_sql_identifier(char *name);
 extern char *map_sql_value_to_xml_value(Datum value, Oid type);
 
-extern void AtEOXact_xml(void);
-
 typedef enum
 {
        XMLBINARY_BASE64,